Книга
Книга
Глава 1. Организация современного компьютера17
Глава 2. Программно-аппаратная архитектура IA-32 процессоров Intel26
Глава 3. Система команд процессора IA-3258
Глава 4. Системы счисления74
Глава 5. Синтаксис ассемблера85
Глава 6. Первая программа121
Глава 7. Команды обмена данными146
Глава 8. Арифметические команды165
Глава 9. Логические команды и команды сдвига193
Глава 10. Команды передачи управления209
Глава 11. Программирование типовых управляющих структур236
Глава 12. Цепочечные команды249
Глава 13. Сложные структуры данных268
Глава 14. Макросредства языка ассемблера293
Глава 15. Модульное программирование324
Глава 16. Создание Windows-приложений на ассемблере365
Глава 17.Архитектура и программирование сопроцессора447
Вместо заключения510
Приложение. Система команд процессоров IA-32511
Список литературы625
Алфавитный указатель626
Содержание
Предисловие13
Благодарности16
Отиздательства16
Глава 1. Организация современного компьютера17
Машинный язык и язык ассемблера18
История процессоров Intel21
Итоги24
Глава 2. Программно-аппаратная архитектура IA-32
процессоров Intel26
Архитектура ЭВМ27
Архитектура IA-3229
Варианты микроархитектуры процессоров Intel30
Программная модель IA-3240
Итоги56
Глава 3. Система команд процессора IA-3258
Формат машинных команд IA-3259
Поле префиксов61
Код операци и63
Байт режима адресации mod r/m64
Байт масштаба, индекса и базы70
Поля смещения и непосредственного операнда71
Функциональная классификация машинных команд72
Итоги73
Глава 4. Системы счисления74
Двоичная система счисления75
Шестнадцатеричная система счисления76
Десятичная система счисления77
Перевод чисел из одной системы счисления в другую78
Перевод в десятичную систему счисления78
Перевод в двоичную систему счисления78
Перевод в шестнадцатеричную систему счисления79
808384 85 869097103о 9808384 85 869097103о 9Перевод дробных чисел
808384 85 869097103о 9
808384 85 869097103о 9
Перевод чисел со знаком
Итоги
Глава 5. Синтаксис ассемблера
Синтаксис ассемблера
Операнды
Операнды-выражения
Директивы сегментации
Простые типы данных ассемблера
Главаб. Первая программа121
Жизненный цикл программы121
Пример простой программы123
Процесс разработки программы128
Трансляция программы129
Компоновка программы134
Отладка программы135
Особенности разработки программ в MASM141
Особенности разработки программ в Microsoft Visual Studio142
Выбор пакета ассемблера143
Итоги144
Глава 7. Команды обмена данными146
Пересылка данных147
Ввод из порта и вывод в порт149
Работа с адресами и указателями156
Преобразование данных158
Работа со стеком160
Итоги164
Глава 8. Арифметические команды165
Обзор166
Целые двоичные числа167
Десятичные числа168
Арифметические операции над целыми двоичными числами170
Сложение двоичных чисел без знака170
Сложение двоичных чисел со знаком172
Вычитание двоичных чисел без знака173
Вычитание двоичных чисел со знаком175
Вычитание и сложение операндов большой размерности176
Умножение двоичных чисел без знака177
Умножение двоичных чисел со знаком179
Деление двоичных чисел без знака179
Деление двоичных чисел со знаком180
Вспомогательные команды для арифметических вычислений181
Команды преобразования типов181
Другие полезные команды183
Арифметические операции над двоично-десятичными числами183
Неупакованные BCD-числа184
Упакованные BCD-числа190
Итоги192
Глава 9. Логические команды и команды сдвига193
Логические данные194
Логические команды195
Команды сдвига199
Линейный сдвиг199
Циклический сдвиг201
Дополнительные команды сдвига203
Примеры работы с битовыми строками205
Рассогласование битовых строк205
Вставка битовых строк206
Извлечение битовых строк207
Пересылка битов207
Итоги207
Глава 10. Команды передачи управления209
Безусловные переходы214
Команда безусловного перехода215
Процедуры219
Условные переходы224
Команда сравнения225
Команды условного перехода и флаги227
Команды условного перехода и регистр ЕСХ/СХ228
Установка байта по условию229
Организация циклов229
Ограничение области видимости для меток234
Итоги235
Глава 11. Программирование типовых управляющих структур236
Условный оператор if-else237
Оператор выбора switch237
Операторы цикла240
Оператор цикла с предусловием while240
Операторы continue и break240
Оператор цикла с постусловием do-while241
Оператор итерационного цикла for241
Особенности пакета MASM242
Условная конструкция .IF242
Циклическая конструкция .REPEAT243
Циклическая конструкция .WHILE244
Конструкции .BREAK и .CONTINUE244
Комплексный пример245
Итоги248
Глава 12. Цепочечные команды249
Пересылка цепочек253
Команда MOVS253
Пересылка байтов, слов и двойных слов254
Сравнение цепочек255
Команда CMPS255
Сравнение байтов, слов и двойных слов258
Сканирование цепочек,259
Команда SCAS:259
Сканирование строки байтов, слов, двойных слов260
Загрузка элемента цепочки в аккумулятор261
Команда LODS261
Загрузка в регистр AL/AX/EAX байтов, слов, двойных слов262
Перенос элемента из аккумулятора в цепочку262
Команда STOS263
Сохранение в цепочке байта, слова, двойного слова
из регистра AL/AX/EAX264
Работас портами ввода-вывода265
Ввод элемента цепочки из порта ввода-вывода265
Вывод элемента цепочки в порт ввода-вывода265
Итоги266
Глава 13. Сложные структуры данных268
Массивы269
Описание и инициализация массива в программе269
Доступ кэлементам массива271
Двухмерные массивы275
Типовые операции с массивами278
Структуры280
Описание шаблона структуры281
Определение данных с типом структуры281
Методы работы со структурами282
Объединения284
Записи286
Описание записи286
Определение экземпляра записи287
Работа с записями288
Дополнительные возможности обработки290
Итоги292
Глава 14. Макросредства языка ассемблера293
Псевдооператоры EQU и =295
Макрокоманды297
Макродирективы305
Директивы WHILE и REPT305
Директива IRP307
Директива IRPC307
Директивы условной компиляции'.308
Директивы компиляции по условию308
Директивы генерации ошибок315
Константные выражения в условных директивах318
Дополнительные средства управления трансляцией319
Директивы управления файлом листинга321
Итоги322
Глава 15. Модульное программирование324
Структурное программирование325
Процедуры в языке ассемблера327
Передача аргументов через регистры331
Передача аргументов через общую область памяти332
Передача аргументов через стек334
Использование директив EXTRN и PUBLIC337
Возврат результата из процедуры340
Связь ассемблера с языками высокого уровня344
Встраиваемый ассемблерный код345
Внешний ассемблерный код346
Команды ENTER и LEAVE356
С и ассемблер359
Итоги364
Глава 16. Создание Windows-приложений на ассемблере365
Программирование оконных Windows-приложений367
Каркасное Windows-приложение на C/C++368
Каркасное Windows-приложение на ассемблере379
Стартовый код387
Главная функция389
Обработка сообщений в оконной функции398
Средства TASM для разработки Windows-приложений401
Углубленное программирование на ассемблере для Win32404
Ресурсы Windows-приложений на языке ассемблера406
Меню в Windows-приложениях406
Перерисовка изображения413
Окна диалога в Windows-приложениях420
Программирование консольных Windows-приложений435
Минимальная программа консольного приложения437
Организация высокоуровневого консольного ввода-вывода438
Пример программы консольного ввода-вывода440
Итоги446
Глава 17. Архитектура и программирование сопроцессора447
Архитектура сопроцессора448
Регистр состояния SWR453
Регистр управления CWR454
Регистр тегов TWR455
Форматы данных456
Двоичные целые числа457
Упакованные целые десятичные числа457
Вещественные числа458
Специальные численные значения463
Система команд сопроцессора466
Команды передачи данных468
Команды загрузки констант471
Команды сравнения данных472
Арифметические команды474
Команды трансцендентных функций482
Команды управления сопроцессором494
Исключения сопроцессора и их обработка500
Недействительная операция501
Деление на ноль501
Денормализация операнда501
Переполнение и антипереполнение502
Неточный результат502
Немаскируемая обработка исключений502
Использование отладчика505
Общие рекомендации по программированию сопроцессора507
Итоги508
Вместо заключения510
Приложение. Система команд процессоровIA-32511
Целочисленные команды513
Команды сопроцессора553
Команды блока м MX569
Команды блока ХММ (SSE и SSE2)587
Список литературы■625
Алфавитный указатель626
Предисловие
Перед вами второе, исправленное и дополненное, издание книги «Assembler» из серии книг «Учебное пособие» издательства «Питер». Материал подобран и выстроен таким образом, чтобы служить информационно-методической базой для самостоятельного и организованного в рамках различных вузовских дисциплин и факультативов изучения языка ассемблера. Исходя из этого, учебник адресован следующим категориям читателей:
преподавателям и студентам вузов для использования в качестве источника методически подобранной и систематизированной информации по различным аспектам применения ассемблера в контексте архитектуры Intel-совместимых процессоров;
занимающимся программированием и желающим освоить ассемблер для расширения своего профессионального кругозора, придания новых свойств своему опыту и для решения конкретных практических задач;
школьникам, углубленно изучающим программирование для компьютеров на базе Intel-совместимых процессоров, а также всем тем, кто интересуется различными аспектами низкоуровневого программирования.
Материал учебника, выбранный уровень и методика его изложения преследуют следующие цели:
изложить основы архитектуры Intel-совместимых процессоров;
показать неразрывную связь архитектуры процессора с его машинным языком;
* представить систему машинных команд в виде функциональных групп с тем, чтобы объяснить цели, которые преследовали разработчики процессора при введении той или иной команды в систему машинных команд;
научить использовать инструментальные средства разработки ассемблерных программ;
т научить осмысленному подходу к выбору средств ассемблера для реализации практических задач средней сложности.
Язык ассемблера является символическим представлением машинного
он неразрывно связан с архитектурой самого процессора. По мере внесения изменений в архитектуру процессора совершенствуется и язык ассемблера. По этой причине книга направлена на решение комплексной задачи — не просто рассмотреть ассемблер как еще один из десятков языков программирования, а показать
объективность его существования и неразрывную связь его конструкций с архитектурой конкретного процессора. Материал книги содержит описание основных особенностей архитектуры и системы команд процессоров Pentium Pro/MMX/П/ III/IV.
Изложение материала в учебнике ведется в форме глав, которых всего 17. Логически их можно разделить на четыре части.
я В первых шести главах приводятся сведения о том, что представляет собой современный компьютер, что подразумевают понятия архитектуры процессора и компьютера в целом. Приводится информация о системе и синтаксисе машинных команд и команд ассемблера. Важными являются сведения о жизненном цикле типовой программы на ассемблере и инструментальных средствах разработки ассемблерных программ.
• Вторая часть книги, начиная с главы 7, посвящена рассмотрению команд ассемблера в соответствии с их функциональным назначением. Этот материал является базовым, и его достаточно, чтобы научиться писать простые программы на ассемблере. Начиная с главы 11 обсуждаются специальные средства ассемблера, которые используются для написания программ средней сложности. Рассматриваются возможности ассемблера для работы со сложными структурами данных, механизм макрокоманд, вопросы организации модульного программирования, в том числе принципы связи с модулями, написанными на C/C++ и Pascal.
綴 Последние две главы учебника посвящены различным аспектам написания ассемблерных программ, использующих современные программно-аппаратные расширения. Приводятся подробные сведения о порядке разработки оконных и консольных Windows-приложений, применении сопроцессора.
в Важная часть учебника — его приложение. В нем собрана справочная информация о командах (вплоть до Pentium IV). Данных, которые приведены в приложении, достаточно для проведения широкого круга работ — от общего знакомства с системой машинных команд до поддержки процесса дизассемблирования на уровне машинных кодов.
^變 Вдругихприложениях, расположенныхнасайте http://www.piter.com/download, собрана справочная информация о различных средствах пакетов ассемблера MASM и TASM.
Таким образом, книга является самодостаточным учебным пособием. Ее использование позволяет сформировать фундаментальные знания по различным аспектам низкоуровневого программирования на языке ассемблера для Intel-совместимых компьютеров. В долгосрочной перспективе материал учебника может служить справочником.
По сравнению с первым изданием в учебнике сделаны многочисленные изменения. Во-первых, материал был приведен в соответствие с современным уровнем развития процессоров Intel (до Pentium IV). Во-вторых, исправлены ошибки и опечатки. Во многом это заслуга внимательных читателей, за что им отдельная благодарность. В-третьих, произведено перестроение материала с учетом существования своеобразных продолжений учебников — книг серии «Практика» издательства
«Питер». Для настоящего учебника пока существует одна такая книга — «Assembler: практика». Ее содержание посвящено углубленному изучению языка ассемблера на различных востребованных на практике задачах прикладного характера. Материал книги «Assembler: практика» может служить основой для выполнения курсовых и дипломных работ, не говоря уже о его использовании при работе над различными программными проектами. Планируется появление второй книги из этой серии, которая будет ориентирована на освещение вопросов системного программирования. Каждую из этих книг следует позиционировать следующим образом. Учебник формирует основы и принципы, а на определенном этапе начинает выполнять справочные функции. Книги серии «Практикум» представляют описания вариантов реализации востребованных на практике задач прикладного и системного характера.
Об ошибках следует сказать отдельные слова. Конечно, книги, а тем более учебники, не должны содержать неточностей и ошибок, но при нынешних темпах жизни и развития техники это — недостижимый идеал. Конечно, это не говорит о том, что нельзя написать книгу без ошибок. Безусловно можно, но для этого потребуется времени в несколько раз больше и ее выход в свет может оказаться совершенно бессмысленной затратой времени и сил, так как к этому моменту перестанет быть актуальным сам предмет, которому посвящена книга. Поэтому вопрос о том, что важнее — своевременная книга, содержащая определенное количество ошибок, но помогающая читателю решить актуальные проблемы сегодняшнего дня, или идеально выверенное издание, освещающее вопросы вчерашнего дня, остается философским. Книги, в которых много исходного кода, — это особый вид книг, которые можно охарактеризовать как «книги-программы», а в программах, как известно, последних ошибок не бывает. Более того, рискну высказать мнение, что с учебной целью ошибки даже полезны. Это подтверждает и свой, и чужой опыт. Пословица «На ошибках учатся» имеет скрытый смысл — наиболее устойчивые знания формируются именно при исправлении своих и чужих ошибок. Это даже не знания, это уже профессиональный опыт. Тем не менее мои рассуждения не следует рассматривать как оправдание ошибок первого издания учебника и будущих ошибок второго издания. Они неизбежны, и автор будет благодарен всем читателям, кто заметит ошибки, неточности и просто опечатки и сообщит о них редакции или автору по указанным далее адресам электронной почты.
Часто спрашивают, для программирования каких процессоров можно использовать учебник. Ответ — для Intel-совместимых процессоров. Под термином «Intel- совместимые процессоры» подразумеваются процессоры фирм Intel, AMD, VIA, Transmeta, полностью поддерживающие базовую систему команд процессоров Intel и полностью или частично поддерживающие различные расширения базовой системы команд процессоров Intel.
Что нужно для работы с книгой? Во-первых, компьютер на базе Intel-совместимого процессора. Во-вторых, пакету ассемблеров TASM и MASM. Причем лучше всего будет на этапе изучения использовать оба этих пакета. К сожалению, пакет TASM в самостоятельном виде уже не развивается, но работа с ним на этапе обучения достаточно комфортна, тем более что он имеет режим работы, позволяющий во многих случаях без дополнительной доработки переносить программы для
использования с пакетом MASM. Последняя доступная версия этого пакета — TASM 5.0. С пакетом MASM дела обстоят лучше — он развивается. Последние доступные версии — MASM 6.14 и MASM32 версии 7.0. И наконец, для работы нужен один из текстовых редакторов класса notepad.exe.
Благодарности
Хорошая традиция — выражение благодарности окружающим людям за их активный или пассивный вклад в появление книг на свет. Это не является некой формой похвальбы, а говорит лишь о том, что ты не один на белом свете и своими успехами и неудачам обязан многим людям, которых ты, возможно, никогда не видел и никогда не увидишь. Поэтому я рад и благодарен письму каждого читателя. Адрес электронной почты V_yurov@mail.ru всегда доступен для писем читателей относительно содержимого книг, пожеланий, сообщений о замеченных ошибках и неточностях. Особую благодарность выражаю жене Елене и детям — Саше и Юле.
От издательства
Ваши замечания, предложения, вопросы отправляйте по адресу электронной почты comp@piter.com (издательство «Интер», компьютерная редакция).
Мы будем рады узнать ваше мнение!
Все исходные тексты программ, приведенные в книге, а также дополнительные приложения вы сможете найти по адресу http://www.piter.com/download.
Подробную информацию о наших книгах вы найдете по адресу http:// www.piter.com.
Глава 1
Организация современного компьютера
Внешний вид типичного современного компьютера
Структурнаясхемакомпьютера
辫Место ассемблера
Историческая ретроспектива процессоров Intel
Современному человеку трудно представить свою жизнь без электронно-вычислительных машин (ЭВМ). В настоящее время любому желающему по силам собрать у себя на рабочем столе полноценный вычислительный центр, степень функциональности которого может быть ограничена только фантазией и финансовыми возможностями его обладателя.
В минимальной комплектации типовой персональный компьютер состоит из компонентов, показанных на рис.
Из рисунка видно, что компьютер образуют несколько физических устройств, каждое из которых подключено к одному блоку, называемому системным. Если рассуждать логически, то ясно, что он играет роль некоторого координирующего устройства. Попытка открыть корпус и заглянуть внутрь системного блока, скорее всего, не приведет к удовлетворению жажды познания — непонятная совокупность разнообразных плат, блоков, индикаторов и соединительных проводов вряд ли даст однозначные ответы на возможные вопросы. Внутри монитора вы тем более ничего интересного не найдете, за исключением возможности удовлетворить другую жажду — жажду приключений. Если хорошо поискать отверткой подходящий источник, то, в отличие от системного блока, можно довольно быстро получить разряд в несколько киловольт.
Системный блок
Рис. 1.1. Компьютер и периферийные устройства
Рассмотрим структурную схему типичного современного персонального компьютера. Она не претендует на безусловную точность и имеет целью лишь показать назначение, взаимосвязь и типовой состав его элементов.
На рис. 1.2 показана функциональная схема системного блока компьютера на базе процессоров семейства Intel. На схеме представлены: центральный процессор, оперативная память, внешние устройства. Все компоненты соединены между собой через системную шину. Системная шина имеет дополнительную шину — шину расширения. В компьютерах на базе Pentium в качестве такой шины используется шина PCI (Peripheral Component Interface), к которой подсоединяются внешние устройства, а также шины более ранних стандартов, например ISA (Industry Standard Architecture).
На рисунке показана самая общая схема сердца компьютера — процессора. Основу процессора составляют блок микропрограммного управления, исполнительное устройство, обозначенное как «конвейер», и регистры. Остальные компоненты процессора выполняют вспомогательные функции. Более подробный вариант этой схемы мы рассмотрим в следующей главе.
Машинный язык и язык ассемблера
Чтобы лучше понять принципы работы компьютера, давайте сравним его с человеком. У компьютера есть органы восприятия информации из внешнего мира — это клавиатура, мышь, накопители на магнитных дисках (на схеме они расположены под системными шинами). У компьютера есть органы, «переваривающие» полученную информацию, — это центральный процессор и оперативная память. И наконец, у компьютера есть органы речи, выдающие результаты переработки. Это также некоторые из устройств ввода-вывода, расположенные в нижней части схемы. Современным компьютерам, конечно, далеко до человека. Их можно сравнить с существами, взаимодействующими с внешним миром на уровне большого, но ограниченного набора безусловных рефлексов. Этот набор рефлексов образует систему машинных команд. На каком бы высоком уровне вы ни общались с компьютером, в конечном итоге все сводится к скучной и однообразной последователь-
Центральный процессор (Pentium Рг6/Н/Н1);УстройствоJ 1микропрограммного управленияfБлок I . микрокомандЦентральный процессор (Pentium Рг6/Н/Н1);УстройствоJ 1микропрограммного управленияfБлок I . микрокомандКонгКонгБуфер командБуфер командРегистрымикропроцессораРегистрымикропроцессораi Шина адресаГ in;* Шина энных• Шина управленияi4i Шина адресаГ in;* Шина энных• Шина управленияi4ОперативнаяпамятьОперативнаяпамятьУстройствовыборки/декодированияУстройствовыборки/декодированияУстройстводиспетчеризации/исполненияУстройстводиспетчеризации/исполненияБлок удаления и восстановленияБлок удаления и восстановления' Выборка' Выборка事……Загрузка事……ЗагрузкаX СохранениеX СохранениеКэш команд(Ы)Кэш команд(Ы)Кэш данных(L1)Кэш данных(L1)Устройство шинного интерфейсаУстройство шинного интерфейсаMLjШина кэша L2 (64 бита)MLjШина кэша L2 (64 бита)Кэш второго уровня (L2)Кэш второго уровня (L2)Системная шинаСистемная шинаJ Системный[контроллерJ Системный[контроллерШина PCIШина PCI
Центральный процессор (Pentium Рг6/Н/Н1);
УстройствоJ 1
микропрограммного управленияf
Блок I . микрокоманд
Центральный процессор (Pentium Рг6/Н/Н1);
УстройствоJ 1
микропрограммного управленияf
Блок I . микрокоманд
Конг
Конг
Буфер команд
Буфер команд
Регистрымикропроцессора
Регистрымикропроцессора
i Шина адреса
Г in
;* Шина энных
• Шина управления
i4
i Шина адреса
Г in
;* Шина энных
• Шина управления
i4
Оперативная
память
Оперативная
память
Устройствовыборки/декодирования
Устройствовыборки/декодирования
Устройстводиспетчеризации/исполнения
Устройстводиспетчеризации/исполнения
Блок удаления и восстановления
Блок удаления и восстановления
' Выборка
' Выборка
事……
Загрузка
事……
Загрузка
X Сохранение
X Сохранение
Кэш команд
(Ы)
Кэш команд
(Ы)
Кэш данных
(L1)
Кэш данных
(L1)
Устройство шинного интерфейса
Устройство шинного интерфейса
MLj
Шина кэша L2 (64 бита)
MLj
Шина кэша L2 (64 бита)
Кэш второго уровня (L2)
Кэш второго уровня (L2)
Системная шина
Системная шина
J Системный
[контроллер
J Системный
[контроллер
Шина PCI
Шина PCI
7i7iШина адреса/данных
7
i
7
i
Таким образом, внешне являясь полиглотом, компьютер понимает только один язык — язык машинных команд. Конечно, для общения и работы с компьютером необязательно знать этот язык, но практически любой профессиональный программист рано или поздно сталкивается с необходимостью его изучения. К счастью, при этом человеку не нужно пытаться постичь значение различных комбинаций двоичных чисел, так как еще в 50-е гг. программисты стали использовать для программирования символический аналог машинного языка, который назвали языком ассемблера. Этот язык точно отражает все особенности машинного языка. Именно поэтому, в отличие от языков высокого уровня, язык ассемблера для каждого типа компьютеров свой. Более того, бессмысленны разговоры о том, что ассемблер как язык программирования устарел и знать его необязательно. АССЕМБЛЕР ОБЪЕКТИВЕН, и подобные разговоры в определенной ситуации могут выглядеть довольно глупо (особенно для особо «продвинутых» программистов).
На первый взгляд ассемблер кажется очень сложным, но это не так. Для освоения этого языка совершенно не нужно выучивать наизусть все его команды и директивы. Более того, в большинстве случаев для практической работы достаточно понимания основных концепций и идей, лежащих в основе языка ассемблера. Детали реализации той или иной команды всегда можно найти в справочнике команд, гораздо важнее понимать, какое место данная команда занимает в системе команд, в идеале хорошо было бы знать и цели, которые преследовали разработчики процессора, вводя данную команду в систему машинных команд. Одна из целей данного учебника — сформировать у читателя именно такое понимание языка ассемблера.
Из всего сказанного напрашивается вывод о том, что самую эффективную программу можно написать только на ассемблере (при условии, что ее пишет квалифицированный программист), так как этот язык является «родным» для компьютера. Здесь есть одно маленькое 《но»: это очень трудоемкий и требующий большого внимания и практического опыта процесс. Поэтому реально на ассемблере пишут в основном программы, которые должны обеспечить эффективную работу с аппаратной частью компьютера. Иногда на ассемблере пишутся критичные ко времени выполнения или расходованию памяти фрагменты программы. Впоследствии они оформляются в виде подпрограмм и совмещаются с кодом на языке высокого уровня.
В данном учебнике будут рассмотрены два фундаментальных для понимания логики функционирования компьютера вопроса: первый — архитектурные особенности процессора и основы его взаимоотношений с другими компонентами компьютера; второй — место и роль языка ассемблера во всем этом процессе. За основу в нашем рассмотрении будут взяты процессоры фирмы Intel. Необходимо отметить, что эти процессоры не являются единственными процессорами на рынке аппаратного обеспечения (hardware), хотя и занимают довольно большой сегмент этого рынка. Исторически сложилось так, что архитектура процессоров Intel полностью или частично поддерживается процессорами других фирм. Поэтому процессорам фирмы Intel приходится делить свой сегмент рынка с процессорами фирм AMD, VIA, Transmeta. Так как в своем сегменте рынка (Intel-совместимых процессоров) процессоры фирмы Intel являются стандартом де-факто, то в данном
учебнике речь будет идти исключительно о них, однако все приводимые примеры программ будут работать и на Intel-совместимых процессорах процессорах других фирм.
История процессоров Intel
Процессоры Intel в своем развитии прошли довольно сложный и насыщенный различными событиями путь. Корпорация Intel была основана в 1968 г. Первый процессор i4004 был разработан Intel в 1969 г. Он представлял собой 4-разрядное параллельное вычислительное устройство с 2300 транзисторами. Его возможности были сильно ограничены — он выполнял всего лишь четыре основные арифметические операции. Поначалу 14004 применялся в карманных калькуляторах. Позднее сфера его использования была расширена за счет различных систем управления, в частности, систем управления светофорами.
В 1972 г. был выпущен 8-разрядный процессор i8008 с адресацией внешней оперативной памяти размером 16 Кбайт. Неожиданный успех этого процессора подтолкнул разработчиков Intel к дальнейшим изысканиям. В 1974 г. был выпущен i8080, который, оставаясь 8-разрядным, мог адресовать 64 Кбайт оперативной памяти. Для того времени это было революцией, этот процессор был выпущен в миллионах экземпляров. Историю современных Intel-совместимых процессоров принято вести именно от i8080.
Очередной революционный процессор Intel — i8086 — появился в 1978 г. Его основные характеристики —наличие 16-разрядных регистров, 16-разрядной шины данных. Поддержка сегментной организации памяти наряду с 20-разрядной шиной адреса позволяла организовать адресацию памяти в пределах 1 Мбайт при доступности адресного пространства размером 256 Кбайт. С целью занятия более широкой ниши на рынке Intel вскоре представила более дешевый вариант этого процессора-i8088.При внутренней 16-разряднойархитектуре 18086он имел8-раз- рядную шину данных, вследствие чего был менее производителен. Важно отметить, что дешевизна достигалась в основном не за счет дешевизны самого процессора, азасчет удешевления конечного продукта — компьютера на его основе. Другой причиной появления и широкого распространения i8088 было наличие большого количества 8-разрядных внешних устройств. 8-разрядная шина данных i8088 позволяла упростить процесс сопряжения с этими устройствами. Отметим, что одним из внешних устройств, поддерживаемых процессорами i8086/88, был математический сопроцессор i8087,который мог устанавливаться в специальный разъем материнской платы. Это позволяло более эффективно выполнять операции с плавающей точкой в соответствии со стандартом IEEE-754. Важно также подчеркнуть, что, начиная с 18086/88, все процессоры Intel совместимы «снизу вверх». Следствием этого является гарантированная возможность запуска на современном процессоре Pentium практически любой программы, написанной для i8086/88 (естественно, при соответствующей поддержке системного программного обеспечения).
Согласно известному закону, программное обеспечение (software), подобно газу, стремится заполнить весь доступный ему объем. Поэтому поначалу кажущиеся
фантастическими возможности новых процессоров со временем становятся узами, тем самым подталкивая конструкторов процессоров искать новые архитектурные и технологические решения для перевода программ в новые, более свободные рамки существования. Оперативная память объемом 1 Мбайт — это много. Долгая жизнь процессоров i8086/88 подтверждает это. Но с течением времени даже такой объем памяти превратился в ограничение, в связи с чем стали применяться различные технологии для обхода этого ограничения. Многие, наверное, еще помнят загрузку MS-DOS с драйверами расширенной памяти EMS (Expanded Memory Specification), с помощью которых через окно размером 64 Кбайт можно было обращаться к внешней дополнительной памяти размером до 32 Мбайт. Но это не могло продолжаться вечно, и в 1982 г. Intel представляет процессор i80286. И хотя он не решил окончательно проблему ограничения пространства памяти, в нем были реализованы определенные архитектурные изменения, благодаря которым последующие модели процессоров позволили разрядить ситуацию с доступом к памяти. К главным нововведениям следует отнести расширение шины адреса до 24 битов, что позволило адресовать уже 16 Мбайт оперативной памяти, а также появление еще одного режима работы процессора — защищенного. В этом отношении данный процессор также можно считать революционным. До этого в процессорах Intel отсутствовала поддержка на процессорном уровне какой-либо защиты программ от взаимного влияния. Введение подобных средств в архитектуру процессоров Intel стимулировало производителей на разработку многозадачных операционных систем. Наиболее яркие примеры — Microsoft (ОС Windows) и IBM (OS/2). Справедливости ради следует отметить, что доминирующей системой для i286 была MS- DOS, и данный процессор использовался как более быстрый вариант i8086/88. Для широкого распространения многозадачные системы должны были пройти определенный путь развития.
Мощным стимулом к развитию многозадачных систем стали возможности, предоставляемые новым процессором i80386, выпущенным Intel в 1985 г. Это был первый 32-разрядный процессор, который положил начало семейству процессоров IA-32 (32-bit Intel Architecture). Главные отличительные особенности: 32-раз- рядные шины адреса и данных (адресация оперативной памяти — до 4 Гбайт); увеличенная до 32 битов разрядность внутренних регистров процессора; введение третьего режима работы процессора (режима виртуального процессора 8086); поддержка страничной адресации памяти, благодаря которой стало возможном за счет дисковой памяти виртуализировать доступ к оперативной памяти и теоретически расширить ее рамки вплоть до 4 Гбайт. Аналогично ситуации с i8086 и i8088, одновременно был выпущен более дешевый вариант процессора i80386 (i80386-DX) с 16-разрядной шиной данных — i80386-SX. При необходимости на материнскую плату можно было установить и математический сопроцессор i80387. Процессор i80386 первым из семейства процессоров Intel стал поддерживать возможность параллельного функционирования своих устройств: устройства шинного интерфейса (bus interface unit), блока предварительной выборки (code prefetch unit), блока декодирования команд (instruction decode unit), исполнительного блока (execution unit), блока сегментации (segment unit), блока страничной адресации (paging unit). Концепция параллельного функционирования устройств позже, а именно в i80486, стала основой другой концепции — конвейеризации вычислений.
Процессор i80486 появился в 1989 г. Основные его характеристики — наличие встроенного математического сопроцессора, поддержка многопроцессорного режима работы и работы с двумя видами кэш-памяти — внутренней размером 8 Кбайт (1-го уровня L1) и внешней кэш-памятью (2-го уровня L2). Важные изменения в архитектуре процессора i80486 коснулись концепции параллельных вычислений. Они стали воплощением идей, лежащих в основе RISC-технологии разработки процессоров. Согласно им, машинные команды со сложным алгоритмом работы (Complete-Instuction-Set Computing, CISC) на микропрограммном уровне реализовывались на основе более простых команд из ограниченного набора (Reduced Instruction Set Computer, RISC). Для выполнения CISC-команд требуется несколько тактов работы процессора, иногда несколько десятков. RISC-команды должны выполняться за один такт. Такое изменение внутреннего представления внешних команд, наряду с изменением последовательности их выполнения и режимов декодирования, позволило реализовать реальную конвейеризацию вычислений. В результате выполнение CISC-команды происходило за один такт процессора. Конвейер процессора i80486 состоял из 5 ступеней. Начиная с процессора i80486 процессоры Intel поддерживают различные концепции энергосбережения. Интересно, что совершенствование процессора i80486 шло в ходе его промышленного производства. Вследствие этого по своим возможностям следующие по времени выпуска процессоры i80486 в большей или меньшей степени отличались от предыдущих.
Ну и, наконец, ЭПОХА PENTIUM. Знаменитый своей ошибкой блока с плавающей точкой первый Pentium — Pentium-60 — был представлен в начале 1993 г. Благодаря суперскалярной архитектуре (с двумя конвейерами, известными как и и v) он мог выполнять две машинные инструкции за один такт. К внутреннему кэшу команд добавлен внутренний 8-килобайтный кэш данных. Реализована технология предсказания переходов (branch prediction). Для увеличения пропускной способности при обработке данных процессор Pentium поддерживает внутренние пути шириной 128 и 256 битов, внешняя шина данных увеличена до 64 битов. Добавлены средства для построения многопроцессорных систем, в частностирасширен- ный программируемый контроллер прерываний (Advanced Programmable Interrupt Controller, APIC), дополнительные выводы и специальный режим дуальной обработки (dual processing), ориентированный на построение двухпроцессорных систем. Начиная с модели процессоров Pentium с тактовой частотой 133 МГц (1997 г.) в них был введен блок ММХ-команд (ММХ означает MultiMedia extensions). Реализованный на базе сопроцессора, данный блок поддерживал SIMD-технологию, которая предполагает обработку блока однородных данных одной машинной инструкцией. Цель введения данного расширения — увеличение производительности приложений, обрабатывающих массивы однородных целочисленных данных. Примеры таких приложений — программы обработки изображений, звука, архи- вирования^разархивирования данных и др. Излишне говорить, что все эти нововведения резко подняли производительность процессора Pentium по сравнению с его предшественником — процессором i486 — и не оставили последнему особых альтернатив для существования.
На сегодняшний день имеется три поколения, или семейства, процессоров Pentium — Pentium, P6 и Pentium IV с микроархитектурой NetBurst. К семейству
Pentium относятся упоминавшиеся ранее процессоры Pentium и Pentium MMX. История семейства Р6 началась в 1995 году с появлением процессора Pentium Pro. Несмотря на схожие названия, внутренние архитектуры процессоров двух семейств были совершенно разными. Не вдаваясь в новые схемотехнические решения, реализованные в процессоре Pentium Pro, отметим его основные архитектурные свойства. Процессор поддерживал работу трех конвейеров, то есть мог обрабатывать до трех команд за один такт. Для этого использовались особые технологии обработки потока команд, которые подробнее будут рассмотрены в следующей главе. Процессор Pentium Pro использовал новую технологию работы с кэш-памятью. Наряду с уже обычным внутренним кэшем первого уровня размером 8 Кбайт в одном корпусе (но не на одной микросхеме) с процессором располагалась кэшпамять второго уровня размером 256 Кбайт, для связи с которой имелась специальная 64-разрядная шина, работающая на частоте процессора. Шина данных процессора Pentium Pro имела разрядность 36 бита, что позволяло при определенных условиях организовать адресацию памяти до 64 Гбайт.
Процессор Pentium II, появившийся на свет в 1997 г., добавил к архитектуре процессора Pentium Pro поддержку ММХ-команд. Кроме того, были увеличены размеры кэш-памяти всех уровней — кэш-память команд и данных первого уровня выросла до 16 Кбайт каждая, кэш-память второго уровня могла иметь величину 256,512Кбайт или 1 Гбайт. Кэш-память второго уровня могла работать на половине частоты работы процессора. Также процессор поддерживал множество технологий энергосбережения. Следующие две модели процессоров, выпущенные в 1998 г., — Celeron и Pentium II Xeon — были, соответственно, более «легкой» и более «тяжелой» модификациями процессора Pentium П. Celeron позиционировался как процессор для построения компьютерных систем массового использования. Pentium II Xeon предназначался для построения высокопроизводительных серверных систем.
Последний процессор семейства Р6 — Pentium III, — выпущен в 2000 г. Его основное отличие — поддержка дополнительного набора ММХ-команд, называемых SSE-расширением (SSE — Streaming SIMD Extensions) основного набора команд процессора. Для этого в архитектуру процессора был введен специальный блок.
На сегодняшний день последним 32-разрядным процессором является Pentium IV. Он позиционируется как процессор нового поколения с новым типом микроархитектуры, носящей название NetBurst. Подробнее основные архитектурные особенности процессора Pentium IV будут рассмотрены в следующей главе при обсуждении микроархитектуры NetBurst. Отметим лишь один важный в контексте нашего обсуждения момент — с появлением процессора Pentium IV система команд процессоров Intel пополнилась еще 144 новыми командами. В основном это команды для блока ММХ с плавающей точкой, а также команды управления кэшированием и памятью. Условное название этой группы команд — SSE2 (Streaming SIMD Extensions 2).
Итоги
經 Несмотря на значительные внешние различия структурно большинство современных компьютеров устроены примерно одинаково. В их состав обязательно входят центральный процессор, внешняя и оперативная память, устройства ввода-вывода и отображения информации.
Итоги25
趣 Работать компьютер заставляет некий «серый кардинал» — машинный язык. Пользователь может даже и не подозревать о его существовании. Общаться с компьютером пользователю помогают операционные системы, офисные пакеты, системы программирования и т. д. Современные технологии программирования позволяют создавать программы, не написав ни строчки кода. Но в «мозг» компьютера команды все же поступают на машинном языке.
終Машинный язык полностью отражает все архитектурные тонкости конкретного типа компьютеров. Следствием этого является его индивидуальность для каждого семейства ЭВМ. Чтобы эффективно использовать все возможности компьютера, применяют символический аналог машинного языка — язык ассемблера.
艱 Работать на компьютере можно и без знания ассемблера. Но элементом подготовки программиста-профессионала обязательно является изучение этого языка. Почему? Изучая ассемблер, человек обязательно попутно знакомится с архитектурой компьютера. А это, в свою очередь, позволяет ему в дальнейшем создавать более эффективные программы на других языках и объединять их при необходимости с программами на ассемблере.
纖 Процессоры Intel являются стандартом де-факто в своем сегменте рынка. За тридцать с лишним лет своего развития процессоры Intel из примитивных устройств, пригодных лишь для калькуляторов, превратились в сложные системы, содержащие более 7 000 000 транзисторов и поддерживающие работу высокопроизводительных приложений и многопроцессорных конфигураций.
Глава 2
Программно-аппаратная архитектура IA-32 процессоров Intel
Общее понятие об архитектуре ЭВМ
Архитектура и свойства машины фон Неймана
Общие и индивидуальные свойства процессоров Intel
Архитектура IA-32 процессоров Intel
Варианты микроархитектуры Р6 (Pentium Pro/I l/lll)
и NetBurst (Pentium IV)
Программная модель архитектуры IA-32
Режимы работы процессора архитектуры IA-32
Набор регистров процессора архитектуры IA-32
^ Организация памяти компьютера архитектуры IA-32
Формирование физического адреса в реальном и защищенном режимах
В главе 1 мы описали компьютер на «житейском» уровне. Его сердце — процессор. Без него компьютер представляет собой упорядоченный, но безжизненный набор . аппаратных компонентов. Кроме различных схемотехнических решений, процессор живет благодаря машинному языку, «зашитому» в его устройстве микропрограммного управления. Именно машинный язык определяет логику работы процессора. В свою очередь, характеристики машинного языка полностью определяются особенностями того типа процессора, для которого этот язык предназначен.
При выборе конкретного типа (или модели) компьютера неизбежно возникают вопросы: как оценить его возможности, каковы его отличия от компьютеров других типов (моделей)? Рассмотрения одной лишь его функциональной схемы явно недостаточно, так как она вряд ли чем-то принципиально отличается от схем других машин. У всех компьютеров есть оперативная память, процессор, внешние устройства. В данном случае в содержании рекламы стоит обратить внимание на то, какие отличительные характеристики компьютера как товара продавец в первую очередь пытается довести до сведения потенциального покупателя. Обычно первые слова подобной рекламы: «Компьютер на базе процессора...». То есть все дело в процессоре. Именно он задает те правила, по которым в конечном итоге все устройства компьютера функционируют как единый механизм.
Для всего, что характеризует компьютер с точки зрения его функциональных программно-управляемых свойств, существует специальный широко распространенный термин — архитектура ЭВМ. Следует отметить, что большинство составляющих этот термин понятий относятся именно к процессору.
Архитектура ЭВМ
Однозначно определить понятие архитектуры ЭВМ довольно трудно, потому что при желании в него можно включить все, что связано с компьютерами вообще и какой-то конкретной моделью компьютера в частности. Попытаемся все же его формализовать.
Архитектура ЭВМ — это абстрактное представление ЭВМ, которое отражает ее структурную, схемотехническую и логическую организацию. Понятие архитектуры ЭВМ является комплексным, в него входят:
縱 структурная схема ЭВМ;
験 средства и способы доступа к элементам структурной схемы ЭВМ;
發 организация и разрядность интерфейсов ЭВМ;
礬 набор и доступность регистров;
粽 организация и способы адресации памяти;
提" способы представления и форматы данных ЭВМ;
^ набор машинных команд ЭВМ;
様 форматы машинных команд;
驟 правила обработки нештатных ситуаций (прерываний).
Таким образом, описание архитектуры включает в себя практически всю необходимую для программиста информацию о компьютере. Понятие архитектуры ЭВМ — иерархическое. Допустимо вести речь как об архитектуре компьютера в цеЛОМ, так и об архитектуре отдельных его составляющих, например, архитектуре процессора или архитектуре подсистемы ввода-вывода.
Все современные компьютеры обладают некоторыми общими и индивидуальными архитектурными свойствами. Индивидуальные свойства присущи только конкретной модели компьютера и отличают ее от своих больших и малых собратьев. Общие архитектурные свойства, наоборот, присущи некоторой, часто доволь-
но большой группе компьютеров. На сегодняшний день общие архитектурные свойства большинства современных компьютеров подпадают под понятие фон-ней- мановской архитектуры. Так названа архитектура по имени ученого фон Неймана. Когда фон Нейман начал заниматься компьютерами, программирование последних осуществлялось способом коммутирования. В первых ЭВМ для генерации нужных сигналов необходимо было с помощью переключателей выполнить ручное программирование всех логических схем. В первых машинах использовали десятичную логику, при которой каждый разряд представлялся десятичной цифрой и моделировался 10 электронными лампами. В зависимости от нужной цифры одна лампа включалась, остальные девять оставались выключенными. Фон Нейман предложил схему ЭВМ с программой в памяти и двоичной логикой вместо десятичной. Логически машину фон Неймана составляли пять блоков (рис. 2.1): оперативная память, арифметико-логическое устройство (АЛУ) с аккумулятором, блок управления, устройства ввода и вывода. Особо следует выделить роль аккумулятора. Физически он представляет собой регистр АЛУ. Для процессоров Intel, в которых большинство команд — двухоперандные, его роль не столь очевидна. Но существовали и существуют процессорные среды с однооперандными машинными командами. В них наличие аккумулятора играет ключевую роль, так как большинство команд используют его содержимое в качестве либо второго, либо единственного операнда команды.
Рис. 2.1. Схема машины фон Неймана
Ниже описаны свойства и принципы работы машины фон Неймана.
臓 Линейное пространство памяти. Для оперативного хранения информации компьютер имеет совокупность ячеек с последовательной нумерацией (адресами) О, 1, 2,… Данная совокупность ячеек называется оперативной памятью.
•遂Принцип хранимой программы. Согласно этому принципу, код программы и ее данные находятся в одном и том же адресном пространстве оперативной памяти.
犠Принцип микропрограммирования. Суть этого принципа заключается в том, что машинный язык еще не является той конечной субстанцией, которая физически
приводит в действие процессы в машине. В состав процессора (см. главу 1) входит устройство микропрограммного управления^ поддерживающее набор действий-сигналов, которые нужно сгенерировать для физического выполнения каждой машинной команды.
•緻 Последовательное выполнение программ. Процессор выбирает из памяти команды строго последовательно. Для изменения прямолинейного хода выполнения программы или осуществления ветвления необходимо использовать специальные команды. Они называются командами условного и безусловного переходов.
後 Отсутствие разницы между данными и командами в памяти. С точки зрения процессора, нет принципиальной разницы между данными и командами. Данные и машинные команды находятся в одном пространстве памяти в виде последовательности нулей и единиц. Это свойство связано с предыдущим. Процессор, поочередно обрабатывая некоторые ячейки памяти, всегда пытается трактовать содержимое ячеек как коды машинных команд, а если это не так, то происходит аварийное завершение программы. Поэтому важно всегда четко разделять в программе пространства данных и команд.
о Безразличие к назначению данных. Машине все равно, какую логическую нагрузку несут обрабатываемые ею данные.
Данный учебник посвящен вопросам программирования процессоров фирмы Intel и Intel-совместимых процессоров других фирм. Поэтому в качестве примера индивидуальных архитектурных принципов компьютера, в силу иерархичности этого понятия, рассмотрим индивидуальные архитектурные принципы и свойства процессоров Intel. Их полное рассмотрение не является нашей целью, уделим внимание лишь тем их них, которые наиболее характерны и понадобятся нам для дальнейшего изложения. Более подробно вопросы организации архитектуры компьютера описаны в [7].
Согласно материалам фирмы Intel, индивидуальные архитектурные свойства и принципы работы всех ее процессоров, начиная с i8086 и заканчивая Pentium IV, выстроены в рамках единой архитектуры, которая позже получила название IA-32 (32-bit Intel Architecture). Эта архитектура не является «закостенелым» образованием. В процессе эволюции процессоров Intel она постоянно изменяется и развивается. Каждый из процессоров вносил в IA-32 одно или несколько архитектурных новшеств. Несмотря на то, что датой рождения архитектуры IA-32 нужно считать дату появления на свет процессора i80386, предыдущие модели процессоров также внесли существенный вклад в представление ее принципов и свойств. Если вернуться к материалу главы 1, то можно проследить за вкладом, который внес каждый из процессоров Intel в ходе своей эволюции в формирование элементов архитектуры IA-32. Так, благодаря процессорам i8086/88 в IA-32 существует сегментация памяти, i80286 ввел защищенный режим и т. д.
Архитектура IA-32
В рамках архитектуры IA-32 существует и развивается ряд микроархитектур. Ранее было показано, что понятие архитектуры компьютера иерархично и что существуют общие и индивидуальные свойства архитектуры. В контексте обсуждения
архитектуры процессоров Intel имеет смысл также позиционировать различные их архитектурные свойства и принципы как общие и индивидуальные. К индивидуальным архитектурным свойствам и принципам можно отнести существующие в рамках различных микроархитектур (см. далее). Что касается общих архитектурных свойств и принципов IA-32, то к ним относятся те, которые имеют место для всех процессоров Intel или, по крайней мере, существуют вне рамок конкретной микроархитектуры для большого числа моделей процессоров. Так как процессор в основном определяет логику работы компьютера, то и названия большинства общих архитектурных свойств и принципов IA-32 совпадают с названиями аналогичных свойств и принципов компьютера: номенклатура программно-доступных регистров; организация и способы адресации памяти; номенклатура режимов работы процессоров; организация и разрядность внешних интерфейсов ЭВМ; способы представления и форматы данных; набор и форматы машинных команд ЭВМ; порядок обработки прерываний. Практически все эти общие архитектурные свойства и принципы составляют программную модель процессора, которая будет рассмотрена в дальнейшем.
Варианты микроархитектуры процессоров Intel
Понятие микроархитектуры впервые было определено Intel для процессоров семейства Pentium Pro. Его введение объяснялось необходимостью правильного позиционирования новых процессоров среди существующих. Внешняя программная модель (логическая) 32-разрядных процессоров изменялась только в сторону развития, в то время как их исполнительная (физическая) часть могла быть совершено разной. Понятие микроархитектуры ориентировано на описание особенностей исполнительной части процессоров, то есть того, какими способами и какими средствами процессор выполняет обработку машинного кода (рис. 2.2). На сегодняшний день в рамках IA-32 существует две микроархитектуры процессоров Intel: P6 и NetBurst.
Рис. 2.2. Представление компьютера в виде уровней
Микроархитектура Р6
Микроархитектуру Р6 поддерживают такие процессоры Intel, как Pentium Pro, Pentium II (Xeon), Celeron, Pentium III (Xeon). Эта микроархитектура является, по определению Intel, трехходовой (three-way) суперскалярной конвейерной архи-
тектурой. Термин «трехходовая» означает поддержку технологий параллельного вычисления, позволяющих процессору одновременно (за один такт) обрабатывать до трех инструкций. Проблема оптимальной обработки потока машинных команд является ключевой при разработке любого процессора. Поэтому для большей ясности необходимо показать эту проблему в развитии. В компьютере фон-нейма- новской архитектуры существуют две основные стадии исполнения команды — выборка очередной команды из памяти и собственно ее исполнение. В первых процессорах Intel все блоки процессора работали последовательно, начиная с этапа выборки очередной команды из памяти и заканчивая этапом завершения ее обработки процессором. Напоминание об этом осталось в названии регистра IP/EIP — (Instruction Pointer — указатель инструкции). До появления процессоров Intel с конвейерной архитектурой данный регистр непосредственно указывал на очередную команду, подлежащую выполнению. Процессоры Intel относятся к группе CISC-процессоров, в которых для выполнения одной команды может требоваться от единиц до нескольких десятков процессорных тактов. При такой обработке команд увеличение производительности может быть достигнуто только повышением частоты генерации машинных тактов. Простое увеличение частоты работы процессора не имеет смысла, так как есть физически обусловленная верхняя граница, до которой ее можно поднимать. По этому пути разработчики Intel шли до процессора i80386 включительно. В ходе исполнения команды есть и другое узкое место — выборка команды из памяти. Это затратная по времени операция. Частичное решение проблемы было найдено еще на заре развития компьютерной техники в виде буфера упреждающей выборки. Развитием этой и реализацией других идей стал конвейер — специальное устройство, существующее на уровне архитектуры исполнительной части компьютера. Благодаря конвейеру исполнение команды разбивается на несколько стадий, каждая из которых реализует некоторую элементарную операцию общего процесса обработки команды. Впервые для процессоров Intel конвейер был реализован в архитектуре процессора i80486. Конвейер i80486 имеет пять ступеней, которые соответствуют перечисленным далее стадиям обработки машинной команды.
Выборка команды из кэш-памяти или из оперативной памяти.
Декодирование команды.
Генерация адреса, в ходе которой определяются адреса операндов в памяти и выполняется выборка операндов.
Выполнение операции с помощью АЛУ.
Запись результата (место записи результата зависит от алгоритма работы конкретной машинной команды).
В чем преимущество такого подхода? Очередная команда после ее выборки попадает в блок декодирования. Таким образом блок выборки освобождается и может выбрать следующую команду. В результате на конвейере могут находиться в различной стадии выполнения пять команд. Скорость вычисления в результате существенно возрастает.
В процессорах Pentium конвейерная архитектура была усовершенствована и получила название суперскалярной. В отличие от скалярной архитектуры i80486
(с одним конвейером), первые модели процессоров Pentium имели два конвейера. В идеале такой суперскалярный процессор должен выполнять две команды за машинный такт. Но не все так просто. Реально два конвейера Pentium не были функционально равнозначными. В связи с этим они даже имели разные названия — u-конвейер (главный) и v-конвейер (второстепенный). Главный конвейер был полнофункциональным и мог выполнять любые машинные команды. Функциональность второстепенного конвейера была ограничена основными целочисленными командами и одной командой с плавающей точкой (FXCH). Внутренняя структура обоих конвейеров такая же, как у i80486 с одним общим блоком выборки команд. Для того чтобы два разных по функциональным возможностям конвейера могли обеспечить предельную эффективность (две выполненных команды за такт работы процессора), необходимо было группировать команды из входного потока в совместимые пары. Важно заметить, что исходная последовательность команд входного потока была неизменной. Если процессору не удавалось собрать совместимую пару, то выполнялась одна команда на u-конвейере. Оставшуюся команду процессор пытался «спарить» со следующей командой входного потока.
Вернемся к процессорам микроархитектуры Р6. Они имеют другую структуру конвейера. Собственно конвейера в понимании i80486 и первых Pentium уже нет. Конвейеризация заключается в том, что весь процесс обработки команд разбит на 12 стадий, которые исполняются различными блоками процессора. Сколько именно команд обрабатывается процессором, сказать трудно. Термин трехходовой означает лишь то, что для исполнения из входного потока выбираются до трех команд. Известен верхний предел — в процессоре в каждый момент времени могут находиться до 30 команд в различной стадии исполнения. Детали этого процесса скрыты за понятием динамическое исполнение с нарушением исходного порядка следования машинных команд (out of order), что означает исполнение команд в порядке, определяемом исполнительным устройством процессора, а не исходной последовательностью команд. В основу технологии динамического исполнения положены три концепции:
• Предсказание правильного адреса перехода. Основная задача механизма предсказания — исключить перезагрузку конвейера. Под переходом понимается запланированное алгоритмом изменение последовательного характера выполнения программы. Как показывает статистика, типичная программа на каждые 6-8 команд содержит 1 команду перехода. Последствия обработки перехода предсказать несложно: при наличии конвейера через каждые 6-8 команд его нужно очищать и заполнять заново в соответствии с адресом перехода. Все преимущества конвейеризации теряются. Поэтому в архитектуру Pentium в состав устройства выборки/декодирования (см. главу 1) был введен блок предсказания переходов. Вероятность правильного предсказания составляет около 80 %.
後 Динамический анализ потока данных. Анализ проводится с целью определения зависимостей команд программы от данных и регистров процессора с последующей оптимизацией выполнения потока команд. Главный критерий здесь — максимально полная загрузка процессора. Для реализации данного критерия допускается нарушение исходного порядка следования команд. Сбоя при этом не7 происходит, так как внешне логика работы программы не нарушается. 39999-
Подобная внутренняя неупорядоченность исполнения команд позволяет держать процессор загруженным даже тогда, когда данные в кэш-памяти второго уровня отсутствуют и необходимо тратить время на обращение за ними в оперативную память.
Спекулятивное исполнение — способность процессора исполнять машинные команды на участках программы с условными переходами и циклами до того, как эти переходы будут разрешены алгоритмом программы. Если переход предсказан правильно, то процессор к этому моменту уже имеет исполненный код, в противном случае весь конвейер нужно очищать, загружать и исполнять код новой ветви программы, что очень накладно.
Рассмотрим порядок функционирования исполнительного устройства микроархитектуры Рб и реализацию с его помощью описанных ранее технологий. Это рассмотрение не является строгим, кое-где для лучшего понимания оно упрощено. Для иллюстрации будем использовать схему, представленную на рис. 2.3 и являющуюся развитием схемы на рис. 1.2 (см. главу 1). Из схемы видно, что структурно процессор микроархитектуры Р6 состоит из нескольких подсистем.
經 Подсистема памяти состоитиз системнойшины, кэша второго уровня L2, устройства шинного'интерфейса, кэша первого уровня L1 (инструкций и данных), устройства связи с памятью и буфера переупорядочивания запросов к памяти.
綫 Устройство выборки/декодированиясостоит из устройства выборки команд, блока предсказания переходов, в который входят блоки меток перехода и вычисления адреса следующей инструкции, устройства декодирования, устройства микропрограммного управления и таблицы псевдонимов регистров.
'•'• Буфер команд.
演 Устройство диспетчеризации/исполнения содержит буфер микроопераций, готовых к исполнению, и пять исполнительных устройств (два — для исполнения целочисленных операций, два — для исполнения операций с плавающей точкой, а также устройство связи с памятью). Необходимо заметить, что здесь допущена вольность в трактовке назначения исполнительных устройств: выделены устройства для выполнения обычных команд (целочисленных и с плавающей точкой) и MMX-команд (также целочисленных и с плавающей точкой). Реальное деление несколько иное. Такое допущение сделано исключительно с учебной целью — для более осознанного перехода от архитектуры к системе команд ассемблера.
电協 Блок удаления и восстановления.
Рассмотрим подсистему памяти. Для бесперебойной работы процессора в микроархитектуре Рб используется два уровня кэш-памяти1. Кэш-память первого уровня состоит из кэшей команд и данных размером по 8 Кбайт, расположенных внутри процессора в непосредственной близости к его исполнительной части. Кэш-память второго уровня является внешней по отношению к процессору (но в едином кон-
Кэширование — способ увеличения быстродействия системы за счет хранения часто используемых данных и кодов в так называемой кэш-памяти, находящейся внутри процессора (кэш-память первого уровня) либо в непосредственной близости от него (кэш-память второго уровня).
2 Зак. 256
Центральный процессор (Pentium Рго/ll/lll)
Системная шина
Рис. 2.3. Структурная схема процессора семейства Р6 (Pentium Pro/11/lll)
структиве с ним), имеет значительно б ольший размер (256 Кбайт, 512 Кбайт или 1 Мбайт) и соединена с ядром процессора посредством 64-разрядной шины. Разделение кэш-памяти на две части (для кода и данных) обеспечивает бесперебойную поставку машинных инструкцийи элементов данныхв исполнительное устройство процессора. Исходные данные для кэш-памяти первого уровня предоставляет кэш-память второго уровня. Заметьте, что информация из нее поступает на устройство шинного интерфейса и далее в соответствующую кэш-память первого уровня по 64-разрядной шине. При этом благодаря более быстрому обновлению содержимого кэш-памяти первогоуровня обеспечивается высокий темп работы процессора.
Устройствошинногоинтерфейсаобращаетсякоперативнойпамятисистемы через внешнюю системную шину. Эта 64-разрядная шина ориентирована на обработку запросов, то есть каждый шинный запрос обрабатывается отдельно и требу-
ет обратной реакции. Пока устройство шинного интерфейса ожидает ответа на один запрос шины, возможно формирование многочисленных дополнительных запросов. Все они обслуживаются в порядке поступления. Считываемые по запросу данные помещаются в кэш второго уровня. То есть процессор посредством устройства шинного интерфейса читает команды и данные из кэша второго уровня. Устройство шинного интерфейса взаимодействует с кэшем второго уровня через 64-разряд- ную шину кэша, которая также ориентирована на обработку запросов и функционирует на тактовой частоте процессора. Доступ к кэшу первого уровня осуществляется через внутренние шины на тактовой частоте процессора. Синхронная работа с системной памятью кэш-памяти обоих уровней осуществляется благодаря специальному протоколу MESI [7], [13].
Запросы от команд на получение операндов из памяти в исполнительном устройстве процессора обслуживаются посредством устройства связи с памятью и буфера переупорядочивания запросов к памяти. Эти два устройства специально включены в схему для того, чтобы обеспечить бесперебойное снабжение исполняемых команд необходимыми данными. Особо стоит подчеркнуть роль буфера переупо- рядочивания запросов к памяти. Он отслеживает все запросы к операндам в памяти и выполняет функции планирующего устройства. Если нужные для очередной операции данные в кэш-памяти первого уровня (L1) отсутствуют, то буфер пере- упорядочивания запросов к памяти автоматически передает информацию о неудачном обращении к данным кэшу второго уровня (L2). Если и в кэше L2 нужных данных не оказалось, то буфер переупорядочивания запросов к памяти заставляет устройство шинного интерфейса сформировать запрос к оперативной памяти компьютера.
Устройство выборки/декодирования извлекает одну 32-байтную строку кэша команд (L1) за такт и передает в декодер, который преобразует ее в последовательность микроопераций. Поток микроопераций (пока он еще соответствует последовательности исходных команд) поступает в буфер команд. Устройство выборки команд вычисляет указатель на следующую команду, подлежащую выборке, на основании информации трех источников: буфера меток перехода, состояния пре- рывания/исключения и сообщения от исполнительного целочисленного устройства об ошибке в предсказании метки перехода. Важная часть этого процесса — предсказание метки перехода, которое выполняется по специальному алгоритму. В его основе лежит работа с буфером меток перехода, который содержит информацию о последних 256 переходах. Если очередная команда, выбираемая из памяти, является командой перехода, то содержащийся в ней адрес перехода сравнивается с адресами, уже находящимися в буфере меток перехода. Если этот адрес уже есть в буфере меток переходов, то он станет адресом следующей команды, с которой устройство выборки будет извлекать очередную команду. Если искомого адреса перехода в буфере нет, то выборка команд из памяти будет продолжена до момента исполнения команды перехода исполнительным устройством. В результате ее исполнения становится ясно, было ли правильным решение об адресе начала выборки следующих команд после выборки команды перехода. Если предсказанный переход оказывается неверным, то конвейер сбрасывается и загружается заново в соответствии с адресом перехода. Цель предсказания переходов — в том, чтобы
устройство исполнения постоянно было занято полезной работой и сброс конвейера производился как можно реже.
Устройство выборки команд выбирает команды для исполнения и помещает их в устройство декодирования. Устройство декодирования состоит из трех параллельно работающих декодеров (два простых и один сложный). Именно эти декодеры воплощают в жизнь понятие исполнения с нарушением исходного порядка следования команд (out of order) и являются теми самыми тремя входами (three way) в исполнительное устройство процессора. Декодеры преобразуют команды процессора в микрооперации. Микрооперации представляют собой примитивные команды, которые выполняются пятью исполнительными устройствами процессора, работающими параллельно. Многие машинные команды преобразуются в одиночные микрооперации (это делает простой декодер), а некоторые машинные команды — в последовательность от двух и более (оптимально — четырех) микроопераций (это делает сложныйдекодер). Информация о последовательности микроопераций для реализации конкретной машинной команды содержится В устройстве микропрограммного управления. Кроме команд, декодеры обрабатывают также префиксы команд. Декодер команд может формировать до шести микроопераций за такт — по одной от простых декодеров и до четырех от сложного декодера. Для достижения наибольшей производительности работы декодеров необходимо, чтобы на их вход поступали команды, которые декодируются шестью микрооперациями в последовательности 4 + 1 + 1. Если время работы программы критично, то имеет смысл провести ее оптимизацию, заключающуюся в переупорядочивании исходного набора команд таким образом, чтобы группы команд формировали последовательности микроопераций по схеме 4 + 1 + 1. После того как команды разбиты на микрооперации, порядок их выполнения трудно предсказать. При этом могут возникнуть проблемы с таким критичным ресурсом, как регистры. Суть здесь в том, что если в двух соседних фрагментах программы данные помещались в одинаковые регистры, откуда они, возможно, записывались в некоторые области памяти, а после переупорядочивания эти фрагменты перемешались, то как разобраться в том, какие регистры и где использовались. Эта проблема носит название проблемы ложных взаимозависимостей и решается с помощью механизма переименования регистров. Основу этого механизма составляет набор из 40 внутренних универсальных регистров, которые и задействуются в реальных вычислениях исполнительным устройством абсолютно прозрачно для программ. Универсальные регистры могут работать как с целыми числами, так и со значениями с плавающей точкой. Информация о действительных именах регистров процессора и их внутренних именах (номерах универсальных регистров) помещается в таблицу псевдонимов регистров.
В заключение процесса декодирования устройство управления таблицей псевдонимов регистров добавляет к микрооперациям биты состояния и флаги, чтобы подготовить их к неупорядоченному выполнению, после чего посылает получившиеся микрооперации в буфер переупорядоченных команд. Нужно заметить, что новый порядок их следования не соответствует порядку следования соответствующих команд в исходной программе. Буфер переупорядоченных команд представляет собой массив ассоциативной памяти, физически выполненный в виде 40 регистров и представляющий собой кольцевую структуру, элементы которой содержат
два типа микроопераций: ожидающие своей очереди на исполнение и уже частично выполненные, но не до конца из-за их переупорядочивания и зависимости от других частично или полностью не выполненных микроопераций. Устройство дис- петчеризации/исполнения может выбирать микрооперации из этого буфера в любом порядке.
Устройство диспетчеризации/исполнения планирует и исполняет неупорядоченную последовательность микроопераций из буфера переупорядоченных команд. Но оно не занимается непосредственной выборкой микроопераций из буфера переупорядоченных команд, так как в нем могут содержаться и не готовые к исполнению микрооперации. Этим занимается устройство, управляющее специальным буфером, который условно назовем буфером микроопераций, готовых к исполнению. Оно постоянно сканирует буфер переупорядоченных команд в поисках микроопераций, готовых к исполнению (фактически это означает доступность всех операндов), после чего посылает их соответствующим исполнительным устройствам, если они не заняты. Результаты исполнения микроопераций возвращаются в буфер переупорядоченных команд и сохраняются там наряду с другими микрооперациями до тех пор, пока не будут удалены устройством удаления и восстановления.
Подобная схема планирования и исполнения программ реализует классический принцип неупорядоченного выполнения, при котором микрооперации посылаются исполнительным устройствам вне зависимости от их расположения в исходном алгоритме. В случае, если к выполнению одновременно готовы две или более микрооперации одного типа (например, целочисленные), то они выполняются в соответствии с принципом FIFO (First In, First Out — первым пришел, первым ушел), то есть в порядке поступления в буфер переупорядоченных команд.
Непосредственно исполнительное устройство состоит из пяти блоков, каждый из которых обрабатывает свой тип микроопераций: два целочисленных устройства, два устройства для вычислений с плавающей точкой и одно устройство связи с памятью. Таким образом, за один машинный такт одновременно исполняется пять микроопераций.
Два целочисленных исполнительных устройства могут параллельно обрабатывать две целочисленные микрооперации. Одно из этих целочисленных исполнительных устройств специально предназначено для работы с микрооперациями переходов. Оно способно обнаружить непредсказанный переход и сообщить об этом устройству выборки команд, чтобы перезапустить конвейер. Такая операция реализована следующим образом. Декодер команд отмечает каждую микрооперацию перехода и адрес перехода. Когда целочисленное исполнительное устройство выполняет микрооперацию перехода, то оно определяет, был предсказан переход или нет. Если переход предсказан правильно, то микрооперация отмечается пригодной для использования, и выполнение продолжается по предсказанной ветви. Если переход предсказан неправильно, то целочисленное исполнительное устройство изменяет состояние всех последующих микроопераций с тем, чтобы удалить их из буфера переупорядоченных команд. После этого целочисленное устройство помещает метку перехода в буфер меток перехода, который, в свою очередь, совместно с устройством выборки команд перезапускает конвейер относительно нового исполнительного адреса.
Устройство связи с памятью управляет загрузкой и сохранением данных для микроопераций. Для их загрузки в исполнительное устройство достаточно определить только адрес памяти, поэтому такое действие кодируется одной микрооперацией. Для сохранения данных необходимо определять и адрес, и записываемые данные, поэтому это действие кодируется двумя микрооперациями. Та часть устройства связи с памятью, которая управляет сохранением данных, имеет два блока, позволяющие ему обрабатывать адрес и данные для микрооперации параллельно. Это позволяет устройству связи с памятью выполнить загрузку и сохранение данных для микроопераций параллельно в одном такте.
Исполнительные устройства с плавающей точкой аналогичны устройствам в более ранних моделях процессора Pentium. Было добавлено только несколько новых команд с плавающей точкой для организации условных переходов и перемещений.
Последний блок в этой схеме выполнения команд исходной программы — блок удаления и восстановления, задачей которого является возврат вычислительного процесса в рамки, определенные исходной последовательностью команд. Для этого он постоянно сканирует буфер переупорядоченных команд на предмет обнаружения полностью выполненных микроопераций, не имеющих связи с другими микрооперациями. Такие микрооперации удаляются из буфера переупорядоченных команд и восстанавливаются в порядке, соответствующем порядку следования команд исходной программы с учетом прерываний, исключений, точек прерывания и переходов. Блок удаления и восстановления может удалить три микрооперации за один машинный такт. При восстановлении порядка следования команд блок удаления и восстановления записывает результаты в реальные регистры процессора и в оперативную память.
Таким образом, система динамического исполнения команд позволяет организовать прохождение команд программы через исполнительное устройство процессора эффективнее, чем это было в конвейере процессора i80486 и первых процессоров Pentium.
Микроархитектура NetBurst
Микроархитектура NetBurst, реализованная в процессоре Pentium IV, является развитием идей микроархитектуры Р6, поэтому рассматривается довольно конспективно. Судя по названию (net — сеть, burst — прорыв), микроархитектура NetBurst призвана обеспечить некий «сетевой прорыв». Очевидно, что этим разработчики хотели подчеркнуть те новые особенности процессора Pentium IV, которые позволяют организовать более быструю и эффективную работу приложений в современных сетевых и мультимедийных информационных средах. Отметим наиболее важные свойства новой микроархитектуры.
я Быстрая исполнительная часть процессора. АЛУ процессора работает на удвоенной частоте процессора. За каждый такт процессора выполняются две основные целочисленные команды. Обеспечена более высокая пропускная способность потока команд через исполнительную часть процессора и уменьшены различные задержки.
蠶 Гиперконвейерная технология. Гиперконвейер Pentium IV состоит из 20 ступеней. Цель увеличения длины конвейера — упрощение задач, реализуемых каж-
Архитектура IA-3239
дой из его ступеней, и, как следствие, упрощение соответствующей аппаратной логики.
Улучшенная технология динамического исполнения благодаря более глубокой «произвольности^ в порядке исполнения кода и усовершенствованной системе предсказания переходов. Размер буфера меток перехода увеличен до 4 Кбайт (Pentium III — 512 байт). Усовершенствован и сам алгоритм предсказания. В результате вероятность предсказания перехода возрастает до 95 %.
Новая подсистема кэширования. Отсутствует кэш команд первого уровня. Вместо него введен кэш трасс. Трассаминазываются последовательности микроопераций, в которые были декодированы ранее выбранные команды. Кэш трасс способен хранить до 12 Кбайт микроопераций и доставлять исполнительному ядру до 3 микроопераций за такт. Кэш второго уровня работает на полной частоте ядра процессора.
Структурная схема процессора Pentium IV показана на рис. 2.4.
Рис. 2.4. Структурная схема процессора Pentium IV
Микроархитектура NetBurst поддерживает еще одну новую технологию — Ну- perThreading. Данная технология позволяет на базе одного физического процессора Pentium IV моделировать несколько логических, каждый из которых имеет собственное архитектурное пространство IA-32. Под архитектурным пространством
IA-32 понимается совокупность регистров данных, сегментных регистров, системных регистров и регистров MSR. Каждый логический процессор имеет также собственный контроллер прерываний API С.
На этом, наверное, следует завершить обсуждение общих вопросов, связанных с архитектурой процессоров Intel. Более подробную информацию по этой теме, в том числе о логике функционирования и характеристиках микроархитектур Р6 и NetBurst, можно получить из материала книги [13]. Наша ближайшая задача — разобраться, как управлять этими сложными процессорами. Программисту совершенно необязательно помнить о схемотехнических тонкостях реализации процессора, ему достаточно знать, какие части процессора ему доступны и как с ними взаимодействовать для реализации некоторой задачи. Первый шаг в этом направлении — знакомство с программной моделью процессора. Эта модель описывает видимые для программиста объекты архитектуры процессора, знание которых позволяет программисту эффективно и в полном объеме использовать возможности процессора.
Программная модель IA-32
Любая выполняющаяся программа получает в свое распоряжение определенный набор ресурсов процессора. Эти ресурсы необходимы для обработки и хранения в памяти команд и даиных программы, а также информации о текущем состоянии программы и процессора. Программную модель процессора в архитектуре IA-32 процессоров Intel составляет следующий набор ресурсов (рис. 2.5):
' пространство адресуемой памяти до 232 - 1 байт (4 Гбайт), для Pentium III/IV — до 236 - 1 байт (64 Гбайт);
許 набор регистров для хранения данных общего назначения;
набор сегментных регистров;
迷 набор регистров состояния и управления;
芸、 набор регистров устройства вычислений с плавающей точкой (сопроцессора);
набор регистров целочисленного MMX-расширения, отображенных на регистры сопроцессора (впервые появились в архитектуре процессора Pentium MMX);
, набор регистров MMX-расширения с плавающей точкой (впервые появились в архитектуре процессора Pentium III);
9 программный стек — специальная информационная структура, работа с которой предусмотрена на уровне машинных команд (более подробно она будет обсуждена позже).
Это основной набор ресурсов. Кроме того, к ресурсам, поддерживаемым архитектурой IA-32, необходимо отнести порты ввода-вывода, счетчики мониторинга производительности.
Программные модели более ранних процессоров (i486, первые Pentium) отличаются меньшим размером адресуемого пространства оперативной памяти (232 - 1, так как разрядность их шины адреса составляет 32 бита) и отсутствием некоторых групп регистров. Для каждой группы регистров в скобках показано, начиная
Архитектура IA-3241
Регистры уст ройства с плавающе11 точкой (сопроцессора)Регистры уст ройства с плавающе11 точкой (сопроцессора)Регистры общего назначения целочисленного устройстваРегистры общего назначения целочисленного устройстваPentium Pro/ll/lll/IVPentium Pro/ll/lll/IV《 -ss:esp《 -ss:espАдресноепространствопамятиАдресноепространствопамятиСтекСтек0Сегментные регистры0Сегментные регистры••••Ist(0)st(1)1 |st(3)st(5)st(6)Ist(0)st(1)1 |st(3)st(5)st(6)ГIIГII..7979.•.•00f|f|i486/Pentiumi486/PentiumРегистры состояния и управленияРегистры состояния и управленияi Регистр флагов'eflags |_| flags ]i Регистр флагов'eflags |_| flags ]eip厂 31eip厂 311515Регистр-указатель командыРегистр-указатель команды00IPI150IPI150Системные регистрыСистемные регистрыI |II |ImmxOmmx21nmmmmx4mmx5mmx6•mmx7mmxOmmx21nmmmmx4mmx5mmx6•mmx7I |IIIIII0I |IIIIII0| I | || I | |||127127xmmO xmml xmm2 xmm3 xmm4xmmO xmml xmm2 xmm3 xmm4xmm5xmm6xmm7xmm5xmm6xmm7I | I | | I |I 0I | I | | I |I 0
Регистры уст ройства с плавающе11 точкой (сопроцессора)
Регистры уст ройства с плавающе11 точкой (сопроцессора)
Регистры общего назначения целочисленного устройства
Регистры общего назначения целочисленного устройства
Pentium Pro/ll/lll/IV
Pentium Pro/ll/lll/IV
《 -ss:esp
《 -ss:esp
Адресноепространствопамяти
Адресноепространствопамяти
Стек
Стек
0
Сегментные регистры
0
Сегментные регистры
••
••
Ist(0)
st(1)1 |
st(3)
st(5)
st(6)
Ist(0)
st(1)1 |
st(3)
st(5)
st(6)
Г
I
I
Г
I
I
.
.
79
79
.•
.•
0
0
f
|
f
|
i486/Pentium
i486/Pentium
Регистры состояния и управления
Регистры состояния и управления
i Регистр флагов
'eflags |_| flags ]
i Регистр флагов
'eflags |_| flags ]
eip厂 31
eip厂 31
15
15
Регистр-указатель команды
Регистр-указатель команды
0
0
IPI
150
IPI
150
Системные регистры
Системные регистры
I |
I
I |
I
mmxO
mmx2
1nmm
mmx4
mmx5
mmx6
•mmx7
mmxO
mmx2
1nmm
mmx4
mmx5
mmx6
•mmx7
I |
I
I
I
I
I
I
0
I |
I
I
I
I
I
I
0
| I | |
| I | |
|
|
127
127
xmmO xmml xmm2 xmm3 xmm4
xmmO xmml xmm2 xmm3 xmm4
xmm5
xmm6
xmm7
xmm5
xmm6
xmm7
I | I | | I |
I 0
I | I | | I |
I 0
Регистры целочисленного MMX-расширения(Pentium MMX/II/II1/IV)Регистры целочисленного MMX-расширения(Pentium MMX/II/II1/IV)Регистры MMX-расширения с плавающей точкой(Pentium III/IV)Регистры MMX-расширения с плавающей точкой(Pentium III/IV)
Регистры целочисленного MMX-расширения
(Pentium MMX/II/II1/IV)
Регистры целочисленного MMX-расширения
(Pentium MMX/II/II1/IV)
Регистры MMX-расширения с плавающей точкой(Pentium III/IV)
Регистры MMX-расширения с плавающей точкой(Pentium III/IV)
Рис. 2.5. Программная модель архитектуры IA-32 процессоров Intel
с какой модели данная группа регистров появилась в программной модели процессоров Intel. Если такого обозначения нет, то это означает, что данная группа регистров присутствовала в процессорах i386 и i486. Что касается еще более ранних процессоров i8086/88, то на самом деле они тоже полностью представлены на схеме, но составляют лишь ее небольшую ее часть. В программную модель данных процессоров входят 8- и 16-разрядные регистры общего назначения, сегментные регистры, регистры FLAGS, IP и адресное пространство памяти размером до 1 Мбайт.
Свойства некоторых перечисленных далее программно-доступных ресурсов определяются текущим режимом работы процессора.
Режимы работы процессора архитектуры IA-32
Режим работы процессора определяет поведение, номенклатуру и свойства доступных ресурсов процессора. Перевод процессора из одного режима в другой осуществляется специальными программными и аппаратными методами.
В рамках архитектуры IA-32 доступны следующие режимы работы процессора. Режимреальных адресов, или простореальный режим (real mode) — это режим, в котором работал i8086. Наличие его в i486 и Pentium обусловлено тем, что фирма Intel старается обеспечить в новых моделях процессоров возможность функционирования программ, разработанных для ранних моделей.
о Защищенный режим (protected mode) позволяет максимально реализовать все идеи, заложенные в процессорах архитектуры IA-32, начиная с i80286. Программы, разработанные для i8086 (реального режима), не могут функционировать в защищенном режиме. Одна из причин этого связана с особенностями формирования физического адреса в защищенном режиме.
И Режим виртуального процессора 8086 предназначен для организации многозадачной работы программ, разработанных для реального режима (процессора i8086), совместно с программами защищенного режима. Переход в этот режим (virtual 8086 mode) возможен, если процессор уже находится в защищенном режиме. Работа программ реального режима в режиме виртуального i8086 возможна благодаря тому, что процесс формирования физического адреса для них производится по правилам реального режима.
緻 Режим системного управления (System Management Mode, SMM) — это новый режим работы процессора, впервые появившийся в процессоре Pentium. Он обеспечивает операционную систему механизмом для выполнения машиннозависимых функций, таких как перевод компьютера в режим пониженного энергопотребления или выполнения действий по защите системы. Для перехода в данный режим процессор должен получить специальный сигнал SMI от усовершенствованного программируемого контроллера прерываний (Advanced Programmable Interrupt Controller, APIC), при этом сохраняется состояние вычислительной среды процессора. Функционирование процессора в этом режиме подобно его работе в режиме реальных адресов. Возврат из этого режима производится специальной командой процессора.
Процессор всегда начинает работу в реальном режиме.
Набор регистров
Регистрами называются области высокоскоростной памяти, расположенные внутри процессора в непосредственной близости от его исполнительного ядра. Доступ к ним осуществляется несравнимо быстрее, чем к ячейкам оперативной памяти. Соответственно, машинные команды с операндами в регистрах выполняются максимально быстро, поэтому в программах на языке ассемблера регистры используются очень интенсивно. К сожалению, архитектура IA-32 предоставляет в распоряжение программиста не слишком много регистров, поэтому они являются критически важным ресурсом и за их содержимым приходится следить очень внимательно.
Большинство регистров имеют определенное функциональное назначение. С точки зрения программиста, их можно разделить на две большие группы.
is Первую группу образуют пользовательские регистры, к которым относятся:
D регистры общего назначения EAX/AX/AH/AL, EBX/BX/BH/BL, EDX/DX/DH/DL, ЕСХ/ CX/CH/CL, ЕВР/ВР, ESI/SI, EDI/DI, ESP/SP предназначены для хранения данных и адресов, программист может их использовать (с определенными ограничениями) для реализации своих алгоритмов;
D сегментные регистры CS, DS, SS, ES, FS, GS используются для хранения адресов сегментов в памяти;
регистры сопроцессора ST(0), ST(1), ST(2), ST(3), ST(4), ST(5), ST(6), ST(7) предназначены для написания программ, использующих тип данных с плавающей точкой (глава 17);
П целочисленные регистры MMX-расширенияMMXO,MMX1, MMX2, MMX3, ММХ4, MMX5, MMX6, ММХ7;
D регистры MMX-расширения с плавающей точкойXMMO,XMMi, ХММ2, XMM3,・ XMM4,XMM5,XMM6,XMM7;
D регистры состояния и управления (регистр флагов E FLAGS/FLAGS и регистр- указатель команды EIP/IP) содержат информацию о состоянии процессора, исполняемой программы и позволяют изменить это состояние.
• Во вторую группу входят системные регистры, то есть регистры, предназначенные для поддержания различных режимов работы, сервисных функций, а также регистры, специфичные для определенной модели процессора. Перечислим системные регистры, поддерживаемые IA-32:
управляющие регистрыCR0...CR4определяют режим работы процессора и характеристики текущей исполняемой задачи;
D регистры управления памятью GDTR, IDTR, LDTR и TR используются в защищенном режиме работы процессора для локализации управляющих структур этого режима;
П отладочные регистры DR0...DR7 предназначены для мониторинга и управления различными аспектами отладки;
D регистры типов областей памяти MTRR используются для аппаратного управления кэшированием в целях назначения соответствующих свойств областям памяти;
П машинно-зависимые регистры MSR используются для управления процессором, контроля за его производительностью, получения информации об ошибках.
Почему в обозначениях многих из регистров общего назначения присутствует наклонная разделительная черта? Это не разные регистры — это части одного большого 32-разрядного регистра, но их можно использовать в программе как отдельные объекты. Зачем так сделано? Чтобы обеспечить работоспособность программ, написанных для прежних 16-разрядных моделей процессоров фирмы Intel начиная с i8086. Процессоры i486 и Pentium имеют в основном 32-разрядные регистры.
Их количество, за исключением сегментных регистров, такое же, как и у i8086, но размерность больше, что и отражено в обозначениях — они имеют приставку е (extended).
Многие из приведенных регистров предназначены для работы с определенными вычислительными подсистемами процессора: сопроцессором, MMX-расширениями. Поэтому их целесообразно рассматривать в соответствующем контексте. Так как первая часть учебника посвящена вопросам программирования целочисленной подсистемы процессора, то в данной главее описываются регистры, обеспечивающие ее функционирование. В дальнейшем при обсуждении новых вычислительных подсистем процессора будут рассмотрены и другие регистры из перечисленных ранее.
Регистры общего назначения
Регистры общего назначения используются в программах для хранения:
經 операндов логических и арифметических операций;
變; компонентов адреса;
указателей на ячейки памяти.
В принципе, все эти регистры доступны для хранения операндов без особых ограничений, хотя в определенных условиях некоторые из них имеют жесткое функциональное назначение, закрепленное на уровне логики работы машинных команд. Среди всех этих регистров особо следует выделить регистр ESP. Его не следует использовать явно для хранения каких-либо операндов программы, так как в нем хранится указатель на вершину стека программы.
Все регистры этой группы позволяют обращаться к своим «младшим» частям (см. рис. 2.5). Обращение производится по их именам. Важно отметить, что использовать для самостоятельной адресации можно только младшие 16- и 8-разрядные части этих регистров. Старшие 16 битов как самостоятельные объекты недоступны. Это сделано, как мы отметили ранее, для совместимости с первыми 16-разрядны- ми моделями процессоров фирмы Intel. Перечислим регистры, относящиеся к группе регистров общего назначения и физически находящиеся в процессоре внутри арифметико-логического устройства (поэтому их еще называютрегистрами АЛУ): 鑫 регистр-аккумулятор (Accumulator register) EAX/AX/AH/AL применяется для хранения промежуточных данных, в некоторых командах его использование обязательно;
т базовый регистр (Base register) EBX/BX/BH/BL применяется для хранения базового адреса некоторого объекта в памяти;
II регистр-счетчик (Count register) ECX/CX/CH/CL применяется в командах, производящих некоторые повторяющиеся действия. Использование регистра-счетчика зачастую скрыто в алгоритме работы той или иной команды. Например, команда организации цикла LOOP помимо передачи управления анализирует и уменьшает на единицу значение регистра ЕСХ/СХ;
т регистр данных (Data register) EDX/DX/DH/DL, так же как и регистр ЕАХ/АХ/АН/ AL, хранит промежуточные данные (в некоторых командах его явное использование обязательно, в других он используется неявно).
Следующие два регистра предназначены для поддержки так называемых цепочечных операций, то есть операций, производящих последовательную обработку цепочек элементов, каждый из которых может иметь длину 32, 16 или 8 бит:
И регистр индекса источника (Source Index register) ESI/SI в цепочечных операциях содержит текущий адрес элемента в цепочке-источнике;
级 регистр индекса приемника (Destination Index register) EDI/DI в цепочечных операциях содержит текущий адрес в цепочке-приемнике.
В архитектуре процессора на программно-аппаратном уровне поддерживается такая структура данных, как стек. Для работы со стеком в системе команд процессора есть специальные команды, а в программной модели процессора для этого существуют специальные регистры:
綫 регистр указателя стека (Stack Pointer register) ESP/SP содержит указатель на вершину стека в текущем сегменте стека;
綫: регистр указателя базы кадра стека (Base Pointer register) EBP/BP предназначен для организации произвольного доступа к данным внутри стека.
Не стоит пугаться столь жесткого функционального назначения регистров АЛ У. На самом деле при программировании хранить операнды команд можно в большинстве регистров, причем практически в любых сочетаниях. Однако всегда следует помнить, что некоторые команды требуют строго определенных регистров. Цель подобного жесткого закрепления регистров для этих команд — более компактная кодировка их машинного представления. Знание особенностей использования регистров машинными командами (см. приложение) позволяет, при необходимости, экономить память, занимаемую кодом программы, и более эффективно программировать алгоритм.
Сегментные регистры
Процессоры Intel аппаратно поддерживают сегментную организацию программы. Это означает, что любая программа состоит из трех сегментов: кода, данных и стека. Логически машинные команды в архитектуре IA-32 построены так, что при выборке каждой команды для доступа к данным программы или к стеку неявно используется информация из вполне определенных сегментных регистров. В зависимости от режима работы процессора по их содержимому определяются адреса памяти, с которых начинаются соответствующие сегменты. В программной модели IA-32 имеется шесть сегментныхрегистров CS, SS, DS, ES, GS, FS, служащих для доступа к четырем типам сегментов.
緻 Сегмент кода содержит команды программы. Для доступа к этому сегменту служит регистр сегмента кода (Code Segment register) CS. Он содержит адрес сегмента с машинными командами, к которому имеет доступ процессор (эти команды загружаются в конвейер процессора).
綫; Сегмент данных содержит обрабатываемые программой данные. Для доступа к этому сегменту служит регистр сегмента данных (Data Segment register) DS, который хранит адрес сегмента данных текущей программы.
經 Сегмент стека представляет собой область памяти, называемую стеком. Работу со стеком процессор организует по следующему принципу: последний запи-
санный в эту область элемент выбирается первым. Для доступа к этой области служит регистр сегмента стека (Stack Segment register) SS, содержащий адрес сегмента стека.
№ Дополнительный сегмент данных. Неявно алгоритмы выполнения большинства машинных команд предполагают, что обрабатываемые ими данные расположены в сегменте данных, адрес которого находится в регистре сегмента данных DS. Если программе недостаточно одного сегмента данных, то она имеет возможность задействовать еще три дополнительных сегмента данных. Но в отличие от основного сегмента данных, адрес которого содержится в регистре DS, при использовании дополнительных сегментов данных их адреса требуется указывать явно с помощью специальных префиксов переопределения сегментов в команде. Адреса дополнительных сегментов данных должны содержаться в регистрах дополнительного сегмента данных (Extension Data Segment registers) ES, GS, FS.
Регистры состояния и управления
В процессор включены два регистра (см. рис. 2.5), постоянно содержащие информацию о состоянии как самого процессора, так и программы, команды которой он в данный момент обрабатывает:
ii регистр-указатель команд EIP/IP;
it регистр флагов Е FLAGS/FLAGS.
С помощью этих регистров можно также ограниченным образом управлять состоянием процессора.
Регистр-указатель команд (Instruction Pointer register) EIP/IP имеет разрядность 32(16) бита и содержит смещение следующей подлежащей выполнению команды относительно содержимого регистра сегмента кода CS в текущем сегменте команд. Этот регистр непосредственно недоступен программисту, но загрузка и изменение его значения производятся различными командами управления, к которым относятся команды условных и безусловных переходов, вызова процедур и возврата из процедур. Возникновение прерываний также приводит к модификации регистра EIP/IP.
Разрядностьрегистра флагов (flagregister) EFLAGS/FLAGS равна 32(16) битам. Отдельные биты данного регистра имеют определенное функциональное назначение и называются флагами. Младшая часть регистра EFLAGS/FLAGS полностью аналогична регистру FLAGS процессора i8086. На рис. 2.6 показано содержимое регистра EFLAGS.
Исходя из особенностей использования, флаги регистра EFLAGS/FLAGS можно разделить на три группы.
В первую группу флагов регистра EFLAGS/FLAGS входят 8 флагов состояния. Эти флаги могут изменяться после выполнения машинных команд. Флаги состояния регистра EFLAGS отражают особенности результата исполнения арифметических или логических операций. Это дает возможность анализировать состояние вычислительного процесса и реагировать на него с помощью команд. условных переходов и вызовов подпрограмм.
Архитектура IA-3247
ФЛАГИ СОСТОЯНИЯ :ФЛАГИ СОСТОЯНИЯ :.флаг вложенности задачи.флаг вложенности задачи-УРОвень привилегированности ввода-вывода-УРОвень привилегированности ввода-вывода©flags (0..31)©flags (0..31)flags (0.15)flags (0.15)флаг переполненияфлаг знака• флаг нулявспомогательный флаг переносафлаг переноса —флаг четности —флаг переполненияфлаг знака• флаг нулявспомогательный флаг переносафлаг переноса —флаг четности —2121202019191818171716161515141413 1213 12111110100909080807070606050504040303020201010000IDIDVIPVIPVIFVIFACACVMVMRFRFNTNTIOPLIOPLOFOFDFDFIFIFTFTFSFSFZFZFООAFAFPFPFCFCF
ФЛАГИ СОСТОЯНИЯ :
ФЛАГИ СОСТОЯНИЯ :
.флаг вложенности задачи
.флаг вложенности задачи
-УРОвень привилегированности ввода-вывода
-УРОвень привилегированности ввода-вывода
©flags (0..31)
©flags (0..31)
flags (0.15)
flags (0.15)
флаг переполнения
флаг знака
• флаг нуля
вспомогательный флаг переноса
флаг переноса —
флаг четности —
флаг переполнения
флаг знака
• флаг нуля
вспомогательный флаг переноса
флаг переноса —
флаг четности —
21
21
20
20
19
19
18
18
17
17
16
16
15
15
14
14
13 12
13 12
11
11
10
10
09
09
08
08
07
07
06
06
05
05
04
04
03
03
02
02
01
01
00
00
ID
ID
VIP
VIP
VIF
VIF
AC
AC
VM
VM
RF
RF
NT
NT
IOPL
IOPL
OF
OF
DF
DF
IF
IF
TF
TF
SF
SF
ZF
ZF
О
О
AF
AF
PF
PF
CF
CF
ФЛАГ УПРАВЛЕНИЯ: I флаг направленияФЛАГ УПРАВЛЕНИЯ: I флаг направленияСИСТЕМНЫЕ ФЛАГИ:
ФЛАГ УПРАВЛЕНИЯ: I флаг направления
ФЛАГ УПРАВЛЕНИЯ: I флаг направления
флагтрассировки
флаг прерывания
флаг возобновления
флаг виртуального 8086
флаг контроля выравнивания
флаг виртуального прерывания
флаг отложенного прерывания (для режима виртуального 18086)
■флагподдержки идентификации
процессора
Рис. 2.6. Содержимое регистра eflags
臻 Флаг переноса (carry flag) CF:
1 — арифметическая операция произвела перенос из старшего бита результата, старшим является 7-й, 15-й или 31-й бит в зависимости от размерности операнда;
О — переноса не было.
滚 Флаг четности (parity flag) PF:
1-8младших разрядов (этот флаг только для 8 младших разрядов операнда любого размера) результата содержат четное число единиц;
0 — 8 младших разрядов результата содержат нечетное число единиц.
鹽 Вспомогательный флаг переноса (auxiliary carry flag) AF применяется только для команд, работающих с BCD-числами. Фиксирует факт заема из младшей тетрады результата:
— в результате операции сложения был произведен перенос из разряда 3 в старший разряд или при вычитании был заем в разряд 3 младшей тетрады из значения в старшей тетраде;
О — переносов и заемов в третий разряд (из третьего разряда) младшей тетрады результата не было.
Флаг нуля (zero flag) ZF:
1 — результат нулевой;
О — результат ненулевой.
* Флаг знака (sign flag) SF отражает состояние старшего бита результата (биты 7, 15 или 31 для 8-, 16- или 32-разрядных операндов соответственно):
— старший бит результата равен 1;
О — старший бит результата равен 0.
ж Флаг переполнения (overflow flag) OF используется для фиксации факта потери значащего бита при арифметических операциях:
— в результате операции происходит перенос в старший знаковый бит результата или заем из старшего знакового бита результата (биты7,15или 31 для 8', 16- или 32-разрядных операндов соответственно);
О — в результате операции не происходит переноса в старший знаковый бит результата или заема из старшего знакового бита результата.
Уровень привилегированности ввода-вывода (Input/Output privilege level) IOPL используется в защищенном режиме работы процессора для контроля доступа к командам ввода-вывода в зависимости от привилегированности задачи.
Флаг вложенности задачи (nested task) NT используется в защищенном режиме работы процессора для фиксации того факта, что одна задача вложена в другую.
Во вторую группу флагов (группа флагов управления) регистра EFLAGS/FLAGS входит всего один флаг направления (directory flag) DF. Он находится в десятом бите регистра ЕFLAGS и используется цепочечными командами. Значение флага DF определяет направление поэлементной обработки в этих операциях: от начала строки к концу (DF = 0) либо, наоборот, от конца строки к ее началу (DF=1).Для работы с флагом DF существуют специальные команды CLD (снять флаг DF) и STD (установить флаг DF). Применение этих команд позволяет привести флаг DF в соответствие с алгоритмом и обеспечить автоматическое увеличение или уменьшение счетчиков при выполнении операций со строками.
В третью группу флагов регистра EFLAGS/FLAGS входит 8 системных флагов, управляющихвводом-выводом, маскируемымипрерываниями, отладкой, переключением между задачами и режимом виртуального процессора 8086. Прикладным программам не рекомендуется модифицировать без необходимости эти флаги, так как в большинстве случаев это ведет к прерыванию работы программы. Далее перечислены системные флаги и их назначение.
Флаг трассировки (trace flag) TF предназначен для организации пошаговой работы процессора:
— процессор генерирует прерывание с номером 1 после выполнения каждой машинной команды (может использоваться при отладке программ, в частности отладчиками);
О — обычная работа.
鬱 Флаг прерывания (interrupt enable flag) IF предназначен для разрешения или запрещения (маскирования) аппаратных прерываний (прерываний по входу INTR):
— аппаратные прерывания разрешены;
О — аппаратные прерывания запрещены.
Флаг возобновления (resume flag) RF используется при обработке прерываний от регистров отладки.
И Флаг режима виртуального процессора 8086 (virtual 8086 mode) VM является признаком работы процессора в режиме виртуального 8086:
— процессор работает в режиме виртуального процессора 8086;
О — процессор работает в реальном или защищенном режиме.
Флаг контроля выравнивания (alignment check) AC предназначен для разрешения контроля выравнивания при обращениях к памяти. Используется совместно с битом AM в системном регистре CRO. К примеру, Pentium разрешает размещать команды и данные начиная с любого адреса. Если требуется контролировать выравнивание данных и команд по адресам, кратным 2 или 4, то установка данных битов приведет к тому, что все обращения по некратным адресам будут возбуждать исключительную ситуацию.
風 Флаг виртуального прерывания (virtual interrupt flag) VIF, появившийся в процессоре Pentium, при определенных условиях (одно из которых — работа процессора в v-режиме) является аналогом флага IF. Флаг VIF используется совместно с флагом VI Р.
Флаг отложенного виртуального прерывания (virtual interrupt pending flag) VIP, появившийся в процессоре Pentium, устанавливается в 1 для индикации отложенного прерывания. Используется при работе в v-режиме совместно с флагом VIF.
後 Флаг идентификации (identification flag) ID используется для того, чтобы показать факт поддержки процессором инструкции CPUID. Если программа может установить или сбросить этот флаг, это означает, что данная модель процессора поддерживает инструкцию CPUID.
Организация памяти
Физическая память, к которой процессор имеет доступ по шине адреса (см. рис. 1.2), называется оперативной памятью (или оперативным запоминающим устройством — ОЗУ). На самом нижнем уровне память компьютера можно рассматривать как массив битов. Один бит может хранить значение 0 или 1. Для физической реализации битов и работы с ними идеально подходят логические схемы. Но процессору неудобно работать с памятью на уровне битов, поэтому реально ОЗУ организовано как последовательность ячеек — байтов. Один байт состоит из восьми битов. Каждому байту соответствует свой уникальный адрес (его номер), называемый физическим. Диапазон значений физических адресов зависит от разрядности шины адреса процессора. Для i486 и Pentium он находится в пределах от О до 232 - 1 (4 Гбайт). Для процессоров Pentium Pro/II/III/IV этот диапазон шире — от 0 до 236 - 1 (64 Гбайт).
Механизм управления памятью полностью аппаратный. Это означает, что программа не может сама сформировать физический адрес памяти на адресной шине. Ей приходится «играть» по правилам процессора. Что это за правила, мы узнаем чуть позже. Пока же отметим, что в конечном итоге этот механизм позволяет обеспечить:
* компактность хранения адреса в машинной команде;
И гибкость механизма адресации;
^ защиту адресных пространств задач в многозадачной системе;
Ж поддержку виртуальной памяти.
Процессор аппаратно поддерживает две модели использования оперативной памяти.
薮. В сегментированной модели программе выделяются непрерывные области памяти (сегменты), а сама программа может обращаться только к данным, которые находятся в этих сегментах.
* Страничную модель можно рассматривать как надстройку над сегментированной моделью. В случае использования этой модели оперативная память рассматривается как совокупность блоков фиксированного размера (4 Кбайт и более). Основное применение этой модели связано с организацией виртуальной памяти, что позволяет операционной системе использовать для работы программ пространство памяти большее, чем объем физической памяти. Для процессоров i486 и Pentium размер возможной виртуальной памяти может достигать 4 Тбайт.
Сегментированная модель памяти
Сегментация — механизм адресации, обеспечивающий существование нескольких независимых адресных пространств как в пределах одной задачи, так и в системе в целом для защиты задач от взаимного влияния. В основе механизма сегментации лежит понятие сегмента, который представляет собой независимый поддерживаемый на аппаратном уровне блок памяти.
Когда мы рассматривали сегментные регистры, то отмечали, что для процессоров Intel, начиная с i8086, принят особый подход к управлению памятью. Каждая программа в общем случае может состоять из любого количества сегментов, но непосредственный доступ она имеет только к трем основным сегментам (кода, данных и стека), а также к дополнительным сегментам данных числом от одного до трех. Программа никогда не знает, по каким физическим адресам будут размещены ее сегменты. Этим занимается операционная система. Операционная система размещает сегменты программы в оперативной памяти по определенным физическим адресам, после чего помещает значения этих адресов в определенные места. Куда именно, зависит от режима работы процессора. Так, в реальном режиме эти адреса помещаются непосредственно в соответствующие сегментные регистры, а в защищенном режиме они размещаются в элементы специальной системной дескрипторной таблицы. Внутри сегмента программа обращается к адресам относительно начала сегмента линейно, то есть начиная с 0 и заканчивая адресом, равным размеру сегмента. Этот относительный адрес, или смещение, который процессор использует для доступа к данным внутри сегмента, называется эффективным.
Отличия моделей сегментированной организации памяти в различных режимах хорошо видны на схеме (рис. 2.7). Различают три основных модели сегментированной организации памяти:
綴 сегментированная модель памяти реального режима;
сегментированная модель памяти защищенного режима;
сплошная модель памяти защищенного режима.
а)модель памяти реального режимаа)модель памяти реального режимаСегментный регистрСегментный регистрСегментный регистрСегментный регистр236-1 (232- 1)236-1 (232- 1)в) Сплошная модель памяти защищенного режимав) Сплошная модель памяти защищенного режимаПамятьПамятьСегментные регистры:Сегментные регистры:Таблица дескрипторовТаблица дескрипторовРис. 2.7. Модели памяти процессоров IntelРис. 2.7. Модели памяти процессоров Intel
а)модель памяти реального режима
а)модель памяти реального режима
Сегментный регистр
Сегментный регистр
Сегментный регистр
Сегментный регистр
236-1 (232- 1)
236-1 (232- 1)
в) Сплошная модель памяти защищенного режима
в) Сплошная модель памяти защищенного режима
Память
Память
Сегментные регистры:
Сегментные регистры:
Таблица дескрипторов
Таблица дескрипторов
Рис. 2.7. Модели памяти процессоров Intel
Рис. 2.7. Модели памяти процессоров Intel
Рассмотрим порядок формирования физического адреса в реальном и защищенном режимах. Уточним терминологию. Под физическим адресом понимается адрес памяти, выдаваемый на шину адреса процессора. Другое название этого адреса — линейный адрес. Подобная двойственность в названии обусловлена наличием страничной модели организации оперативной памяти. Эти названия являются синонимами только при отключении страничного преобразования адреса
(в реальном режиме страничная адресация всегда отключена). Страничная модель, как мы отметили ранее, является надстройкой над сегментированной моделью. В страничной модели линейный и физический адреса имеют разные значения. Далее мы будем обсуждать схему, на которой показан порядок формирования адреса в реальном режиме работы процессора. Обратите внимание на наличие в этой схеме устройства страничного преобразования адреса, предназначенного для того, чтобы совместить две принципиально разные модели организации оперативной памяти и выдать на шину адреса истинное значение физического адреса памяти.
Формирование физического адреса в реальном режиме
Далее перечислены характеристики механизма адресации физической памяти в реальном режиме.
餒 Диапазон изменения физического адреса — от 0 до 1 Мбайт. Эта величина определяется тем, что шина адреса i8086 имела 20 линий.
缴Максимальный размер сегмента — 64 Кбайт. Это объясняется 16-разрядной архитектурой i8086. Нетрудно подсчитать, что максимальное значение, которое могут содержать 16-разрядные регистры, составляет 2〔б - 1, что применительно к памяти и определяет величину 64 Кбайт.
綫 Для обращения к конкретному физическому адресу оперативной памяти необходимо определить адрес начала сегмента (сегментную составляющую) и смещение внутри сегмента.
Понятие адреса начала сегмента ввиду принципиальной важности требует дополнительного пояснения. Исходя из разрядности сегментных регистров, можно утверждать, что сегментная составляющая адреса (или база сегмента) представляет собой всего лишь 16-разрядное значение, помещенное в один из сегментных регистров. Максимальное значение, которое при этом получается, соответствует 216 - 1. Если так рассуждать, то получается, что адрес начала сегмента может быть только в диапазоне 0-64 Кбайт от начала оперативной памяти. Возникает вопрос, как адресовать остальную часть оперативной памяти вплоть до 1 Мбайт с учетом того, что размер самого сегмента не превышает 64 Кбайт. Дело в том, что в сегментном регистре содержатся только старшие 16 битов физического адреса начала сегмента. Недостающие младшие четыре бита 20-разрядного адреса получаются сдвигом значения в сегментном регистре влево на 4 разряда. Эта операция сдвига выполняется аппаратно и для программного обеспечения абсолютно прозрачна. Получившееся 20-разрядное значение и является настоящим физическим адресом, соответствующим началу сегмента. Что касается второго компонента (смещения), участвующего в образовании физического адреса некоторого объекта в памяти, то он представляет собой 16-разрядное значение. Это значение может содержаться явно в команде либо косвенно в одном из регистров общего назначения. В процессоре эти две составляющие складываются на аппаратном уровне, в результате получается физический адрес памяти размерностью 20 битов. Данный механизм образования физического адреса позволяет сделать программное обеспечение перемещаемым, то есть не зависящим от конкретных адресов загрузки его в оперативной памяти (рис. 2.8).
Архитектура IA-3253
ОперативнаяпамятьОперативнаяпамятьСдвинуть на 4 бита влевоСдвинуть на 4 бита влевоЭффективный адресЭффективный адресАЛУАЛУ15О15О.Линейныйадрес.ЛинейныйадресФизическийадресФизическийадресРис. 2.8.Рис. 2.8.15 СегментныеI csрегистры:ds15 СегментныеI csрегистры:dses fses fsШина адресаШина адресаМеханизм формирования физического адресаМеханизм формирования физического адресав реальном режимев реальном режиме
Оперативнаяпамять
Оперативнаяпамять
Сдвинуть на 4 бита влево
Сдвинуть на 4 бита влево
Эффективный адрес
Эффективный адрес
АЛУ
АЛУ
15О
15О
.Линейныйадрес
.Линейныйадрес
Физическийадрес
Физическийадрес
Рис. 2.8.
Рис. 2.8.
15 СегментныеI cs
регистры:
ds
15 СегментныеI cs
регистры:
ds
es fs
es fs
Шина адреса
Шина адреса
Механизм формирования физического адреса
Механизм формирования физического адреса
в реальном режиме
в реальном режиме
На рисунке хорошо видно, как формируется некоторый целевой физический адрес: сегментная часть извлекается из одного из сегментных регистров, сдвигается на четыре разряда влево и суммируется со смещением. В свою очередь, видно, что значение смещения можно получить минимум из одного и максимум из трех источников: из значения смещения в самой машинной команде и/или из содержимого одного базового и/или одного индексного регистра. Количество источников, участвующих в формировании смещения, определяется кодированием конкретной машинной команды, и если таких источников несколько, то значения в них складываются. В заключение заметим, что не стоит волноваться из-за несоответствия размеров шины адреса процессора i486 или Pentium (32 бита) и 20-разряд- ного значения физического адреса реального режима. Пока процессор находится в реальном режиме, старшие 12 линий шины адреса попросту недоступны, хотя при определенных условиях и существует возможность работы с первыми 64 Кбайт оперативной памяти, лежащими сразу после первого мегабайта.
Недостатки такой организации памяти:
И сегменты бесконтрольно размещаются с любого адреса, кратного 16 (так как содержимое сегментного регистра аппаратно смещается на 4 разряда), и, как следствие, программа может обращаться по любым адресам, в том числе и реально не существующим;
сегменты имеют максимальный размер 64 Кбайт;
сегменты могут перекрываться другими сегментами.
Желанием ввести в архитектуру средства, позволяющие избавиться от указанных недостатков, и обусловлено, в частности, появление защищенного режима,
в котором работают все современные операционные системы, в том числе Windows и Linux.
Формирование физического адреса в защищенном режиме
Основная идея защищенного режима — защитить исполняемые процессором программы от взаимного влияния. В защищенном режиме процессор поддерживает два типа защиты — по привилегиям и по доступу к памяти. В контексте нашего изложения интерес представляет второй тип защиты. Его мы и рассмотрим.
Для введения любого механизма защиты нужно иметь как можно больше информации об объектах защиты. Для процессора такими объектами являются исполняемые им программы. Организуя защиту программ по доступу к памяти, фирма Intel не стала нарушать принцип сегментации, свойственный ее процессорам. Так как каждая программа занимает один или несколько сегментов в памяти, то логично иметь больше информации об этих сегментах как об объектах, реально существующих в данный момент в вычислительной системе. Если каждому из сегментов присвоить определенные атрибуты, то часть функций по контролю за доступом к ним можно переложить на процессор. Что и было сделано. Любой сегмент памяти в защищенном режиме имеет следующие атрибуты:
趣 расположение сегмента в памяти;
蠶 размер сегмента;
総 уровень привилегий (определяет права данного сегмента относительно других сегментов);
И тип доступа (определяет назначение сегмента);
и некоторые другие.
В отличие от реального режима, в защищенном режиме программа уже не может запросто обратиться по любому физическому адресу памяти. Для этого она должна иметь определенные полномочия и удовлетворять ряду требований.
Ключевым объектом защищенного режима является специальная структура — дескриптор сегмента, который представляет собой 8-байтовый дескриптор (крат-
Тип сегмента
P DPL S
1 C/ED R/W
А
7 65 4
3 21
0
.base 3
Размер сегмента (16...19')
,
,base 2 Ф
Базовый адрес сегмента(31...24)
V
G D О U limit 2 iiii
Бай
т AR
Базовый адрес сегмента (23... 16)
63
55
47
390
Базовый адрес сегмента (0...15)
Размер сегмента (0...15)
base 1
limit 1
3115
О
Рис. 2.9. Структура дескриптора сегмента защищенного режима процессора
кое описание) непрерывной области памяти, содержащий перечисленные ранее атрибуты. На рис. 2.9 приведена структура дескриптор сегмента.
Приведем назначение некоторых полей дескриптора сегмента:
鬱limit」 и Limit_2 — 20-разрядное поле, определяющее размер сегмента;
後 base_l и base_2 — 32-разрядное поле, определяющее значение линейного адреса начала сегмента в памяти;
线 AR — байт, поля которого определяют права доступа к сегменту;
^ D — бит разрядности операндов и адресов (глава 3);
薜 G — бит гранулярности.
В защищенном режиме размер сегмента не фиксирован, его расположение можно задать в пределах 4 Гбайт. Если посмотреть на рисунок, то возникнет вопрос: почему разорваны поля, определяющие размер сегмента и его начальный (базовый) адрес? Это результат эволюции процессоров. Защищенный режим впервые появился в процессоре i80286. Этот процессор имел 24-разрядную адресную шину и, соответственно, мог адресовать в защищенном режиме до 16 Мбайт оперативной памяти. Для этого ему достаточно было иметь в дескрипторе поле базового адреса 24 бита и поле размера сегмента 16 битов. После появления процессора i80386 с 32-разрядной шиной команд и данных в целях совместимости программ разработчики не стали менять формат дескриптора, а просто использовали свободные поля. Внутри процессора эти поля объединены. Внешне же они остались разделенными, и при программировании с этим приходится мириться.
Следующий интересный момент связан с тем, что размер сегмента в защищенном режиме может достигать 4 Гбайт, то есть занимать все доступное физическое пространство памяти. Как это возможно, если суммарный размер поля размера сегмента составляет всего 20 битов, что соответствует величине 1 Мбайт? Секрет скрыт в поле гранулярности — бит G (см. рис. 2.9). Если G = 0, то значение в поле размера сегмента означает размер сегмента в байтах, если G - 1, — то в страницах. Размер страницы составляет 4 Кбайт. Нетрудно подсчитать, что когда максимальное значение поля размера сегмента составляет Offfffh то это соответствует 1 М страниц или величине 1 М х 4 Кбайт = 4 Гбайт.
Выведение информации о базовом адресе сегмента и его размере на уровень процессора позволяет аппаратно контролировать работу программ с памятью и предотвращать обращения к несуществующим адресам либо к адресам, находящимся вне предела, разрешенного полем размера сегмента limit.
Другой аспект защиты заключается в том, что сегменты неравноправны в правах доступа к ним. Информация об этом содержится в специальном байте AR, входящем в состав дескриптора. Наиболее важные поля байта AR — это dpiи биты R/W, C/ED и I, которые вместе определяют тип сегмента. Поле dpi — часть механизма защиты по привилегиям. Суть этого механизма заключается в том, что конкретный сегмент может находиться на одном из четырех уровней привилегированности с номерами О, 1, 2 и 3. Самым привилегированным является уровень 0. Существует ряд ограничений (опять-таки на аппаратном уровне) на взаимодействие сегментов кода, данных и стека с различными уровнями привилегий.
Итак, мы выяснили, что в защищенном режиме перед использованием любой области памяти должна быть проведена определенная работа по инициализации соответствующего дескриптора. Эту работу выполняет операционная система или программа, сегменты которой также описываются подобными дескрипторами. Все дескрипторы собираются вместе в одну из трех дескрипторных таблиц:
纖 глобальная дескрипторная таблица (Global Descriptor Table, GDT), ее адрес хранится в регистре GDTR;
緩 локальная дескрипторная таблица (Local Descriptor Table, LDT), ее адрес хранится в регистре LDTR;
綫 дескрипторная таблица векторов прерываний (Interrupt Descriptor Table, IDT), ее адрес хранится в регистре IDTR.
В какую именно таблицу должен быть помещен дескриптор, определяется его назначением. Адрес, по которому размещаются эти дескрипторные таблицы, может быть любым; он хранится в специально предназначенном для этого адреса системном регистре.
Схемы, показанные на рис. 2.7, б и в, иллюстрируют Принцип формирования адреса в защищенном режиме. Важно отметить изменение роли сегментных регистров. В защищенном режиме они содержат не адрес, а селектор, то есть указатель насоответствующуюячейкуоднойиздескрипторныхтаблиц(GDTилиLDT).
Остальные элементы архитектуры IA-32, такие как формат машинных команд, типы данных и др., логично рассмотреть в следующих главах в контексте соответствующих аспектов использования языка ассемблера.
Итоги
緩 Понимание архитектуры ЭВМ является ключевым для изучения ассемблера. Это касается любого типа компьютера. Структура ассемблера, формат его команд, адресация операндов и т. д. полностью отражают особенности архитектуры компьютера. Есть общие архитектурные свойства, присущие всем современным машинам фон-неймановской архитектуры, и есть частные свойства, присущие конкретному типу компьютеров.
緩 Целью изучения архитектуры является:
□ выявление набора доступных для программирования регистров, их функционального назначения и структуры;
D понимание организации оперативной памяти и порядка ее использования;
П знакомство с типами данных;
П изучение формата машинных команд;
П выяснение организации обработки прерываний.
И Процессор содержит 32 доступных тем или иным образом регистра. Они делятся на пользовательские и системные.
* Пользовательские регистры имеют определенное функциональное назначение. Среди них особо нужно выделить регистр флагов Е FLAGS и регистр указателя команды EIP. Назначение регистра EFLAGS — отражать состояние процессора
Итоги57
после выполнения последней машинной команды. Регистр EIP содержит адрес следующей выполняемой машинной команды. Доступ к этим регистрам, в силу их специфики, со стороны программ пользователя ограничен.
幾 Процессор имеет три основных режима работы:
D реальный режим, который использовался для i8086 и поддерживается до сих пор для обеспечения совместимости программного обеспечения;
□ защищенный режим, который впервые появился в i80286;
D режим виртуального процессора i8086 обеспечивает полную эмуляцию i8086, позволяя при этом организовать многозадачную работу нескольких таких программ.
幾 Процессор имеет сложную систему управления памятью, функционирование которой зависит от режима работы процессора.
Глава 3
Система команд
процессора IA-32
Формат машинных команд IA-32
> Назначение и интерпретация полей машинной команды
* Основы декодирования машинных команд
Функциональная классификация машинных команд
Система машинных команд является важнейшей частью архитектуры компьютера, так как с их помощью производится непосредственное управление работой процессора. К примеру, система команд процессора Pentium IV содержит более 300 машинных команд. С появлением каждой новой модели процессора количество команд, как правило, возрастает, отражая архитектурные новшества данной модели по сравнению с предшествующими.
При знакомстве с системой машинных команд необходимо учитывать два аспекта — собственно набор машинных команд и правила представления этих команд на уровне процессора, то есть формат машинных команд. Процессору компьютера понятен только один язык — язык машинных команд. Машинные команды представляют собой сформированные по определенным правилам последовательности нулей и единиц. Для того чтобы заставить процессор выполнить некоторое действие, ему нужно выдать соответствующее указание в виде машинной команды, а для выполнения более сложной работы достаточно написать программу в двоичных кодах. Программирование первых компьютеров осуществлялось именно таким способом. Недостатки процесса написания программ в двоичном коде очевидны. Для облегчения процесса разработки программ был придуман язык ассемблера, как символический аналог машинного языка, а в архитектуру компьютера введен блок микропрограммного управления. Для каждой машинной команды блок
микропрограммного управления содержит отдельную микропрограмму, с помощью которой действия, заданные этой командой, переводятся на язык сигналов, направляемых нужным подсистемам процессора. После этих нововведений процесс разработки программы значительно упростился. Человек пишет программу на понятном ему языке символов, специальная программа — ассемблер — переводит (транслирует) программу человека на машинный язык, а блок микропрограммного управления нужным образом интерпретирует машинные команды для процессора, процессор выполняет нужную работу.
В дальнейшем, с появлением программного обеспечения более высокого уровня, язык ассемблера не потерял своей роли, а наоборот, приобрел новые качества. В силу иерархичности программного обеспечения компьютера ассемблер стал промежуточным, связующим звеном между разнородным и разноязыким программным обеспечением более высокого уровня и процессором.
Таким образом, существует взаимно однозначное соответствие машинных команд и команд ассемблера. Понимание правил формирования машинных команд из команд ассемблера является одним из необходимых условий не только для изучения языка ассемблера, но и для понимания логики работы компьютера в целом.
Формат машинных команд IA-32
Машинная команда представляет собой закодированное по определенным правилам указание процессору на выполнение некоторой операции. Правила кодирования команд называются форматом команд. Команды процессоров архитектуры IA-32 считаются сложными. Максимальная длина машинной команды IA-32 составляет 15 байт. Реальная команда может содержать гораздо меньшее количество полей, вплоть до одного — только код операции. Приведенный на рис. 3.1 формат машинной команды является наиболее полным.
Как на уровне формата машинной команды соответствуют между собой машинные команды и команды ассемблера? Для примера рассмотрим типичную команду языка ассемблера:
mov ebx, eax
Команда MOV производит копирование содержимого регистра EAX в регистр ЕВХ. Соответствующая машинная команда будет выглядеть так:
8B D8
Значение 8В — код операции. Еще один пример команды MOV:
mov ecx, 128
Данная команда инициализирует содержимое регистра ЕСХ десятичным значением 128. Соответствующая машинная команда выглядит так:
B9 00000080
Значение поля с кодом операции — В9. Из примеров видно, что прямого соответствия между структурой команды ассемблера и соответствующей машинной командой нет. Несмотря на то, что команда ассемблера одна и та же (MOV), коды машинных команд — разные (8В и В9). Большинство команд ассемблера имеют несколько возможных вариантов сочетания операндов. Как показано в приведенных примерах, несмотря на одинаковые названия команд ассемблера, для
ОднобайтовыепрефиксыОднобайтовыепрефиксыГ повторенияГ повторения!!размера операндаразмера операндазамены сегментазамены сегментаКОПКОП7 6 5 4 37 6 5 4 3КОПКОП7 6 5 4 37 6 5 4 3КОПКОП| блокировки шины| блокировки шины7 6 5 4 3 2 1 о;7 6 5 4 3 2 1 о;|размера адреса|размера адресаКОП3""6 5 Z*WWКОП3""6 5 Z*WW1, 2 или 30 или 10 или 1& L 2 или 40Р 1» 2или4код операциибайт modr/mбайт sibсмещение в команденепосредственный операнд1, 2 или 30 или 10 или 1& L 2 или 40Р 1» 2или4код операциибайт modr/mбайт sibсмещение в команденепосредственный операндКоличество байтовКоличество байтовw | mod |||w | mod |||КОПКОП7 6 5 4 3 2 10indexbase7 6 5 4 3 2 10indexbasewws ws wregreg
Однобайтовыепрефиксы
Однобайтовыепрефиксы
Г повторения
Г повторения
!
!
размера операнда
размера операнда
замены сегмента
замены сегмента
КОП
КОП
7 6 5 4 3
7 6 5 4 3
КОП
КОП
7 6 5 4 3
7 6 5 4 3
КОП
КОП
| блокировки шины
| блокировки шины
7 6 5 4 3 2 1 о;
7 6 5 4 3 2 1 о;
|размера адреса
|размера адреса
КОП
3""6 5 Z*WW
КОП
3""6 5 Z*WW
1, 2 или 3
0 или 1
0 или 1
& L 2 или 4
0Р 1» 2или4
код операции
байт modr/m
байт sib
смещение в команде
непосредственный операнд
1, 2 или 3
0 или 1
0 или 1
& L 2 или 4
0Р 1» 2или4
код операции
байт modr/m
байт sib
смещение в команде
непосредственный операнд
Количество байтов
Количество байтов
w | mod |||
w | mod |||
КОП
КОП
7 6 5 4 3 2 10
indexbase
7 6 5 4 3 2 10
indexbase
w
w
s w
s w
reg
reg
i 7 6 5 4 3
I, I7 6 5 4I, I7 6 5 4w1 ?4 Iw1 ?4 I3 2 103 2 10
I, I
7 6 5 4
I, I
7 6 5 4
w1 ?4 I
w1 ?4 I
3 2 10
3 2 10
Рис.Формат машинной команды
каждого возможного сочетания операндов имеется своя машинная команда, со своим значением поля кода операции (рис.Это говорит о том, что машинная ко
манда всегда однозначна по отношению к производимым ею действиям на уровне аппаратуры. Несколько упрощая реальность, можно утверждать, что значение в поле кода операции является номером микропрограммы в блоке микропрограммного управления для каждой конкретной команды ассемблера с каждым конкретным вариантом сочетания операндов.
Логически любая команда языка ассемблера содержит несколько элементов.
Элемент, описывающий, что делать, называется кодом операции (КОП). Значение в поле кода операции некоторым образом определяет в блоке микропрограммного управления подпрограмму, реализующую действия для данной команды.
в Элементы, описывающие объекты, с которыми нужно что-тоявляют
ся операндами. Операнды в команде могут и не задаваться, а подразумеваться по умолчанию.
и Элементы, описывающие, какявляются типамии обычно
задаются неявно. Они используются транслятором ассемблера при формировании машинной команды для определения значения поля кода операции.
Эти же элементы имеет и машинная команда, но в закодированном виде. Перевод команд ассемблера в соответствующие машинные команды осуществляет специальная программа — ассемблер, которую можно также назвать транслятором (компилятором) ассемблера.
Опишем назначения полей машинной команды (рис.
Поле префиксов
Префиксы — необязательные однобайтные элементы машинной команды. Назначение префиксов — изменить действия, выполняемые командой. Префиксы могут указываться программистом явно при написании исходного текста программы, либо их, по определенным соображениям, может вставить ассемблер. Процессор распознает префиксы по их значениям. Машинная команда может иметь до четырех префиксов одновременно. В памяти префиксы предшествуют команде. Порядок их следования при этом может быть любым.
Далее перечислены типы префиксов, которые может использовать прикладная программа.
* Префикс замены сегмента в явной форме указывает, какой сегментный регистр используется в данной команде для адресации стека или данных. Префикс отменяет выбор сегментного регистра по умолчанию. Префиксы замены сегмента имеют следующие значения:
D 2Eh — замена сегмента CS;
D 36h — замена сегмента SS;
□ 3Eh — замена сегмента DS;
□ 26h — замена сегмента ES;
D 64h — замена сегмента FS;
D 65h — замена сегмента GS.
後 Префикс повторения используется с цепочечными командами (командами обработки строк). Этот префикс «зацикливает» команду для обработки всех элементов цепочки. Система команд поддерживает два типа префиксов: безусловные (REP — 0F3h), заставляющие цепочечную команду повторяться некоторое количество раз, ^условные(REPE/REPZ — OF3h, REPNE/REPNZ — OF2h), которые при зацикливании проверяют некоторые флаги, и в результате проверки возможен досрочный выход из цикла.
繇 Префикс блокировки шины инициирует выдачу процессором сигнала LOCK# (значение OFOh) для блокировки системной шины. Используется в многопроцессорных конфигурациях для обеспечения монопольного владения системной шиной. Сигнал LOCK# может формироваться лишь с определенной номенклатурой команд процессора, работающих в цикле «чтение-модификация-запись».
爨 Префикс размера адреса (значение 67h) уточняет разрядность адреса: 16 или 32 бита. Каждой команде, в которой используется адресный операнд, ставится в соответствие разрядность адреса этого операнда. Если разрядность адреса для данной команды составляет 16 битов, это означает, что команда содержит 16-раз- рядное смещение и оно соответствует 16-разрядному смещению адресного операнда относительно начала некоторого сегмента (см. рис. 3.1). В контексте материала главы 2 (см. рис. 2.7 и 2.8) это смещение называется эффективным адресом. Если разрядность адреса составляет 32 бита, это означает, что команда содержит 32-разрядное смещение, оно соответствует 32-разрядному смещению адресного операнда относительно начала сегмента и по его значению формиру-
ется 32-разрядное смещение в сегменте (см. рис. 3.1). С помощью префикса разрядности адреса можно изменить действующее по умолчанию значение разрядности адреса. Это изменение будет касаться только той команды, которой предшествует префикс.
• Префикс размера операнда (значение 66h) аналогичен префиксу размера адреса, но указывает на разрядность операндов (32 или 16 битов), с которыми работает команда.
По каким правилам устанавливаются по умолчанию значения атрибутов разрядности адреса и операндов? Если команда имеет операнд в памяти, то его адрес представляет собой значение смещения относительно начала сегмента данных (если не используется префикс переопределения сегмента) и содержится в поле смещения машинной команды. Размер этого поля зависит от текущего режима адресации (атрибуты usel6 или use32 в директивах сегментации). При 16-разрядной адресации размер поля смещения в машинной команде составляет 16 битов. При 32-разрядной адресации размер поля смещения в машинной команде составляет 32 бита. Явное задание префикса размера адреса позволяет указать процессору значение, отличающееся от действующего по умолчанию. Например, если действующий размер адреса равен 16 битам, то использование перед какой-либо командой префикса 67h определит для нее (и только для нее!) размер адреса в 32 бита. И наоборот, если действующий размер адреса равен 32 бита, то указание перед командой префикса 67h определит для нее (и только для нее!) размер адреса в 16 битов. Физически это будет отражаться на размере поля смещения в данной машинной команде.
Префикс размера операнда 66h позволяет сменить действующий для данной команды атрибут размера операнда. Команда, которая по умолчанию работает со словом (16 битов) или с двойным словом (32 бита), имеет атрибут размера операнда, равный 16 и 32 бита соответственно. Применение префикса размера операнда позволяет сменить действующий по умолчанию атрибут.
При работе процессора i8086в реальном и виртуальном режимах атрибуты размера адреса и операнда по умолчанию равны 16битов. В защищенном режиме значения этих атрибутов зависят от значения бита D дескриптора сегмента. Если D = 0, то атрибуты размера адреса и операнда равны 16битов, если D = 1, то эти атрибуты равны 32 бита. Изменить действующие по умолчанию атрибуты адреса и размера операндов можно применением атрибутов usel6 или use32 в директивах сегментации.
В реальном режиме с помощью префикса разрядности адреса можно задействовать 32-разрядную адресацию, но при этом необходимо помнить об ограниченности размера сегмента величиной 64 Кбайт. Аналогично префиксу разрядности адреса можно использовать префикс разрядности операнда в реальном режиме для работы с 32-разрядными операндами (к примеру, в арифметических командах).
В команде префиксы размера адреса и операнда могут указываться одновременно. Каким образом комбинация префиксов (указанных явно и установленных по умолчанию) влияет на атрибуты размера операнда и адреса машинной команды, показано в табл. 3.1. В ней отражено и то, как влияет на эти атрибуты состояние бита D дескриптора сегмента в защищенном режиме. Строки таблицы, соот-
ветствующие нулевому значению бита D, используются в реальном и виртуальном режимах работы процессора i8086.
Необходимо обратить внимание на то, что команды работы со стеком также имеют аналогичные атрибуты размера и адреса операнда. Атрибут размера адреса влияет на выбор регистра — указателя стека: при размере адреса 16 битов используется регистр SP, при размере адреса 32 бита используется регистр ESP. Аналогично влияет на работу команд со стеком префикс размера операнда: при использовании префикса размера операнда 16 битов операнд в стеке трактуется как 16-разряд- ный, при использовании префикса размера операнда 32 бита операнд в стеке трактуется как 32-разрядный.
Таблица3.1. Значения атрибутов размеров операнда и адреса
BmtD
66h
67h
Размер операнда
Размер адреса
0
-
-
16
16
0
-
+
16
32
0
+
-
32
16
0
+
+
32
32
1
-
-
32
32
1
-
+
32
16
1
+
-
16
32
1
+
+
16
16
В качестве примера префикса, который при формировании машинной команды вставляет сам ассемблер, можно привести префикс со значением OFFh. Он называется префиксом смены алфавита и извещает о том, что поле кода операции в данной команде двухбайтовое.
Код операции
Код операции — обязательный элемент, описывающий операцию, выполняемую командой. Код операции может занимать от одного до трех байт. Для некоторых машинных команд часть битов кода операции может находиться в байте mod r/m.
Многим командам соответствует несколько кодов операций, каждый из которых определяет нюансы выполнения операции. Отметим, что поле кода операции не имеет однозначной структуры (см. рис. 3.1). В зависимости от конкретных команд, не обязательно разных с точки зрения языка ассемблера, оно может иметь в своем составе от одного до трех элементов, назначение которых описано в табл. 3.2. Один из этих трех элементов является непосредственно кодом операции или ее частью, остальные уточняют детали операции. Такое строение поля кода операции усложняет, в частности, процесс дизассемблирования. Для определения размера и границ очередной команды необходимо полностью проанализировать ее поле кода операции.
Таблица 3.2. Назначение дополнительных битов поля кода операции
Поле
Количество
Назначение
d
1
Определяет направление передачи данных: 0 — передача данных из регистра reg в память (или регистр), адресуемую полем т/т; 1 — передача данных из памяти (или регистра), адресуемой нолем r/m, в регистр reg. При наличии байта sib адрес операнда в памяти формируется с учетом содержимого этого байта
s
1
Задает необходимость расширения 8-разрядного непосредственного операнда до 16 или 32 бита. Старшие биты при этом заполняются значением старшего (знакового) бита исходного 8-разрядного операнда
W
1
Определяет размер данных, которыми оперирует команда: байт, слово, двойное слово: 0 — 8 битов; 1 — 16 битов для 16-разряд- ного размера операндов или 32 бита для 32-разрядного размера операндов
reg
3
Определяет регистр, используемый в команде. Значение поля зависит от ноля w, в том числе если поле w отсутствует (см. следующий подраздел)
Последующие поля машинной команды определяют характеристики и местоположение операндов, участвующих в операции, и особенности их использования (см. далее).
Байт режима адресации mod r/m
Байт режима адресации mod r/m, иногда называемый постбайтом, несет информацию об операндах и режиме адресации. Большинство команд процессора Intel — двухоперандные. Операнды могут находиться в памяти, а также в одном или двух регистрах. Архитектура IA-32 не допускает, чтобы оба операнда команды находились в памяти. Если операнд находится в памяти, то байт mod r/m определяет компоненты (смещение, базовый и индексный регистры), используемые для вычисления его эффективного адреса (см. главу 2). Байт mod r/m состоит из трех полей (см. рис. 3.1).
総Поле mod (два бита) определяет способ адресации и количество байтов, занимаемых в команде адресом операнда (поле смещения в команде). Поле mod используется совместно с полем r/m, которое определяет способ модификации адреса операнда полем смещения в команде. Поле mod в комбинации с полем r/m образует 32 возможных значения, обозначающих один из восьми регистров и 24 режима адресации. К примеру, если mod = 00, то поле смещения в команде отсутствует и адрес операнда определяется содержимым базового и/или индексного регистра. Какие именно регистры потребуются для вычисления эффективного адреса, определяется значением этого байта. Если mod = 01, то поле смещения в команде присутствует, занимает один байт и модифицируется содержимым базового и/или индексного регистра. Если mod = 10, то поле смещения в команде присутствует, занимает два или четыре байта (в зависимости от значения, действующего по умолчанию или определяемого префиксом размера адреса)
и модифицируется содержимым базового и/или индексного регистра. Если mod u 11, то операндов в памяти нет — они находятся в регистрах. Это же значение байта mod используется в случае, когда команда работает с непосредственным операндом.
* Поле reg (3 бита) определяет либо регистр (табл. 3.3 и 3.4), находящийся в команде на месте второго операнда, либо возможное расширение кода операции (давая в совокупности размер поля КОП в 11 битов).
я Поле r/m используется совместно с полем mod и определяет либо регистр, находящийся в команде на месте первого операнда (если mod = 11), либо базовые и индексные регистры, применяемые для вычисления эффективного адреса (совместно с полем смещения в команде).
Таблица 3.3. Значения кодов в поле reg (полеwприсутствует в команде)
Поле reg
w = 0
w= 1
000
AL
AX/EAX
001
CL
CX/ECX
010
DL
DX/EDX
Oil
BL
BX/EBX
100
AH
SP/ESP
101
CH
BP/EBP
no
DH
SI/ESI
111
вн
DI/EDI
Таблица З.4. Значения кодов в поле reg (полеwотсутствует)
Поле reg
16-разрядные операции
32-разрядные операции
000
АХ
ЕАХ
001
сх
ЕСХ
010
DX
EDX
он
ВХ
ЕВХ
100
SP
ESP
101
ВР
ЕВР
но
SI
ESI.
111
DI
EDI
В таблицах нет содержимого поля reg для 16-разрядныхрегистров в 32-разряд- ных операциях, так как в архитектуре Intel отдельно использовать старшую половину 32-разрядного регистра невозможно.
В архитектуре Intel один из операндов обязательно находится в регистре, и он может быть первым или вторым. Расположение первого и второго операндов в формате команды фиксировано. Но, например, команда MOV может выполнять пересылку как из регистра в память, так и из памяти в регистр. В машинном представлении
3 Зак. 256
это одна и та же команда. В ее поле reg будет содержаться код регистра (см. табл. 3.3 и 3.4), а в поле r/m — код режима адресации (см. далее). Эти две команды будут различаться только одним битом d, который определяет направление передачи. Если в команде участвуют два регистра, то в этом случае вступает в силу правило: поле reg определяет второй операнд, а поле r/m — первый. Если команда mov работает с ячейкой памяти, то в исходном тексте программы могут быть следующие варианты записи этой команды:
mov abll,ax пересылка содержимого ах в ячейку памяти abll
или
mov ax,abll ;пересылка содержимого ячейки памяти abll в ах
В машинном представлении эти две команды будут выглядеть одинаково, за исключением бита d:
- для команды MOV abll,ax бит d = 0;
娥для команды MOV ax,abll бит d = 1.
Наиболее сложными для декодирования являются команды с операндом в памяти. Фирма Intel сопровождает описание системы команд специальными таблицами, облегчающими интерпретацию содержимого байта mod r/m (табл. 3.5 и 3.6). С их помощью можно довольно легко восстановить компоненты, из которых формировался адрес операнда, и, в конечном итоге, восстановить соответствующую команду ассемблера для данной машинной команды.
Таблица 3.5. Значения байта mod r/m (16-разряднаяадресация)
r8
AL
CL
DL
BL
AH
CH
DH
BH
Г16
AX
CX
DX
BX
SP
BP
SI
DI
r32
EAX
ECX
EDX
EBX
ESP
EBP
ESI
EDI
/цифра (код операции)
0
1
2
3
4
5
6
7
reg
000
001
010
on
100
101
ПО
111
Эффективный адрес
mod
r/m
Шестнадцатеричныезначения mod r/m
[BX+SI]
00
000
00
08
10
18
20
28
30
38
[BX+DI]
00
001
01
09
11
19
21
29
31
39
[BP+SI]
00
010
02
Oa
12
la
22
2a
32
За
[BP+DI]
00
Oil
03
Ob
13
1b
23
2b
33
3b
[SI]
00
100
04
Oc
14
1с
24
2c
34
3c
[DI]
00
101
05
Od
15
Id
25
2d
35
3d
смещ 16
00
ПО
06
Oe
16
1е
26
2e
36
3e
[ВХ]
00
111
07
Of
17
If
27
2f
37
3f
[BX+SI]+cMem_8
01
000
40
48
50
58
60
68
70
78
[ВХ+Э1]+смещ_8
01
001
41
49
51
59
61
69
71
79
[BP+SI]十 смещ_8
01
010
42
4a
52
5a
62
6a
72
7a
[ВР+Э1]+смещ_8
01
ОН
43
4b
53
5b
63
6b
73
7b
[81]+смещ_8
01
100
44
4c
54
5c
64
6c
74
7c
Эффективный адрес
mod
r/m
Шестнадцатеричные значения mod г/т
[DI]+cMeui_8
01
101
45
4d
55
5d
65
6d
75
7d
[ВР]+смещ_8
01
110
46
4е
56
5e
66
6e
76
7e
[ВХ]+смещ_8
01
111
47
4f
57
5f
67
6f
77
7f
[ВХ+81]+смещ_16
10
000
80
88
90
98
aO
a8
bO
b8
[ВХ+Э1]+смещ_16
10
001
81
89
91
99
al
a9
bl
b9
[ВР+51]+смещ_16
10
010
82
8а
92
9a
a2
aa
b2
ba
[ВР+01]+смещ_16
10
011
83
8Ь
93
9b
a3
ab
b3
bb
[51]+смещ_16
10
100
84
8с
94
9c
a4
ac
b4
be
[В1]+смещ_16
10
101
85
8d
95
9d
a5
ad
b5
bd
[ВР]+смещ_16
10
110
86
8е
96
9e
a6
ae
b6
be
[ВХ]+смещ_16
10
111
87
8f
97
9f
a7
af
77
bf
EAX/AX/AL
11
000 .
сО
с8
dO
d8
eO
e8
ГО
住
ECX/CX/CL
11
001
cl
с9
dl
d9
el
e9
fl
19
EDX/DX/DL
11
010
с2
са
d2
Da
e2
ea
f2
fa
EBX/BX/BL
И
011
сЗ
cb
d3
Db
e3
eb
G
fb
ESP/SP/AH
11
100
с4
сс
d4
DC
e4
ec
f4
fc
ЕВР/ВР/СН
11
101
с5
cd
d5
Dd
e5
ed
fS
fd
ESVSI/DH
11
по
сб
се
d6
De
e6
ее
f6
fe
EDI/DI/BH
11
111
с7
cf
d7
Df
е7
ef
f7
ff
Таблица З.6. Значения байта mod r/m (32-разрядная адресация)
r32
ЕАХ
ЕСХ
EDX
ЕВХ
ESP
ЕВР
ESI
EDI
/цифра (код операции)
0
1
2
3
4
5
6
7
reg
000
001
010
011
100
101
ПО
111
Эффективный адрес
mod
r/m
Шестнадцатеричныезначенияmodr/m1
[ЕАХ]
00
ооо
00
08
10
18
20
28
30
38
[ЕСХ]
00
001
01
09
11
19
21
29
31
39
[EDX]
00
010
02
Оа
12
1а
22
2а
32
За
[ЕВХ]
00
Oil
03
Ob
13
ib
23
2b
33
3b
[sib]
00
100
04
Ос
14
1с
24
2с
34
Зс
Смещ 32
00
101
05
Od
15
Id
25
2d
35
3d
[ESI]
00
по
06
Ое
16
1е
26
2е
36
Зе
[EDI]
00
111
07
Of
17
If
27
2f
37
3f
[ЕАХ]+смещ_8
01
000
40
48
50
58
60
68
70
78
[ЕСХ]+смещ_8
01
001
41
49
51
59
61
69
71
79
[Е0Х]+смещ_8
01
010
42
4а
52
5а
62
6а
72
7а
продолжение身
Таблица 3.6 (продолжение)
Эффективный адрес
mod
r/m
Шестнадцатеричные значения mod r/m1
[ЕВХ]+смещ_8
01
011
43
4Ь
53
5b
63
6b
73
7b
[sibj+смещ 8
01
100
44
4с
54
5c
64
6c
74
7c
[ЕВР]+смещ_8
01
101
45
4d
55
5d
65
6d
75
7d
[Е81]+смещ_8
01
по
46
4е
56
5e
66
6e
76
7e
[EDI]+cMeiu_8
01
111
47
4f .
57
5f
67
6f
77
7f
[ЕАХ]+смещ_32
10
000
80
88
90
98
aO
a8
bO
b8
[ЕСХ]+смещ_32
10
001
81
89
91
99
al
a9
bl
b9
[ЕПХ]+смещ_32
10
010
82
8а
92
9a
a2
aa
b2
ba
[ЕВХ]+смещ_32
10
011
83
8b
93
9b
a3
ab
ЬЗ
bb
[SIB]
10
100
84
8с
94
9c
a4
ac
Ь4
be
[ЕВР]+смещ_32
10
101
85
8d
95
9d
a5
ad
Ь5
bd
[Е81]+смещ_32
10
по
86
8е
96
9e
a6
ae
b6
be
[Ей1]+смещ_32
10
111
87
8f
97
9f
a7
af
77
bf
EAX/AX/AL
11
000
сО
С8
dO
d8
eO
e8
fi)
re
ECX/CX/CL
11
001
с!
С9
dl
d9
el
e9
fl
f9
EDX/DX/DL
11
010
с2
са
d2
Da
e2
ea
f2
fa
EBX/BX/BL
11
011
сЗ
cb
d3
Db
e3
eb
О
ft
ESP/SP/AH
11
100
с4
со
d4
DC
e4
ec
f4
fc
ЕВР/ВР/СН
11
101
с5
cd
d5
Dd
e5
ed
f5
fd
ESI/SI/DH
И
110
сб
се
d6
De
e6
ее
f6
fe
EDI/DI/BH
11
111
с7
cf
d7
Df
e7
ef
f7
ff
Рассмотрим пример использования данных таблиц при значении байта mod r/m равном 87h. Для восстановления местонахождения операндов данной машинной команды следует найти это значение в одной из таблиц (какой таблицей воспользоваться, зависит от текущего режима адресации) и по первому столбцу строки, содержащей код 87h, определить местонахождение первого операнда. В нашем случае адрес операнда формируется из содержимого регистра ВХ и 16-разрядногосме- щения, значение которого следует искать в следующих за байтом mod r/m двух байтах. Второй столбец той же строки содержит значение поля mod. Третье поле байта mod r/m можнонайти, переместившись вверх по столбцу, содержащему значение 87h, до пересечения со строкой reg или /цифра. При этом будет выбрано значение, идентифицирующее один из регистров или продолжение кода операции. Что именно — определяется либо самим кодом операции, либо значением бита w в сочетании с текущей разрядностью адреса (см. ранее).
При использовании 32-разрядной адресации содержимое байта mod r/m трактуется несколько иначе из-за наличия в формате машинной команды байта sib (см. подраздел «Байт масштаба, индекса и базы»).
Некоторые машинные команды могут работать с сегментными регистрами. Далее приведены соглашения по кодированию сегментных регистров. В дальнейшем изложении будем различать два набора регистров:
幾 sreg86 — сегментные регистры, существовавшие в архитектуре процессоров i8086/88и i80286;
И sreg386 — сегментные регистры архитектуры процессоров i80386 и выше.
Различие наборов состоит в том, что кодируются они различным количеством битов: sreg86 — двумя битами (табл. 3.7), a sreg386 — тремя (табл. 3.8).
Таблица 3.7. Кодировка сегментных регистров в наборе sreg86
Код в поле sreg86
Сегментный регистр
00
ES
01
CS
10
SS
11
DS
Таблица 3.8. Кодировка сегментных регистров в наборе sreg386
Код в поле sreg386
Сегментный регистр
000
ES
001
CS
010
SS
Oil
DS
100
FS
101 GS
Одна из целочисленных команд — команда MOV — может оперировать системными регистрами. Кодировка этих регистров приведена в табл. 3.9.
Таблица 3.9. Кодировка системных регистров в команде MOV
Код в поле creg
Регистры управления
Регистры отладки
000
CRO
DRO
001
—
DR1
010
CR2
DR2
Oil
CR3
DR3
100
CR4
—
101
—
—
но
—
DR6
111
-
DR7
Байт масштаба, индекса и базы
Байт масштаба, индекса и базы (Scale-Index-Base — sib) используется для расширения возможностей адресации операндов. На наличие байта sib в машинной команде указывает сочетание одного из значений 01 или 10 поля mod и значения поля r/m = 100. Байт sib состоит из трех элементов (табл. 3.10).
綫 В поле масштаба (ss) размещается масштабный множитель для индексного компонента index, занимающего следующие три бита байта sib. В поле ss может содержаться значение 1, 2, 4 или 8. При вычислении эффективного адреса на это значение будет умножаться содержимое индексного регистра. Более подробно, с практической точки зрения, эта расширенная возможность индексации рассматривается при обсуждении массивов в главе 13.
嚣旨 Поле index позволяет хранить номер индексного регистра, содержимое которого применяется для вычисления эффективного адреса операнда.
и Поле base требуется для хранения номера базового регистра, содержимое которого также применяется для вычисления эффективного адреса операнда. В качестве базового и индексного регистров могут использоваться большинство регистров общего назначения.
Таблица 3.10. Значения байта sib (32-разрядная адресация)
г32
EAX
ЕСХ
EDX
ЕВХ
ESP
*
ESI
EDI
База (base)
000
001
010
Oil
100
101
110
111
Масштабирование индексного регистра
Масштабный множитель (ss)
Индекс (index)
Шестнадцатеричные значения sib
[ЕАХ]
00
000
00
01
02
03
04
05
06
07
[ЕСХ]
00
001
08
09
OA
OB
ОС
OD
OE
OF
[EDX]
00
010
10
11
12
13
14
15
16
17
[ЕВХ]
00
Oil
18
19
1A
IB
1C
ID
IE
IF
—
00
100
20
21
22
23
24
25
26
27
[ЕВР]
00
101
28
29
2A
2B
2C
2D
2E
2F
[ESI]
00
ПО
30
31
32
33
34
35
36
37
[EDI]
00
111
38
39
ЗА
3B
3C
3D
3E
3F
[ЕАХ*2]
01
000
40
41
42
43
44
45
46
47
[ЕСХ*2]
01
001
48
49
4A
4B
4C
4D
4E
4F
[EDX*2]
01
010
50
51
52
53
54
55
56
57
[ЕВХ*2]
01
Oil
58
59
5A
5B
5C
5D
5E
5F
—
01
100
60
61
62
63
64
65
66
67
[ЕВР*2]
01
101
68
69
6A
6B
6C
6D
6E
6F
[ESI*2]
01
110
70
71
72
73
74
75
76
77
[EDI*2]
01
111
78
79
7A
7B
7C
7D
7E
7F
[ЕАХ*4]
10
000
80
81
82
83
84
85
86
87
Масштабирование индексного регистра
Масштабный множитель (SS)
Индекс (index)
Шестнадцатеричные значения sib
[ЕСХ*4]
10
001
88
89
8А
8В
8С
8D
8Е
8F
[EDX*4]
10
010
90
91
92
93
94
95
96
97
[ЕВХ*4]
10
011
98
99
9А
9В
9С
9D
9Е
9F
—
10
100
АО
А1
А2
A3
А4
А5
А6
А7
[ЕВР*4]
10
101
А8
А9
АА
АВ
АС
AD
АЕ
AF
[ES1*4]
10
ПО
ВО
В1
В2
ВЗ
В4
В5
В6
В7
[EDI*4]
10
111
В8
В9
ВА
ВВ
ВС
BD
BE
BF
[ЕАХ*8]
И
000
СО
С1
С2
СЗ
С4
С5
С6
С7
[ЕСХ*8]
И
001
С8
С9
СА
СВ
сс
CD
СЕ
CF
[EDX*8]
И
010
DO
D1
D2
D3
D4
D5
D6
D7
[ЕВХ*8]
и
on
D8
D9
DA
DB
DC
DD
DE
DF
—
11
100
ЕО
Е1
Е2
ЕЗ
Е4
Е5
Е6
Е7
[ЕВР*8]
и
101
Е8
Е9
ЕА
ЕВ
ЕС
ED
ЕЕ
EF
[ESI*8]
11
ПО
FO
F1
F2
F3
F4
F5
F6
F7
[EDI*8]
11
111
F8
F9
FA
FB
FC
FD
FE
FF
По значению байта sib легко восстановить компоненты машинных команд, содержащие адрес операнда с учетом его масштабирования.
Одно значение базового регистра (base) в табл. 3.10 замещено символом звездочки (*). Это означает наличие в команде адреса смещ_32 без базы, если mod равно 00, и [ЕВР] — в противном случае. Такой подход обеспечивает следующие режимы адресации:
и смец[_32[индекс], если mod=00;.
смещ_8[еЬр] [индекс], если mod=01;
II смещ_32[еЬр][индекс], если mod=10.
Поля смещения и непосредственного операнда
Поле смещения в команде — это 8-, 16- или 32-разрядное целое число со знаком, представляющее собой полностью или частично (с учетом приведенных ранее рассуждений) значение эффективного адреса операнда.
Поле непосредственного операнда — необязательное поле, представляющее собой 8_, 16- или 32-разрядный непосредственный операнд. Наличие этого поля, конечно, отражается на значении байта mod r/m.
Столь подробное обсуждение различных полей машинной команды, в том числе с использованием всех приведенных ранее таблиц, имеет целью показать правила формирования операндов машинных команд. При рассмотрении синтаксиса ассемблера (глава 5) на основе этого материала будут обсуждаться правила записи операндов команд ассемблера.
Функциональнаяклассификация
машинных команд
В начале главычто система команд последнего на сегодняшний день
процессора Pentium IV архитектуры IA-32 содержит более 300 машинных команд. Весь набор машинных команд можно разбить на четыре группы (рис. 3.2).
Рис. 3.2. Машинные команды процессора Intel (Pentium IV)
В пределах каждой из этих больших групп, исходя из функционального назначения отдельных команд, можно провести дальнейшее разбиение на более мелкие подгруппы. Такой подход позволяет достичь нескольких целей:
оценить возможности процессора по обработке данных;
рассмотреть совокупность команд процессора архитектуры IA-32 как иерархическую и самодостаточную систему;
№ осмысленно изучать отдельные машинные команды в контексте остальных.
В главах 7-10 и 12 будут рассмотрены команды основного процессора (целочисленные), поэтому здесь приведем их классификацию по функциональному признаку (рис. 3.3).
Рис. 3.3. Функциональная классификация целочисленных машинных команд
Функциональная классификация команд остальных групп будет
на в материале соответствующих глав. Информацию о каждой команде процессора архитектуры IA-32 (вплоть до Pentium IV) можно найти в приложении, которое удобно использовать в ходе работы над программными проектами. Стоит обратить внимание на то, что приложение состоит из четырех — это соответствует разбиению команд процессора на функциональные группы (рис. 3.2).
Итоги
扌- Система машинных команд — важнейшая часть архитектуры компьютера, определяющая возможности его программирования.
' - Для работы процессора достаточно программы в двоичных кодах, но такое прямое программирование на практике не используется. Язык ассемблера — символический аналог машинного языка. Преобразование команд ассемблера в соответствующие машинные команды производит программа-транслятор — ассемблер. Дальнейшая интерпретация машинных команд в конкретные сигналы электронных схем осуществляется с помощью блока микропрограммного управления, входящего в состав процессора.
• Существует взаимно однозначное соответствие машинных команд и команд ассемблера.
№ Кодирование машинных команд производится в соответствии с одним из возможных форматов. Команды процессоров архитектуры IA-32 считаются сложными, так как в основу концепции процессоров Intelположен принцип совместимости — программы, разработанные для более ранних моделей процессоров, должны выполняться на всех последующих.
襄 Структура команд процессора позволяет обеспечить большую гибкость при обработке операндов и разнообразие режимов адресации.
缠; Большинство команд ассемблера имеют несколько возможных вариантов сочетания операндов. Для каждого возможного сочетания операндов имеется своя машинная команда со своим значением поля кода операции.
緻 Машинную команду формируют несколько полей, из которых обязательным является только одно — поле кода операции.
Система команд архитектуры IA-32 является иерархической и самодостаточной. Все команды делятся на четыре группы, в пределах каждой из которых выделяется довольно большое количество функциональных подгрупп.
Глава 4
Системы счисления
> Позиционные и непозиционные системы счисления
Двоичная система счисления
Шестнадцатеричная система счисления
■ Десятичная система счисления
Перевод чисел из одной системы счисления в другую
Понимание порядка представления чисел в двоичной, десятичной и шестнадцатеричной системах счисления является одним из необходимых условий успешного программирования на ассемблере. Не менее важно уметь использовать правила конвертирования числовых данных между этими системами счисления. Причинами этого являются, с одной стороны, двоичная организация современных ЭВМ и, с другой стороны, более привычная для человека работа с числовой информацией на основе десятичной системы счисления.
Как известно, системой счисления называется совокупность правил записи чисел. Системы счисления подразделяются на позиционные и непозиционные. Как в позиционных, так и в непозиционных системах счисления используется определенный набор символов — цифр, последовательное сочетание которых образует число. Непозиционные системы счисления появились раньше позиционных. Они характеризуются тем, что в них символы, обозначающие то или иное число (то есть цифры), не меняют своего значения в зависимости от местоположения в записи этого числа. Классическим примером такой системы счисления являетсяргдл/- ская. В ней для записи чисел используются буквы латинского алфавита. При этом буква I означает единицу, V — пять, X — десять, L — пятьдесят, С — сто, D — пятьсот, М — тысячу. Для получения количественного эквивалента числа в римской системе счисления необходимо просто просуммировать количественные эквиваленты входящих в него цифр. Исключение из этого правила составляет случай, когда младшая цифра находится перед старшей, — в этом случае нужно не складывать, а вычитать число вхождений этой младшей цифры. Например:
DLXXVII = 500 + 50 + 10 + 10 + 5 + 1 + 1 = 577.
Другой пример:
CDXXIX = 500 - 100 + 10 + 10 - 1 + 10 = 429.
В позиционной системе счисления количество символов в наборе равно основанию системы счисления. Место каждой цифры в числе называется позицией. Номер позиции символа (за вычетом единицы) в числе называется разрядом. Разряд 0 называется младшим разрядом. Каждой цифре соответствует определенный количественный эквивалент. Введем обозначение — запись будет означать количественный эквивалент числа А, состоящего из п цифр ak (где k = 0, ..., п - 1) в системе счисления с основанием р. Это число можно представить в виде последовательности цифр:
A - an an 2... a.a
При этом, конечно, всегда выполняется неравенство ak < р.
В общем случае количественный эквивалент некоторого положительного числа А в позиционной системе счисления можно представить выражением:
Ag = a“_jpz + a”_2•炉++ «Г + a.'P^(4.1)
где р — основание системы счисления (некоторое целое положительное число), а — цифра данной системы счисления, п — номер старшего разряда числа.
Для получения количественного эквивалента числа в некоторой позиционной системе счисления необходимо сложить произведения количественных значений цифр на степени основания, показатели которых равны номерам разрядов (обратите внимание на то, что нумерация разрядов начинается с нуля).
После такого формального введения можно приступить к обсуждению некоторых позиционных систем счисления, наиболее часто используемых при разработке программ на ассемблере.
Двоичная система счисления
Набор цифр для двоичной системы счисления — {°’ i}‘ основание степени (р) — 2.
Количественный эквивалент некоторого целого и-значного двоичного числа вычисляется согласно формуле (4Л):
A - « ! • 2n-1 + a 2 • 2"2 + … + a • 21 +V 2°.(4.2)
.
Как мы уже отмечали, наличие этой системы счисления обусловлено тем, что компьютер построен на логических схемах, имеющих в своем элементарном виде только два состояния — включено и выключено. Производить счет в двоичной системе просто для компьютера, но сложно для человека. Например, рассмотрим двоичное число 10100111.
Вычислим десятичный эквивалент этого двоичного числа. Согласно формуле
это будет величина, равная следующей сумме:
. Т + 0 • 26 + 1 • 25 + 0 • 24 + 0 • 23 + 1 • 22 + 1 • 21 + 1 • 2°.
Посчитайте сами, сколько получится.
Сложение и вычитание двоичных чисел (рис. 4.1) выполняется так же, как и в других позиционных системах счисления, например десятичной. Точно так же выполняется заем (перенос) единицы из старшего разряда (в старший разряд).
переносперенос1 1 1заем
перенос
перенос
11010010011
11001100001100110000100101110001001011100000111011011
1100110000
1100110000
10010111000
10010111000
Рис.Сложениедвоичных чисел
Приведем степени двойки (табл.
ТаблицаСтепени двойки
Степень
Два в указанной степени
1
2
2
4
3
8
4
16
5
32
6
64
7
128
8
256
9
512
10
1024
11
2048
12
4096
Шестнадцатеричная система счисления
система счисления имеет набор цифр9,В, С, D,
F} и основание степени (р)— 16.
Количественный эквивалент некоторого целого числа вычисляется согласно формуле
= « , • 16,г14- а 只〔б"-+…+ 仪.16' + « • 16°.
(16)п-1п 210
К примеру, количественный эквивалент шестнадцатеричного числа f45ed23c равен:
15 •+ 4 •+ 5 •+ 14 •+ 13 •+ 2 •+ 3 •+ 12 • 16°.
Посчитайте сами, сколько получится. Приведем соответствие двоичных чисел и их десятичных иэквивалентов (табл. 4.2).
Таблица 4.2.цифры
Десятичное число
Двоичная тетрада
Шестнадцатеричное число
0
0000
0
1
0001
1
2
0010
2
Десятичное число
Двоичная тетрада
Шестнадцатеричное число
3
ООП
3
4
0100
4
5
0101
5
6
ОНО
6
7
0111
7
8
1000
8
9
1001
9
10
1010
А, а
11
1011
в,ь
12
1100
С, с
13
1101
D,d
14
1110
Е, е
15
1111
F.f
16
10000
10
Поначалу запомнить эти соотношения сложно, поэтому полезно иметь под руками некоторую справочную информацию. Табл. 4.2 содержит представления десятичных чисел из диапазона 0-16 в двоичной исистемах
счисления. Ее удобно использовать для взаимного преобразования чисел в рассматриваемых нами трех системах счисления. Шестнадцатеричная система счисления при вычислениях несколько сложнее, чем двоичная, в частности, в том, что касается правил переносов в старшие разряды (заемов из старших разрядов). Главное здесь — помнить следующее равенство:
<1 +
Эти переходы очень важны при выполнении сложения и вычитания чисел (рис. 4.2).
1 1
перенос
11
заем
Е F 1 5
1 слагаемое
BCD8
уменьшаемое
С 1 Е 8
2 слагаемое
5EF4
вычитаемое
1 В 0 F D
результат
5 D E 4
результат
Рис. 4.2. Сложение и вычитаниечисел
Десятичная система счисления
Десятичная система счисления наиболее известна, так как она постоянно используется нами в повседневной жизни. Данная система счисления имеет набор цифр {О, 1, 2, 3, 4, 5, 6, 7, 8, 9} и основание степени (р)- 10.
Количественный эквивалент некоторого целогодесятичного числа
вычисляется согласно формуле (4.1):
A(io) - % • 10"Т +%• ю-2 + ... + 4•g+ «0- 10°.
К примеру, значение числаА<Ю)= 4523 равно:
д^оз + з^о^ + гио^зло0.
Перевод чисел из одной системы счисления в другую
Одного знания о существовании разных систем счисления мало. Для того чтобы в полной мере использовать их в своей практической работе при программировании на ассемблере, необходимо научиться выполнять взаимное преобразование чисел между тремя системами счисления. Этим мы и займемся в дальнейшем. Кроме того, дополнительно будут рассмотрены некоторые особенности процессоров Intel при работе с числами со знаком.
Перевод в десятичную систему счисления
Перевод в десятичную систему счисления является самым простым. Обычно его производят с помощью так называемого алгоритма замещения, суть которого заключается в следующем: сначала в десятичную систему счисления переводится основание степени р, а затем — цифры исходного числа. Результаты подставляются в формулу (4.1). Полученная сумма и будет искомым результатом. Неявно при обсуждении двоичной и шестнадцатеричной систем счисления мы производили как раз такое преобразование.
Перевод в двоичную систему счисления
Перевод из десятичной системы счисления
Перевод числа в двоичную систему счисления из десятичной выполняется по описанному далее алгоритму.
Разделить десятичное число А на 2. Запомнить частное q и остаток а.
Если в результате шага 1 частное q не равно 0, то принять его за новое делимое и отметить остаток а, который будет очередной значащей цифрой числа, и вернуться к шагу i, на котором в качестве делимого (десятичного числа) участвует полученное на шаге 2 частное.
Если в результате шага 1 частное q равно 0, алгоритм прекращается. Выписать остатки в порядке, обратном их получению. Получится двоичный эквивалент исходного числа.
К примеру, перевод в двоичную систему счисления числа 247^ иллюстрирует рис. 4.3. Порядок обхода остатков для получения результата (111101112) показан стрелками.
Перевод из шестнадцатеричной системы счисления
Перевод из шестнадцатеричной системы счисления мы уже обсуждали ранее. Суть его заключается в последовательной замене шестнадцатеричных цифр соответствующими двоичными тетрадами, согласно табл. 4.2. К примеру, двоичное чМсло, соответствующее числу 64d5]б, равно 11100100 110101012.:
247 [2_
246 123 [2_
(Т)122 61 |2_.
①旦30回
® J0_ 15 ②
© 14 7 (_2.
① 6 3 |2_
®_^®[2_
®0
Рис. 4.3. Перевод в двоичную систему счисления
Перевод в шестнадцатеричную систему счисления
Перевод из десятичной системы счисления
Общая идея алгоритма перевода из десятичной системы счисления в шестнадцатеричную аналогична рассмотренной ранее в алгоритме перевода в двоичную систему счисления из десятичной.
Разделить десятичное число А на 16. Запомнить частное q и остаток а.
Если в результате шага 1 частное q не равно 0, то принять его за новое делимое, записать остаток и вернуться к шагу
Если частное q равно 0, прекратить работу алгоритма. Выписать остатки в порядке, обратном их получению. Получитсяэквивалент
исходного десятичного числа.
К примеру, перевод в шестнадцатеричную систему счисления числа 32 иллюстрирует рис. 4.4. Порядок обхода остатков для получения результата
Переводиздвоичнойсистемысчисления
Идея алгоритма состоит в том, что двоичное число разбивается на тетрады начиная с младшего разряда. Далее каждая тетрада приводится к соответствующему числу, согласно табл. 4.2.
К примеру, пусть требуется перевести всистему счисле
ния следующее число:
111001011010111101011000110110001111010101011012.
Разобьем его на тетрады:
0111 0010 1101 0111 1010 11000110 11000111 1010 1010 1101.
По тетрадам приводим последовательности нулей и единиц к шестнадцатеричному представлению:
72d7ac6c7aad.
То есть в результате преобразования мы получимчисло
72d7ac6c7aad1(J.
Перевод дробных чисел
Программист должен уметь выполнять перевод в различные системы счисления не только целых чисел, но и дробных чисел.важно это при программи
ровании алгоритмов, использующих операции с плавающей точкой. Без владения в полной мере знаниями о том, как представляются дробные числа в памяти компьютера и в регистрах сопроцессора, вам вряд ли удастся овладеть программированием на ассемблере в полной мере. Давайте разберемся с наиболее часто используемыми на практике способами перевода дробных чисел. Для этого формулу преобразуем к следующему виду:
A. = a C1 + a「,斗...
... + -p1 + aQ*pa+ -р~л + a 2 • p 2 + ... + a m • p~m.(4.3)
Рассмотрим операции перевода чиселпримерах.
Пример 1
Пусть требуется перевести в десятичное представление следующее дробное число, заданное в двоичной системе счисления:
110100,010010112.
Для перевода используем формулу (4.3):
+1 •+ 0 •+ 0 • 2° + 0 •
+1•+0•+0•+1•+0•+1•+1•
Для вас не составит труда вычислить целую часть десятичной дроби:
. + 1 . + 0 • + 1 • + 0 • + 0 • 2°.
Для вычисления остальной части выражения удобно использовать табл. 4.3.
Таблица 4.3. Значения отрицательных степеней по основанию числа 2
Отрицательная степень
Два в указанной степени
1
0,5
2
0,25
3
0,125
4
0,0625
5
0,03125
6
0,015625
.7
0,0078125
Вы можете сами продолжить табл. 4.3 значениями отрицательных степеней по основанию числа 2 и в конце концов подсчитать результат перевода в десятичное представление значения
110100,010010112.
Пример 2
Пусть требуется перевести в десятичное представление следующую дробь, заданную в шестнадцатеричной системе счисления:
Idf2,ale416.
Вновь используем формулу (4.3):
Idf2,ale416= 1 • 163+ 13 • 162+ 15 • 21 + 2 • 16" + 10 • 16“ + 1 • 16 2+
+ 14-163+ 4 • 16".
Для удобства вычисления дробной части приведем значения отрицательных степеней числа 16 (табл. 4.4).
Таблица 4.4. Значения отрицательных степеней по основанию числа 16
Отрицательная степень
Шестнадцать в указанной степени
1
0,0625
2
0,00390625
3
0,000244140625
4
0,0000:l 52587890625
5
0,00000095367431640625
6
0,000000059604644775390625
7
0,0000000037252902984619140625
Перевод из двоичной системы счисления в шестнадцатеричную и обратно выполняется, как обычно, на основе тетрад и трудности вызывать не должен.
Пример 3
Рассмотрим теперь проблему представления десятичных дробей в двоичной и шестнадцатеричной системах счисления:
Общий алгоритм перевода десятичной дроби в другую систему счисления можно представитьследующейпоследовательностьюшагов.
Выделить целую часть десятичной дроби и выполнить ее перевод в выбранную систему счисления по алгоритмам, рассмотренным ранее.
Выделить дробную часть и умножить ее на основание выбранной новой системы счисления.
В” полученной после умножения дробной части десятичной дроби выделить целую часть и принять ее в качестве значения первого после запятой разряда числа в новой системе счисления.
Если дробная часть значения, полученного после умножения, равна нулю, то прекратить процесс перевода. Процесс перевода можно прекратить также в случае, если достигнута необходимая точность вычисления. В противном случае вернуться к шагу 3.
Рассмотрим пример. Пусть требуется перевести в двоичную систему счисления десятичную дробь Ю8,40610.
Сначала переведем целую часть десятичной дроби Ю8,40610 в двоичную систему счисления (рис. 4.5).
1101100
108 |2
108 54 |2
-<0)5427 区
® 26 13 2
① 12 6 \2_
® 6. 3
@ 2
0
Рис. 4.5. Перевод целой части десятичного числа 108,406в двоичную систему счисления
Затем переведем дробную часть десятичного числа Ю8,40610 (рис. 4.6) по приведенному ранее алгоритму.
Результат перевода следующий:
Ю8,40610 =
При переводе дробного числа из десятичной системы счисления в шестнадцатеричную целесообразно предварительно перевести число в двоичную! систему, а затем двоичное представление разбить на тетрады отдельно до разделительной
Перевод чисел из одной системы счисления в другую
83
011001111
.406
<@ .992
<~чг^
<-4D .972
Рис. 4.6. Перевод дробной части числа 108,406в двоичную систему счисления
запятой и после запятой. Разбиение на тетрады двоичных разрядов целой части производят от запятой в сторону старших разрядов. Неполную старшую тетраду дополняют слева нулями. Разряды дробной части, напротив, разбивают на тетрады от запятой вправо к младшим разрядам. Если последняя тетрада неполная, то ее дополняют нулями справа. На рис. 4.7 показан процесс перевода того же дробного десятичного числа (Ю8,40610) в эквивалентное шестнадцатеричное представление.
108,406 = 110 I 1100, (0)110 1100,
6 G
01100111
01100111
e 7
1= 6с,678
8
Рис. 4.7. Пример перевода десятичного числа в шестнадцатеричную систему счисления
Перевод чисел со знаком
До сих пор предполагалось, что числа положительные. А как представляются в компьютере числа со знаком?
Положительные целые со знаком — это 0 и все положительные числа.
Отрицательные целые со знаком — это все числа, меньшие 0. Отличительным признаком числа со знаком является особая трактовка старшего бита поля, представляющего число. В качестве поля могут выступать байт, слово или двойное слово. Естественно, что физически этот бит ничем не отличается от других — все зависит от команды, работающей с данным полем. Если в ее алгоритме заложена возможность работы с целыми числами со знаком, то команда будет по-особому трактовать старший бит поля. Если старший бит равен 0, число считается положительным и его значение вычисляется по правилам, которые мы рассмотрели ранее. Если старший бит равен 1, число считается отрицательным, а это предполагает, что оно записано в так называемом дополнительном коде. Разберемся в том, что он собой представляет.
Дополнительный код некоторого отрицательного числа представляет собой результат инвертирования (замены 1 на 0 и наоборот) каждого бита двоичного числа, равного модулю" исходного отрицательного числа плюс единица. К примеру, рассмотрим десятичное число —185]0. Модуль данного числа в двоичном представлении равен 101110012.Сначала нужно дополнить это значение слева нулями до нужной размерности — байта, слова и т. д. В нашем случае дополнить нужно до слова, так как диапазон представления знаковых чисел в байте составляет -128...127. Следующее действие — получить двоичное дополнение. Для этого все разряды двоичного числа нужно инвертировать:
00000000101110012->111 111 11010001102.
Теперь прибавляем единицу:
11111111010001102+ 00000000000000012=
Результат преобразования равен 11111111010001112. Именно так и представляется число -18510 в компьютере.
При работе с числами со знаком от вас наверняка потребуется умение выполнять обратное действие — имея двоичное дополнение числа, определить значение его модуля. Для этого необходимо выполнить два действия.
Выполнить инвертирование битов двоичного дополнения.
К полученному двоичному числу прибавить двоичную единицу.
К примеру, определим модуль двоичного представления числа
^18510=Ш1Ш10Ю00111т
Сначала инвертируем биты:
ИНН 11010001112 т 00000000101 110002.
Добавляем двоичную единицу:
00000000101110002+ 00000000000000012 == |-185|.
Теперь мы готовы разговаривать с компьютером на его языке, состоящем из команд и данных. В следующей главе мы напишем первую программу на ассемблере, разбирая которую, мы сможем применить знания, полученные в этой и предыдущих главах.
Итоги
ж Системы счисления подразделяются на позиционные и непозиционные. Как в позиционных, так и в непозиционных системах счисления используется определенный набор символов — цифр, последовательное сочетание которых образует число.
и Специфика работы программиста предполагает хорошее владение счетом в трех системах счисления: двоичной, шестнадцатеричной и десятичной. При этом программист должен достаточно уверенно переводить числа из одной системы счисления в другую.
т Числа со знаком представляются в компьютере особым образом: положительные числа — в виде обычного двоичного числа, а отрицательные — в дополнительном коде.
Глава 5
Синтаксис ассемблера
Структура программы на ассемблере
Типы и структура предложений ассемблера
齡Понятие о метасинтаксических языках
Классификация лексем ассемблера
Описание простых операндов и операндов-выражений
Варианты расположения операндов команд ассемблера
Виды адресации операндов в памяти
Операторы ассемблера
解Стандартные директивы сегментации
Упрощенные директивы сегментации
験' Простые типы данных ассемблера (диапазоны значений)
Директивы описания простых типов данных
В предыдущих главах основное обсуждение было посвящено внутреннему устройству процессора, его принципам работы и программной модели. И это не случайность — чем более низкий уровень функционирования компьютера доступен пониманию программиста, тем легче и осмысленнее для него становится процесс изучения и дальнейшего программирования на языке ассемблера. Сам язык ассемблера пока обсуждался мало. В основном речь шла о нем как о символическом аналоге машинного языка. В связи с этим отмечалось, что программа, написанная на ассемблере, отражает основные особенности архитектуры процессора: организацию памяти, способы адресации операндов, правила использования регистров и т. д. Также говорилось, что необходимость учета подобных особенностей делает
ассемблер уникальным для каждого типа процессоров. Эта и следующие за ней главы будут посвящены изучению правил оформления и разработки программ на языке ассемблера с учетом влияния на эти правила архитектуры IA-32.
Синтаксис ассемблера
Программа на ассемблере представляет собой совокупность блоков памяти, называемых сегментами. Программа может состоять из одного или нескольких таких блоков-сегментов. Сегменты программы имеют определенное назначение, соответствующее типу сегментов: кода, данных и стека. Названия типов сегментов отражают их назначение. Деление программы на сегменты отражает сегментную организацию памяти процессоров Intel (архитектура IA-32). Каждый сегмент состоит из совокупности отдельных строк, в терминах теории компиляции называемых предложениями языка. Для языка ассемблера предложения, составляющие программу, могут представлять собой синтаксические конструкции четырех типов.
微 Команды (инструкции) представляют собой символические аналоги машинных команд. В процессе трансляции инструкции ассемблера преобразуются в соответствующие команды системы команд процессора.
翳 Макрокоманды — это оформляемые определенным образом предложения текста программы, замещаемые во время трансляции другими предложениями.
懇Директивы являются указанием транслятору ассемблера на выполнение некоторых действий. У директив нет аналогов в машинном представлении.
蕊 Комментарии содержат любые символы, в том числе и буквы русского алфавита. Комментарии игнорируются транслятором.
Для распознавания транслятором ассемблера этих предложений их нужно формировать по определенным синтаксическим правилам. Для формального описания синтаксиса языков программирования используются р^злтныеметасинтак- сические языки, которые представляют собой совокупность условных знаков, образующих нотацию метасинтаксического языка, и правил формирования из этих знаков однозначных описаний синтаксических конструкций целевого языка.
В учебных целях удобно использовать два метасинтаксических языка — синтаксические диаграммы и нормальныеформы Бэкуса-Наура. Оба этих языка, в конечном итоге, предоставляют одинаковый объем информации. Поэтому выбор конкретного языка может определяться исходя из того, что синтаксические диаграммы более наглядны, а расширенные формы Бэкуса-Наура более компактны. В учебнике будут использоваться оба способа.
На рис. 5.1, 5.2 и 5.3 показан порядок написания предложений ассемблера с помощью синтаксических диаграмм.
Как использовать синтаксические диаграммы? Очень просто: для этого нужно всего лишь найти и затем пройти путь от входа диаграммы (слева) к ее выходу (направо). Если такой путь существует, то предложение или конструкция являются синтаксически правильными. Если такого пути нет, значит, эту конструкцию компилятор не примет. Иногда на линиях в синтаксических диаграммах присутствуют стрелки. Они говорят о том, что необходимо обратить внимание на направление обхода, указываемое этими стрелками, так как среди путей могут быть
Тексткд^М 也гдрн 旦Тексткд^М 也гдрн 旦口口
Тексткд^М 也гдрн 旦
Тексткд^М 也гдрн 旦
口
口
Рис.Формат предложений ассемблера
Директива
I КомментарийI КомментарийКомментарийКомментарийЦОпёран^Т^
I Комментарий
I Комментарий
Комментарий
Комментарий
.
Ь|ОперанД п|—
Рис. 5.2. Формат директив
Рис. 5.3. Формат команд и макрокомандРис. 5.3. Формат команд и макрокоманд|..
Рис. 5.3. Формат команд и макрокоманд
Рис. 5.3. Формат команд и макрокоманд
I
I Имя
и такие, по которым можно идти справа налево. По сути, синтаксические диаграммы отражают логику работы транслятора при разборе входных предложений программы. Далее перечислены термины, представленные на рисунках.
* Имя метки — символьный идентификатор. Значением данного идентификатора является адрес первого байта предложения программы, которому он предшествует.
Префикс — символическое обозначение элемента машинной команды, предназначенного для изменения стандартного действия следующей за ним команды ассемблера (см. главу 3).
il Имя — идентификатор, отличающий данную директиву от других одноименных директив. В зависимости от конкретной директивы в результате обработки ассемблером этому имени могут быть присвоены определенные характеристики.
Код операции (КОП) и директива — это мнемонические обозначения соответствующей машинной команды, макрокоманды или директивы транслятора.
Операнды — части команды, макрокоманды или директивы ассемблера, обозначающие объекты, над которыми производятся действия. Операнды ассемблера описываются выражениями с числовыми и текстовыми константами, метками и идентификаторами переменных с использованием знаков операций и некоторых зарезервированных слов.
Другой способ описания синтаксиса языка — нормальные (расширенные) формы Бэкуса-Наура. С помощью форм Бэкуса-Наура целевой язык представляется в виде объектов трех типов.
и Основные символы языка, в теории компиляции называемые терминальными, — это имена операторов, регистров и т. п., то есть это те символьные объекты, из которых строится, в частности, исходный текст ассемблерной программы.
Имена конструкций языка, в теории называемые нетерминальными символами, обычно заключаются в угловые скобки <> или пишутся строчными буквами.
в Правила (формы) описывают порядок формирования конструкций, в том числе предложений, целевого языка.
Каждая форма состоит из трех частей — левой, правой и связки:
9 левая часть — всегда нетерминальный символ, который обозначает одну из конструкций языка;
связка — символ стрелки о, который можно трактовать как словосочетание «определяется как»;
и правая часть описывает один или несколько вариантов построения конструкции, определяемой левой частью.
Несколько форм Бэкуса-Наура могут быть связаны между собой по нетерминальным символам, то есть одна форма определяется через другую. Для построения конструкции целевого языка необходимо взять одну или несколько форм Бэ- куса-Наура, в каждой из которых выбрать нужный вариант для подстановки. В конечном итоге должна получиться конструкция (предложение) целевого языка, состоящая только из терминальных символов.
Для примера рассмотрим описание и использование форм Бэкуса-Наура для построения десятичных чисел со знаком. Вначале опишем эти формы (правила): <десятичное_знаковое_целое>=><число_без__знака> | +<число_без_знака> | <число__без_знака> <число_без~знака>=^<дес_цифра> | <чйсло_без_знакахдес_цйфра> 一 - <дес_цйфра>=>011|2|3|4|5|6|7|8|9~一
Здесь:
機 <десятичное_знаковое_целое>,<число_без_знака>,<дес_цифра> — нетерминальные символы (в исходной программе на ассемблере таких объектов нет);
經 +|-|0|1|2|3|4|5|б|7|8|9 — терминальные символы (их можно найти в исходном тексте программы), из терминальные символов по приведенным ранее трем правилам строится любое десятичное число;
*символвертикальнойчерты(|)означаетальтернативупривыбореварианта
некоторого правила.
Для примера выведем число -501, используя формы Бэкуса-Наура:
<десятичное_знаковое_целое> п <число_без_знака> => <;число_без_знака><дес_цифра> => <числ(Г_без_знака>1 п <число_без_знака><дес_цифра>1 п <число_без_знака>01 п <дес _цифра>01 п 501
Предложения ассемблера (см. рис. 5.1-5.3)формируются из лексем, представляющих собой синтаксически неразделимые последовательности допустимых символов языка, имеющие смысл для транслятора.
Вначале определим алфавит ассемблера, то есть допустимые для написания текста программ символы:
ii А5СП_символ_буква — все латинские буквы А - Z, а - z, причем прописные и строчные буквы считаются эквивалентными;
竅 decdigit — цифры от 0 до 9;
специальные знаки _, ?, @, $, &;
разделители: ,, ., [,],(,),<, >, {, }, +,/,*,%,!,",",?, \, = #,Л.
Лексемами языка ассемблера являются ключевые слова, идентификаторы, цепочки символов и целые числа.
Ключевые слова — это служебные символы языка ассемблера. По умолчанию регистр символов ключевых слов не имеет значения. К ключевым словам относятся:
« названия регистров (AL, АН, BL, ВН, CL, СН, DL, DH, АХ, ЕАХ, ВХ, ЕВХ, СХ, ЕСХ, DX, EDX, ВР, EBP, SP, ESP, DI, EDI, SI, ESI, CS, DS, ES, FS, GS, SS, CRO, CR2, CR3, DRO, DR1, DR2, DR3, DR6, DR7);
經 операторы (BYTE, SBYTE, WORD, SWORD, DWORD, SDWORD, FWORD, QWORD, TBYTE, REAL4, REAL8, REAL10, NEAR16, NEAR32, FAR16, FAR32, AND, NOT, HIGH, LOW, HIGHWORD, LOWWORD, OFFSET, SEG, LROFFSET, TYPE, THIS, PTR, WIDTH, MASK, SIZE, SIZEOF, LENGTH, LENGTHOF, ST, SHORT, TYPE, OPATTR, MOD, NEAR, FAR, OR, XOR, EQ, NE, LT, LE, GT, GE, SHR, SHL и др.);
樂 названия команд (КОП) ассемблера, префиксов.
Идентификаторы — последовательности допустимых символов, использующиеся для обозначения имен переменных и меток. Правило записи идентификаторов можно описать следующими формами Бэкуса-Наура:
<id> п А5СП_символ__буква | А5СП_символ буква | <id> А5СП_символ_буква |
<id> <decdigft> | <znak> <decdigif> <id> | <znak> d> ——
<decdigit>=>0| 1 | 2 | 3 1415 | 617 1819
<znak> => _|?®$l_l&
Приведенные формы говорят о том, что идентификатор может состоять из одного или нескольких символов. В качестве символов можно использовать буквы латинского алфавита, цифры и некоторые специальные знаки — _, ?, $, @. Идентификатор не может начинаться символом цифры. Длина идентификатора может составлять до 255 символов (247 в MASM), хотя транслятор воспринимает лишь первые 32, а остальные игнорирует. Регулировать длину возможных идентификаторов (в TASM) можно с использованием ключа командной строки /mv. Кроме того, существует возможность указать транслятору на необходимость различать прописные и строчные буквы либо игнорировать их различие (что и делается по умолчанию). Для этого (в TASM) применяются ключи командной строки/mu,/ml, /mx (см. приложение В насайте http://www.piter.com/download).
Цепочки символов — это последовательности символов, заключенные в одинарные или двойные кавычки. Правила формирования:
<string><quote> [[ <stext> ]] <quote>
<stext><StringChar> | <stext> <stringChar>
<stri ngChar> = <quote> <quote> | любой_символ_кроме_кавычки
<quote> O " | '
Целые числа могут указываться в двоичной, десятичной или шестнадцатеричной системах счисления. Отождествление чисел при записи их в программах на- ассемблере производится по определенным правилам. Десятичные числа не требуют для своего отождествления указания каких-либо дополнительных символов. Для отождествления в исходном тексте программы двоичных и шестнадцатеричных чисел используются следующие правила:
<шестнадц_число> п <дес_шестнадц_число>Ь | 0<сим_шестнадц_число>И <дес_шестнадц_число> n^decdigi£><сим_шестнадц2число> | <decdigit> сейм 二 шести а/щ_иисно> =>
<hexdigit><CHM_uecTHa4u_4Mcno>|<дес_шестнадц_число> | <decdigit> | <hexdigit> <decdigit> =>0fl |2 I 314T5|6|7|8|9 ~~
<hexdi gi t> =>a|b|c|d|e|f|A|B|C|D|E|F
Важно отметить наличие символов после (h) и перед (0) записью шестнадцатеричного числа. Это сделано для того, чтобы транслятор мог отличить в программе одинаковые по форме записи десятичные и шестнадцатеричные числа. К примеру, числа 1578 и 1578hвыглядят одинаково, но имеют разные значения. С другой стороны, какое значение в тексте исходной программы может иметь лексема fe023? Это может быть и некоторый идентификатор, и, судя по набору символов, шестнадцатеричное число. Для того чтобы однозначно описать в тексте программы на ассемблере шестнадцатеричное число, начинающееся с буквы, его дополняют ведущим нулем «О» и в конце ставят символ «h». Для данного примера правильная запись шестнадцатеричного числа — Ofe023h:
<двоичн_число> => <bindigit>bI<bindigi1><двоичн_число>Ь <bindigit> => 0|1
Для двоичных чисел все просто — после записи нулей и единиц, входящих в их состав, необходимо поставить латинскую букву «Ь». Пример: i@®a@i@ib
Рассуждениями, приведенными ранее, был показан порядок формирования предложений программы ассемблера и составляющих их элементов (лексем). Также были рассмотрены правила формирования меток, названий команд (префиксов). Осталось обсудить комментарии и операнды. Что касается комментария, то это самый простой элемент предложения ассемблера. Любая комбинация символов ASCII, расположенная в строке за символом точки с запятой (;), транслятором игнорируется, то есть является комментарием (см. рис. 5.1-5.3). Описанию операндов, ввиду их важности, будет посвящен отдельный подраздел.
Операнды
Операнды — это объекты, над которыми или при помощи которых выполняются действия, задаваемые инструкциями или директивами. Машинные команды могут либо совсем не иметь операндов, либо иметь один или два операнда. Большинство команд требует двух операндов, один из которых является источником, а другой — приемником (операндом назначения). В двухоперандной машинной команде возможны следующие сочетания операндов:
в регистр — регистр;
綴 регистр — память;
в память — регистр;
* непосредственный операнд — регистр;
綫 непосредственный операнд — память.
Здесь важно подчеркнуть, что один операнд может располагаться в регистре или памяти, а второй операнд обязательнодолжен находиться в регистре или непосредственно в команде. Непосредственный операнд может быть только источником.
Для приведенных ранее правил сочетания типов операндов есть исключения, которые касаются:
команд работы с цепочками, которые могут перемещать данные из памяти в память;
команд работы со стеком, которые могут переносить данные из памяти в стек, также находящийся в памяти;
команд типа умножения, которые, кроме операнда, указанного в команде, неявно используют еще и второй операнд.
Операндами могут быть числа, регистры, ячейки памяти, символьные идентификаторы. При необходимости для расчета некоторого значения или определения ячейки памяти, на которую будет воздействовать данная команда или директива, используются выражения, то есть комбинации чисел, регистров, ячеек памяти, идентификаторов с арифметическими, логическими, побитовыми и атрибутивными операторами.
Рассмотрим классификацию операндов, поддерживаемых транслятором ассемблера.
в Операнд задается неявно на микропрограммном уровне. В этом случае команда явно не содержит операндов. Алгоритм выполнения команды использует некоторые объекты по умолчанию (регистры, флаги в EFLAGS и т. д.). Например, команды CLI и STI неявно работают с флагом прерывания IF в регистре EFLAGS, а команда XLAT неявно обращается к регистру AL и строке в памяти по адресу, определяемому парой регистров DS:BX.
к Операнд задается в самой команде (непосредственный операнд). Это может быть число, строка, имя или выражение, имеющее некоторое фиксированное (константное) значение. Физически непосредственный операнд находится в коде команды, то есть является ее частью. Для его хранения в команде выделяется поле длиной до 32 битов (см. главу 3). Непосредственный операнд может быть только вторым операндом (источником). Операнд-приемник может находиться либо в памяти, либо в регистре. Например, команда mov ax,Offffhпересылает в регистр АХ шестнадцатеричную константу Offffh.Команда add sum,2 складывает содержимое поля по адресу sum с целым числом 2 и записывает результат по месту первого операнда, то есть в память. Если непосредственный операнд — имя, то оно не должно быть перемещаемым, то есть зависеть от адреса загрузки программы в память. Такое имя можно определить оператором EQU или =. Пример:
num equ 5 вместо num ассемблер везде подставляет 5
mov al,nummov al,numimd = num-2 : вместо num ассемблер везде подставляет 3
mov al,num
mov al,num
эквивалентно mov 5, здесь 5 - непосредственный операнд
add [si],imd: сложение [si]:= [si]+3,;
; здесь imd - непосредственный операнд
mov al, 5 ; al:=5, здесь 5 - непосредственный операнд
В данном фрагменте определяются две константы, которые затем используются в качестве непосредственных операндов в командах пересылки MOV и сложения ADD.
国三3 Адресные операнды задают физическое расположение операнда в памяти путем указания двух составляющих адреса: сегмента и смещения (рис. 5.4). К примеру:
mov ax,0000h mov ds, ax mov ax,ds:0000h ; записать слово в ах из области памяти по физическому адресу 0000:0000
Здесь третья команда MOV имеет адресный операнд.
Рис. 5.4. Синтаксис адресных операндов
;•『 Перемещаемые операнды — любые символьные имена, представляющие некоторые адреса памяти. Эти адреса могут обозначать местоположение в памяти некоторой инструкции (если операнд — метка) или данных (если операнд — имя области памяти в сегменте данных). Перемещаемые операнды отличаются от адресных тем, ЧТО'ОИИ не привязаны к конкретному адресу физической памяти. Сегментная составляющая адреса перемещаемого операнда неизвестна и определяется после загрузки программы в память для выполнения. К примеру:
data segment
mas_w dw 25 dup (0)
codesegment
lea si ,.mas_w; mas_w - перемещаемый операнд
В этом фрагменте mas_w — символьное имя, значением которого является адрес первого байта области памяти размером 25 слов. Полный физический адрес этой области памяти будет известен только после загрузки программы в память для выполнения.
Счетчик адреса — специфический вид операнда. Он обозначается знаком $. Специфика этого операнда в том, что когда транслятор ассемблера встречает
в исходной программе этот символ, то он подставляет вместо него текущее значение счетчика адреса. Значение счетчика адреса, или, как его иногда называют счетчика размещения, представляет собой смещение текущей машинной команды относительно начала сегмента кода. При обработке транслятором очередной команды ассемблера счетчик адреса увеличивается на длину сформированной машинной команды. Важно правильно это понимать. К примеру, обработка директив ассемблера не влечет за собой изменения счетчика, так как директивы ассемблера, в отличие от его команд, — это лишь указания транслятору на выполнение определенных действий по формированию машинного представления программы, и для них транслятором не генерируется никаких конструкций в памяти. В качестве примера использования в команде значения счетчика адреса можно привести следующий фрагмент:
jmp $+3 ; безусловный переход на команду mov
cld ; длина команды cld составляет 1 байт mov al,l
При формировании выражения для перехода, подобного $+3, нужно помнить о длине самой команды, в которой это выражение используется, так как значение счетчика адреса соответствует смещению в сегменте команд данной, а не следующей за ней команды. В нашем примере команда JMP занимает два байта. Нужно быть осторожным, длина этой и других команд зависит от того, какие в ней используются операнды. Команда с регистровыми операндами будет короче команды, один из операндов которой расположен в памяти. В большинстве случаев эту информацию можно получить, зная формат машинной команды (см. главу 3 и приложение) и анализируя колонку файла листинга с объектным кодом команды.
躲 Регистровый операнд — это просто имя регистра. В программе на ассемблере можно использовать имена всех регистров общего назначения и некоторых системных регистров:
D 32-разрядные регистры ЕАХ, ЕВХ, ЕСХ, EDX, ESI, EDI, ESP, EBP;
16-разрядные регистры АХ, ВХ, СХ, DX, SI, DI, SP, ВР;
8-разрядные регистры АН, AL, BH, BL, CH, CL, DH, DL;
сегментные регистрыC5,D5, SS, ES, FS, GS;
D системные регистры CRO, CR2, CR3, CR4, DRO, DR1, DR2, DR3, DR6, DR7 (см. описа- ниекомандыMOVвприложении).
пример:mov ",4mov dl,pass+4 add al.dlпример:mov ",4mov dl,pass+4 add al.dlНапример, команда add ax,bx складывает содержимое регистров АХ и ВХ и записывает результат в ВХ. Команда dec si уменьшает содержимое SI на 1. И еще
пример:
mov ",4
mov dl,pass+4 add al.dl
пример:
mov ",4
mov dl,pass+4 add al.dl
константу 4 заносим в регистр a I байт по адресу pass+4 - в регистр dl команда с регистровыми операндами
潑 Операнд — порт ввода-вывода. Помимо адресного пространства оперативной памяти процессор поддерживает адресное пространство ввода-вывода, которое используется для доступа к устройствам ввода-вывода. Объем адресного пространства ввода-вывода составляет 64 Кбайт. Для любого устройства компьютера в этом пространстве выделяются адреса. Конкретное значение адреса в пределах этого пространства называется портом ввода-вывода. Физически порту
ввода-вывода соответствует аппаратный регистр (не путать с регистром процессора), доступ к которому осуществляется с помощью специальных команд ассемблера IN и OUT. Например,
in al,6@h; ввести байт из порта 60h
Регистры, адресуемые с помощью порта ввода-вывода, могут иметь разрядность 8,16 или 32 бита, но для конкретного порта разрядность регистра фиксирована. Команды IN и OUT работают с фиксированной номенклатурой объектов. В качестве источника информации или получателя применяются так называемыере- гистры -аккумуляторы ЕАХ, АХ, AL. Выбор регистра определяется разрядностью порта. Номер порта может задаваться непосредственным операндом в командах IN иOUT или значением врегистре DX. Последний способ позволяетдинами- чески определить номер порта в программе. Например, mov dx,20hзаписать номер порта 20h в регистр dx
mov al,20h : записать значение 20h в регистр 3 I out dx, al ; вывести значение 20h в порт 20H
緻 Структурные операнды используются для доступа к конкретному элементу сложного типа данных, называемого структурой. Мы подробно разберемся со структурами в главе 13.
* Записи (аналогично структурному типу) используются для доступа к битовому полю некоторой записи (глава 13).
II Операнд находится в стеке,
ж Операнд располагается в памяти. Это наиболее сложный и в то же время наиболее гибкий способ задания операндов. Он позволяет реализовать прямой и косвенный варианты адресации, являющиеся основными видами адресации.
Последний вариант расположения операндов, ввиду его важности и большого объема, рассмотрим более подробно. Обсуждение будет сопровождаться примерами команд ассемблера, цель которых — демонстрация того, как изменяется формат команды ассемблера при применении того или иного вида адресации. В связи с этим вернемся еще раз к рис. 2.8 (см. главу 2), который иллюстрирует принцип формирования физического адреса на адресной шине процессора. Видно, что адрес операнда формируется как сумма двух составляющих — сдвинутого на 4 бита содержимого сегментного регистра и 16-разрядного эффективного адреса, который в общем случае вычисляется как сумма трех компонентов: базы, смещения и индекса.
Прямая адресация
Прямая адресация — это простейший вид адресации операнда в памяти, так как эффективный адрес содержится в самой команде и для его формирования не используется никаких дополнительных источников или регистров. Эффективный адрес берется непосредственно из поля смещения машинной команды (см. главу 3), которое может иметь размер 8, 16, 32 бита. Это значение однозначно определяет байт, слово или двойное слово в сегменте данных.
Прямая адресация может быть двух типов.
滋 Относительная прямая адресация используется в командах условных переходов для указания относительного адреса перехода. Относительность такого пе-
рехода заключается в том, что в поле смещения машинной команды содержится 8-, 16- или 32-разрядное значение, которое в результате работы команды будет складываться с содержимым регистра указателя команд IP/EIP. В результате такого сложения получается адрес, по которому и осуществляется переход. К примеру,
jc ml;переход на метку ml, если флаг cf = 1
mov2
ml:
Хотя в команде указана конкретная метка, ассемблер вычисляет смещение этой метки относительно следующей команды (в нашем случае mov aL2) и подставляет его в формируемую машинную команду JC.
Абсолютная прямая адресация — в этом случае эффективный адрес является частью машинной команды, но формируется этот адрес только из значения поля смещения в команде. Для формирования физического адреса операнда в памяти процессор складывает это поле со сдвинутым на четыре бита значением сегментного регистра. В команде ассемблера можно использовать несколько форм такой адресации. К примеру, mov ax.word ptr [0000] ;записать слово по адресу
;ds:0000 в регистр ах
Однако такая адресация применяется редко — обычно ячейкам в программе присваиваются символические имена. В процессе трансляции ассемблер вычисляет и подставляет значения смещений этих имен в поле смещения формируемой им машинной команды (см. главу 3). В итоге получается, что машинная команда прямо адресует свой операнд, имея, фактически, в одном из своих полей значение эффективного адреса. К примеру, data segment perl dw 5 data ends code segment mov ax.data mov ds, ax
mov ax,perl ;записать слово perl (его физический адрес ds:0000) в ах
Мы получим тот же результат, что и при использовании команды mov ax.word ptr [0000]
Остальные виды адресации относятся к косвенным. Слово «косвенный» в названии этих видов адресации означает, что в самой команде может находиться лишь часть эффективного адреса, а остальные его компоненты находятся в регистрах, на которые указывают своим содержимым байт mod r/m и, возможно, байт sib.
Косвенная адресация имеет следующие разновидности:
косвенная базовая, или регистровая, адресация;
косвенная базовая адресация со смещением;
косвенная индексная адресация со смещением;
косвенная базовая индексная адресация;
ж косвенная базовая индексная адресация со смещением.
Косвенная базовая адресация
При косвенной базовой (регистровой) адресации эффективный адрес операнда может находиться в любом из регистров общего назначения, кроме SP/ESP и BP/tBP (это специальные регистры для работы с сегментом стека).
Синтаксически в команде этот режим адресации выражается заключением имени регистра в квадратные скобки. К примеру, команда mov ax,[ecx] помещает в регистр АХ содержимое слова по адресу сегмента данных со смещением, хранящимся в регистре ЕСХ. Так как содержимое регистра легко изменить в ходе работы программы, данный способ адресации позволяет динамически назначить адрес операнда для некоторой машинной команды. Это очень полезно, например, для организации циклических вычислений и для работы с различными структурами данных типа таблиц или массивов.
Косвенная базовая адресация со смещением
Косвенная базовая (регистровая) адресация со смещением является дополнением предыдущего вида адресации и предназначена для доступа к данным с известным смещением относительно некоторого базового адреса. Этот вид адресации удобно использовать для доступа к элементам структур данных, когда смещение элементов известно заранее на стадии разработки программы, а базовый (начальный) адрес структуры должен вычисляться динамически на стадии выполнения программы. Модификация содержимого базового регистра позволяет обращаться к одноименным элементам различных экземпляров однотипных структур данных.
К примеру, команда mov ax,[edx+3h] пересылает в регистр АХ слово из области памяти по адресу, определяемому содержимым EDX + 3h. Команда mov ax,mas[dx] пересылает в регистр АХ слово по адресу, определяемому содержимым DX плюс значение идентификатора mas (не забывайте, что транслятор присваивает каждому идентификатору значение, равное смещению этого идентификатора относительно начала сегмента данных).
Косвенная индексная адресация со смещением
Косвенная индексная адресация со смещением очень похожа на косвенную базовую адресацию со смещением. Здесь также для формирования эффективного адреса используется один из регистров общего назначения. Но индексная адресация обладает одной интересной особенностью, которая очень удобна для работы с массивами. Она связана с возможностью так называемого масштабирования содержимого индексного регистра. Что это такое? Для выяснения смысла этого термина стоит вернуться к материалу главы 3. В контексте нашего обсуждения нас интересует байт sib. При описании структуры этого байта (см. раздел «Формат машинных команд 1А-32» в главе 3) отмечалось, что он состоит из трех полей. Одно из этих полей — поле масштаба (ss), на значение которого умножается содержимое индексногорегистра. К примеру, в команде mov ax,masfesi*2] значение эффективного адреса второго операнда определяется выражением mas+(esi)*2. В связи с тем, что в ассемблере нет средств индексации массивов, программисту приходится организовывать ее своими силами. Наличие возможности масштабирования существенно помогает в решении этой проблемы, но при условии, что размер элементов массива составляет 1, 2, 4 или 8 байт.
Косвенная базовая индексная адресация
При косвенной базовой индексной адресации эффективный адрес формируется как сумма содержимого двух регистров общего назначения: базового и индексного. В качестве этих регистров могут применяться любые регистры общего назначения, при этом часто содержимое индексного регистра масштабируется. Например: mov eax,[esi][edx]
В данном примере эффективный адрес второго операнда формируется из двух компонентов, (ESI) + (EDX).
Косвенная базовая индексная адресация со смещением
Косвенная базовая индексная адресация со смещением является дополнением косвенной индексной адресации. Эффективный адрес формируется как сумма трех составляющих: содержимого базового регистра, содержимого индексного регистра и значения поля смещения в команде. К примеру, команда mov eaxz[esi+5][edx] пересылает в регистр ЕАХ двойное слово по адресу: (ESI) + 5 + (EDX). Команда add axrarray[esi][ebx] складывает содержимое регистра АХ с содержимым слова по адресу, образованному значением идентификатора array + (ESI) + (ЕВХ).
Далее вернемся к обсуждению операндов команды и выясним, какие возможности предоставляются ассемблером для формирования более сложных операндов — операндов-выражений.
Операнды-выражения
Операнд команды может быть выражением, представляющим собой комбинацию операндов и операторов ассемблера. Транслятор ассемблера рассматривает выражение как единое целое и преобразует его в числовую константу. Логически значением этой константы может быть адрес некоторой ячейки памяти или некоторое абсолютное значение.
Перечислим возможные типы операторов ассемблера (TASM) и синтаксические правила формирования выражений ассемблера. Как и в языках высокого уровня, выполнение операторов ассемблера при вычислении выражений осуществляется в соответствии с их приоритетами (табл. 5.1). Операторы с одинаковыми приоритетами обрабатываются последовательно слева направо. Изменение порядка выполнения возможно путем расстановки круглых скобок, которые имеют наивысший приоритет.
Таблица 5.1. Операторы и их приоритет
Оператор
Приоритет
LENGTH, SIZE, WIDTH, MASK, (, ), [, ], <, >
1
2
3
PTR, OFFSET, SEG, TYPE, THIS
4
HIGH, LOW
5
+, - (унарные)
6
*,/,MOD,SHL, SHR
продолжение 少■
4 Зак. 256
Таблица5.1 (продолжение)
Оператор
Приоритет
+, - (бинарные)
8
EQ, NE, LT, LE, GT, GE
9
NOT
10
AND
11
OR, XOR
12
SHORT, TYPE
13
Дадим краткую характеристику операторов. В приложении Б (http://www.piter. com/download) для сравнения и информации приведены сведения об операторах и предопределенных символах транслятора MASM.
9 Арифметические операторы (рис. 5.5). К арифметическим операторам относятся унарные и бинарные операторы «плюс» (+) и «минус» (-), а также операторы умножения (*), целочисленного деления (/), получения остатка от деления (MOD). Эти операторы в табл. 5.1 соответствуют уровням приоритета 6, 7, 8. Например,
tab—Sizeequ 50; размер массива в байтах
size_el equ 2; размер элементов
; вычисляется число элементов массива и заносится в регистр сх mov cx,tab_size / size__el ; оператор
Рис. 5.5. Синтаксис арифметических операторов
裳 Операторы сдвига выполняют сдвиг выражения на указанное количество разрядов (рис. 5.6). Например,
mask_b equ 10111011
mov al(mask b shr 3 ;al=00010111
-[Выражение-[ВыражениеЧисло сдвигаемых разрядовЧисло сдвигаемых разрядов
-[Выражение
-[Выражение
Число сдвигаемых разрядов
Число сдвигаемых разрядов
Рис. 5.6. Синтаксис операторов сдвига
Синтаксис ассемблера99
發 Операторы сравнения возвращают значение «истина» или «ложь» и предназначены для формирования логических выражений (рис. 5.7 и табл. 5.2). Логическое значение «истина» соответствует логической единице, а «ложь» — логическому нулю. Логическая единица — значение, все биты которого равны 1,соответствен- но, логический нуль — значение, все биты которого равны 0. Например,
tab_sizeequ 30; размер таблицы
mov al,tab_size ge 50; загрузка размера таблицы в al
cmp al,0; если tab_size < 50. то
j e ml; переход на ml
mi: ...
В этом примере, если значение tab_size больше или равно 50, то результат в AL равен Offh, а если tab_size меньше 50, то результат в AL равен 00h. Команда CMP сравнивает значение AL с нулем и устанавливает соответствующие флаги в FLAGS/ EFLAGS. Команда JE на основе анализа этих флагов передает или не передает управление на метку ml.
Выражен ие_1Выражен ие_1
Выражен ие_1
Выражен ие_1
Рис. 5.7. Синтаксис операторов сравнения
Таблица5.2. Операторысравнения
Оператор
Значение
Eq
Истина, если выражение__1 равно выражение_2|
Ne
Истина, если выражение^! не равно выражение_2[
Lt
Истина, если выражение 1 меньше выражение_2j
Le
Истина, если выражение 1 меньше или равно выражение_2 |
Gt
Истина, если выражение 1 больше выражение 2
Ge
Истина, если выражение_1 больше или равно выражение_2 |
Логические операторы выполняют над выражениями побитовые операции (рис. 5.8). Выражения должны быть абсолютными, то есть такими, численное значение которых может быть вычислено транслятором. Например, flags equ 10010011 mov al .flags xor 01h; al = 10010010; пересылка в al поля flags
; с инвертированным правым битом
Более подробные сведения о правилах, в соответствии с которыми вычисляется результат логических операций, приводятся в главе 9.
Рис.5.8. Синтаксислогическихоператоров
綴Индексный оператор. Как показано на рис. 5.9, квадратные скобки транслятор воспринимает как указание сложить значение выражением за этими скобками со значением выражение_2, заключенным в скобки. Например,
mov ax.masfsi] ; пересылка слова по адресу mas + (si) в регистр ах
^г-(дУ-| Выражение_2 |-(J)—
Ц Выражение 1р
Рис.5.9. Синтаксисиндексногооператора
ПРИМЕЧАНИЕВ литературе принято следующее соглашение: когда в тексте речь идет
о содержимом регистра, то его название берут в круглые скобки. К примеру, в нашем случае запись в тексте комментария последнего фрагмента программы mas + (si) означает вычисление выражения, составляющего значение смещения символического имени mas плюс содержимое регистра SI.
* Оператор переопределения типа PTR применяется для переопределения или уточнения типа метки (или переменной), определяемой выражением (рис. 5.10). Тип может принимать одно из следующих значений: BYTE, WORD, DWORD, QWORD, TBYTE, NEAR, FAR (что означают эти значения, вы узнаете далее в этой главе). Например,
d_wrd dd 0
mov al,byte ptr d_wrd+l ; пересылка второго байта из двойного слова Поясним этот фрагмент программы. Переменная d_wrd имеет тип двойного слова. Что делать, если возникнет необходимость обращения не ко всему значению переменной, а только к одному из входящих в нее байтов (например, ко второму)? Если попытаться сделать это командой mov ald_wrd+l, то транслятор выдаст сообщение о несовпадении типов операндов. Оператор PTR позволяет непосредственно в команде переопределить тип и выполнить команду.
-{Twn|~ptr)—|Выражение |—
Рис. 5.10.Синтаксис оператора переопределения типа
犠 Оператор переопределения сегмента заставляет вычислять физический адрес относительно конкретно задаваемой сегментной составляющей: «имя сегментного регистра», «имя сегмента» из соответствующей директивы SEGMENT или «имя группы» (рис. 5.11).
Рис. 5.11. Синтаксис оператора переопределения сегмента
Этот момент важен, поэтому поясним его подробнее. При обсуждении сегментации (см. главу 2) упоминалось о том, что процессор на аппаратном уровне поддерживает три типа сегментов — кода, стека и данных. В чем заключается такая аппаратная поддержка? К примеру, для выборки на выполнение очередной команды процессор должен обязательно посмотреть содержимое сегментного регистра CS, и только его. В этом регистре содержится информация о начале сегмента команд. В реальном режиме работы процессора в сегментном регистре CS находится не сдвинутый на четыре бита влево физический адрес начала сегмента. Для получения адреса конкретной команды процессору остается умножить содержимое CS на 16 (что означает сдвиг на четыре разряда) и сложить полученное 20-разрядное значение с 16-разрядным содержимым регистра IP. Примерно то же самое происходит и тогда, когда процессор обрабатывает операнды в машинной команде. Если он видит, что операнд — это адрес (эффективный адрес, который является только частью физического адреса), то он знает, в каком сегменте его искать, — по умолчанию это сегмент, адрес начала которого записан в сегментном регистре DS. В защищенном режиме сегментные регистры содержат селекторы, с помощью которых также можно получить доступ к информации об адресе начала сегмента.
А что же с сегментом стека? Для большей ясности стоит вернуться к вопросу о регистрах общего назначения (см. главу 2). В контексте нашего рассмотрения интерес представляют регистры SP и ВР. Если процессор видит в качестве операнда (или его части, если операнд — выражение) один из этих регистров, то по умолчанию он формирует физический адрес операнда, используя содержимое регистра SS как сегментную составляющую этого адреса. Что подразумевает термин «по умолчанию»? Вспомним (см. главу 1) набор микропрограмм в блоке микропрограммного управления, каждая из которых выполняет одну из команд в системе машинных команд процессора. Каждая микропрограмма работает по своему алгоритму. Изменить его, конечно же, нельзя, но можно чуть-чуть подкорректировать. Делается это с помощью необязательного поля префикса машинной команды (см. раздел «Формат машинных команд 1А-32» в главе 3). Если программист согласен с тем, как работает команда, то это поле отсутствует. Если же есть необходимость внести поправку (если, конечно, она допустима для конкретной команды) в алго-
102 Глава 5. Синтаксис ассемблера
ритм работы команды, то необходимо сформировать соответствующий префикс. Префикс представляет собой однобайтовую величину, численное значение которой определяет ее назначение. Процессор распознает по указанному значению, что этот байт является префиксом, и дальнейшая работа микропрограммы выполняется с учетом поступившего указания на корректировку ее работы. В главе 3 мы перечислили все возможные префиксы машинных команд. В контексте нашего обсуждения интерес представляет один из них — префикс замены сегмента. Его назначение состоит в том, чтобы указать процессору (а по сути, микропрограмме) на то, что мы не хотим использовать сегмент по умолчанию. Возможности для подобного переопределения, конечно, ограничены. Сегмент команд переопределить нельзя, адрес очередной исполняемой команды однозначно определяется парой CS:IP. А вот сегменты стека и данных — можно. Для этого и предназначен оператор переопределения сегмента (:). Транслятор ассемблера, обрабатывая этот оператор, формирует соответствующий однобайтовый префикс замены сегмента и ставит его перед машинным представлением соответствующей команды ассемблера. Например, .code
jmp metl ; обход обязателен, иначе поле ind
; будет трактоваться как очередная команда
inddb 5; описание поля данных в сегменте команд
metl:
moval,cs:ind; переопределение сегмента позволяет работать с данными,
; определенными внутри сегмента кода
Продолжим перечисление операторов.
綫 Оператор именования типа структуры также заставляет транслятор производить определенные вычисления, если он встречается в выражении. Подробно этот оператор (.) описывается в главе 13 при обсуждении сложных типов данных.
鬱 Оператор получения сегментной составляющей адреса выражения возвращает физический адрес сегмента для выражения, в качестве которого могут выступать метка, переменная, имя сегмента, имя группы или некоторое символическое имя (рис. 5.12).
Рис. 5.12. Синтаксис оператора получения сегментной составляющей
瀚 Оператор получения смещения выражения позволяет получить значение смещения выражения в байтах относительно начала того сегмента, в котором выражение определено (рис. 5.13).
■™|offsetp*|■™|offsetp*|
■™|offsetp*|
■™|offsetp*|
Рис. 5.13. Синтаксис оператора получения смещения
Например,
.data
pole dw 5
.code
mov ax,seg pole
mov es, ax
mov dx.off set pole теперь в паре es:dx полный адрес pole
Директивы сегментации
В ходе предыдущего обсуждения были приведены основные правила записи команд и операндов в программе на ассемблере. Открытым остался вопрос о TOM, как правильно оформить последовательность команд, чтобы транслятор мог их обработать, а процессор — выполнить. В главах 2 и 3 мы уже касались понятия сегмента. При рассмотрении архитектуры процессора мы узнали, что он имеет шесть сегментных регистров, посредством которых может одновременно работать:
is с одним сегментом кода;
т с одним сегментом стека;
и с одним сегментом данных;
с тремя дополнительными сегментами данных.
Еще раз вспомним, что физически сегмент представляет собой область памяти, занятую командами и/или данными, адреса которых вычисляются относительно значения в соответствующем сегментном регистре.
Синтаксическое описание сегмента на ассемблере представляет собой конструкцию, представленную на рис. 5.14.
-сегмента ~I 財万以]
Рис. 5.14. Синтаксис описания сегмента
Важно отметить, что функциональное назначение сегментации несколько шире, чем простое разбиение программы на блоки кода, данных и стека. Сегментация является частью более общего механизма, связанного с концепцией модульного программирования. Она предполагает унификацию формата объектных модулей,
создаваемых компилятором, в том числе компилируемых с разных языков программирования. Это позволяет объединять программы, написанные на разных языках. Именно для реализации различных вариантов такого объединения и предназначены операнды в директиве SEGMENT. Рассмотрим их подробнее.
綴 Атрибут выравнивания сегмента (тип выравнивания) сообщает компоновщику о том, что нужно обеспечить размещение начала сегмента на заданной границе. Это важно, поскольку при правильном выравнивании доступ к данным в процессорах i80x86 выполняется быстрее. Допустимые значения этого атрибута приведены далее. По умолчанию тип выравнивания имеет значение PARA: D BYTE — выравнивание не выполняется. Сегмент может начинаться с любого адреса памяти;
D WORD — сегмент начинается по адресу, кратному двум, то есть последний (младший) значащий бит физического адреса равен 0 (выравнивание по границе слова);
DWORD — сегмент начинается по адресу, кратному четырем, то есть два последних (младших) значащих бита равны 0 (выравнивание по границе двойного слова);
П PARA — сегмент начинается по адресу, кратному 16, то есть последняя шестнадцатеричная цифра адреса должна быть Oh (выравнивание по границе параграфа);
П PAGE — сегмент начинается по адресу, кратному 256, то есть две последние шестнадцатеричные цифры должны быть OOh (выравнивание по границе страницы размером 256 байт);
Р MEMPAGE — сегмент начинается по адресу, кратному 4 Кбайт, то есть три последние шестнадцатеричные цифры должны быть OOOh (адрес следующей страницы памяти размером 4 Кбайт).
:幾 Атрибут комбинирования сегментов (комбинаторный тип) сообщает компоновщику, как нужно комбинировать сегменты различных модулей, имеющие одно и то же имя. По умолчанию атрибут комбинирования принимает значение PRIVATE. Возможные значения атрибута комбинирования сегмента перечислены далее:
Р PRIVATE — сегмент не будет объединяться с другими сегментами с тем же именем вне данного модуля;
Р PUBLIC — заставляет компоновщик объединить все сегменты с одинаковым именем. Новый объединенный сегмент будет целым и непрерывным. Все адреса (смещения) объектов, а это могут быть, в зависимости от типа сегмента, команды или данные, будут вычисляться относительно начала этого нового сегмента;
Р COMMON — располагает все сегменты с одним и тем же именем по одному адресу, то есть все сегменты с данным именем перекрываются. Размер полученного в результате сегмента будет равен размеру самого большого сегмента;
Р AT xxxx — располагает сегмент по абсолютному адресу параграфа (параграф — область памяти, объем которой кратен 16, потому последняя шестнадцате-
ричная цифра адреса параграфа равна 0). Абсолютный адрес параграфа задается выражением ххх. Компоновщик располагает сегмент по заданному адресу памяти (это можно использовать, например, для доступа к видеопамяти или области ПЗУ), учитывая атрибут комбинирования. Физически это означает, что сегмент при загрузке в память будет расположен начиная с этого абсолютного адреса параграфа, но для доступа к нему в соответствующий сегментный регистр должно быть загружено заданное в атрибуте значение. Все метки и адреса в определенном таким образом сегменте отсчитываются относительно заданного абсолютного адреса;
STACK — определение сегмента стека. Заставляет компоновщик объединить все одноименные сегменты и вычислять адреса в этих сегментах относительно регистра SS. Комбинированный тип STACK (стек) аналогичен комбинированному типу PUBLICза исключением того, что регистр SS является стандартным сегментным регистром для сегментов стека. Регистр SP устанавливается на конец объединенного сегмента стека. Если не указано ни одного сегмента стека, компоновщик выдаст предупреждение, что стековый сегмент не найден. Если сегмент стека создан, а комбинированный тип STACK не используется, программист должен явно загрузить в регистр SS адрес сегмента (подобно тому, как это делается для регистра DS).
經 Атрибут класса сегмента (тип класса) — это заключенная в кавычки строка, помогающая компоновщику определить нужный порядок следования сегментов при сборке программы из сегментов нескольких модулей. Компоновщик объединяет вместе в памяти все сегменты с одним и тем же именем класса (имя класса в общем случае может быть любым, но лучше, если оно отражает функциональное назначение сегмента). Типичным примером использования имени класса (обычно класса code) является объединение в группу всех сегментов кода программы. С помощью механизма типизации класса можно группировать также сегменты инициализированных и неинициализированных данных.
修 Атрибут размера сегмента. Для процессоров i80386 и выше сегменты могут быть 16- или 32-разрядными. Это влияет прежде всего на размер сегмента и порядок формирования физического адреса внутри него. Далее перечислены возможные значения атрибута:
П USE16 — сегмент допускает 16-разрядную адресацию. При формировании физического адреса может использоваться только 16-разрядное смещение. Соответственно, такой сегмент может содержать до 64 Кбайт кода или данных;
П USE32 — сегмент должен быть 32-разрядным. При формировании физического адреса может использоваться 32-разрядное смещение. Поэтому такой сегмент может содержать до 4 Гбайт кода или данных.
Все сегменты сами по себе равноправны, так как директивы SEGMENT и ENDS не содержат информации о функциональном назначении сегментов. Для того чтобы использовать их как сегменты кода, данных или стека, необходимо предварительно сообщить транслятору об этом с помощью специальной директивы ASSUME, формат которой показан на рис. 5.15. Эта директива сообщает транслятору, какой сег-
мент к какому сегментному регистру привязан. В свою очередь, это позволяет транслятору корректно связывать символические имена, определенные в сегментах. Привязка сегментов к сегментным регистрам осуществляется с помощью операндов этой директивы, в которых имя_сегмента должно быть именем сегмента, определенным в исходном тексте программы директивой SEGMENT или ключевым словом NOTHING. Если в качестве операнда используется только ключевое слово NOTHING, то предшествующие назначения сегментных регистров аннулируются, причем сразу для всех шести- сегментных регистров. Ключевое слово NOTHING можно также использовать вместо аргумента имя сегмента; в этом случае будет выборочно разрываться связь между сегментом с именем имя сегмента и соответствующим сегментным регистром.
Рассмотренные ранее директивы сегментации используются для оформления программы в трансляторах MASM и TASM. Поэтому их называют стандартными директивами сегментации.
Для простых программ, содержащих по одному сегменту для кода, данных и стека, хотелось бы упростить их описание. Для этого в трансляторы MASM и TASM была введена возможность использования упрощенных директив сегментации. При этом возникла проблема, связанная с тем, что необходимо было как-то компенсировать невозможность напрямую управлять размещением и комбинированием сегментов. Для этого совместно с упрощенными директивами сегментации стали использовать директиву указания модели памяти MODEL, которая частично стала управлять размещением сегментов и выполнять функции директивы ASSUME (поэтому при введении в программу упрощенных директив сегментации директиву
ASSUME можно не указывать). Директива MODEL связывает сегменты, которые при наличии упрощенных директив сегментации имеют предопределенные имена, с сегментными регистрами (хотя все равно придется явно инициализировать регистр DS).
Для сравнения приведем два листинга с программами на ассемблере. Функционально они одинаковы и выводят на консоль сообщение: «Hello World! No war and bomb! Let's live friendly and learn assembler language.». Листинг 5.1 содержит программу со стандартными директивами сегментации, а листинг 5.2, соответственно, — с упрощенными.
Листинг5.1. Использование стандартных директив сегментации
datasegment para public 'data'message db 'Hello World! No war and assembler language. $'dataendsstk segment stackdb 256 dup ('?')stk endscodesegment para public 'code *mainprocassume cs:code,ds:data,ss:stkmov ax,data mov ds, ax mov ah,9mov dx.offset messageint 21hmov ax,4c00hint 21hmainendpcodeendsend maindatasegment para public 'data'message db 'Hello World! No war and assembler language. $'dataendsstk segment stackdb 256 dup ('?')stk endscodesegment para public 'code *mainprocassume cs:code,ds:data,ss:stkmov ax,data mov ds, ax mov ah,9mov dx.offset messageint 21hmov ax,4c00hint 21hmainendpcodeendsend mainbomb! Let us live friendly and learnbomb! Let us live friendly and learnсегмент стеканачало сегмента коданачало процедуры mainадрес сегмента данных в регистр ах ах в dsсегмент стеканачало сегмента коданачало процедуры mainадрес сегмента данных в регистр ах ах в dsвывод сообщения на экранпересылка 4c00h в регистр ахвызов прерывания с номером 21h конец процедуры mainконец сегмента кодаконец программы с точкой входа mainвывод сообщения на экранпересылка 4c00h в регистр ахвызов прерывания с номером 21h конец процедуры mainконец сегмента кодаконец программы с точкой входа main
datasegment para public 'data'
message db 'Hello World! No war and assembler language. $'
dataends
stk segment stack
db 256 dup ('?')
stk ends
codesegment para public 'code *
mainproc
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
mainendp
codeends
end main
datasegment para public 'data'
message db 'Hello World! No war and assembler language. $'
dataends
stk segment stack
db 256 dup ('?')
stk ends
codesegment para public 'code *
mainproc
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
mainendp
codeends
end main
bomb! Let us live friendly and learn
bomb! Let us live friendly and learn
сегмент стека
начало сегмента кода
начало процедуры main
адрес сегмента данных в регистр ах ах в ds
сегмент стека
начало сегмента кода
начало процедуры main
адрес сегмента данных в регистр ах ах в ds
вывод сообщения на экран
пересылка 4c00h в регистр ах
вызов прерывания с номером 21h конец процедуры main
конец сегмента кода
конец программы с точкой входа main
вывод сообщения на экран
пересылка 4c00h в регистр ах
вызов прерывания с номером 21h конец процедуры main
конец сегмента кода
конец программы с точкой входа main
Листинг 5.2. Использование упрощенныхдиректив сегментации
masm model
small
режим работы для TASM - masm, для MASM - не нужно модель памяти
.data
сегмент данных
message
db 'Hello World! No war and bomb! Let us live friendly and learn
assembler language
.stack
256h
сегмент стека
.code
сегмент кода
main
proc
начало процедуры main
mov
ax,@data
заносим адрес сегмента данных в регистр ах
mov
ds, ax
ах в ds
mov
ah, 9
mov
dx .offset
message
int
21h
вывод сообщения на экран
mov
ax,4c00h
пересылка 4c@@h в регистр ах
int
21h
вызов прерывания с номером 21h
main
endp
конец процедуры main
end mainконец программы с точкой входа main
Синтаксис директивы MODEL показан на рис. 5.16.
Обязательным параметром директивы MODEL является модель_памяти. Этот параметр определяет модель сегментации памяти для программного модуля. Предполагается, что программный модуль может иметь только определенные типы сегментов, которые определяются так называемыми упрощенными директивами описания сегментов (табл. 5.3).
Таблица 5.3. Упрощенные директивы определения сегмента
Формат директивы (режим MASM)1
Назначение
.CODE [имя]
Начало или продолжение сегмента кода
.DATA
Начало или продолжение сегмента инициализированных данных. Также используется для определения данных типа near 1
.CONST
Начало или продолжение сегмента постоянных данных (констант) модуля
.DATA?
Начало или продолжение сегмента неинициализированных данных. Также используется для определения данных типа near
STACK [размер]
Начало или продолжение сегмента стека модуля. Параметр [размер] задает размер стека
.FARDATA [имя]
Начало или продолжение сегмента инициализированных данных тина far
.FARDATA? [имя]
Начало или продолжение сегмента неинициализированных данных типа far
Наличие в некоторых директивах параметра [имя] говорит о том, что возможно определение нескольких сегментов этого типа. С другой стороны, наличие нескольких видов сегментов данных обусловлено требованием обеспечить совместимость с некоторыми компиляторами языков высокого уровня, которые создают разные сегменты данных для инициализированных и неинициализированных данных, а также констант.
При использовании директивы MODEL транслятор делает доступными несколько предопределенных идентификаторов, к которым можно обращаться во время работы программы в целях получения информации о тех или иных характеристиках выбранной модели памяти (см. ниже). Перечислим эти идентификаторы и их значения для транслятора TASM (табл. 5.4). Предопределенные идентификаторы MASMприведены вприложении Б (см.насайтеhttp://www.piter.com/download).
Таблица 5.4. Идентификаторы, создаваемые директивой MODEL
Имя идентификатора
Значение переменной
©code
Физический адрес сегмента кода
@data
Физический адрес сегмента данных типа near
@fardata
Физический адрес сегмента данных типа far
Имя идентификатора
Значение переменной
◎fardata?
Физический адрес сегмента неинициализированных данных типа far
@curseg
Физический адрес сегмента инициализированных данных типа far
@stack
Физический адрес сегмента стека
Если вернуться к листингу 5.2, то можно увидеть пример использования одного из перечисленных идентификаторов, а именно @data. Цель его применения — получение значения физического адреса сегмента данных программы.
В заключение обсуждения директивы MODEL отметим, что операнды директивы MODEL задают модель памяти, определяющей набор сегментов программы, размеры сегментов данных и кода, способы связывания сегментов и сегментных регистров. В табл. 5.5 приведены некоторые значения параметров модели памяти директивы MODEL
Таблица 5.5. Модели памяти
Модель
Тип кода
Тип данных
Назначение модели
TINY
near
near
Код и данные объединены в одну группу с именем DGROUP. Используется для создания программ файлового формата СОМ
SMALL
near
near
Код занимает один сегмент, данные объединены в одну группу с именем DGROUP. Эту модель обычно используют для большинства программ на ассемблере
MEDIUM
far
near
Код занимает несколько сегментов, по одному на каждый объединяемый программный модуль. Все ссылки на передачу управления имеют тип far. Данные объединены в одной группе; все ссылки на них имеют тип near
COMPACT
near
far
Код в одном сегменте; ссылки на данные имеют тип far
LARGE
far
far
Код в нескольких сегментах, по одному на каждый объединяемый программный модуль
FLAT
near
near
Код и данные в одном 32-битном сегменте (плоская модель памяти)
Параметр модификатор директивы MODEL позволяет уточнить некоторые особенности использования выбранной модели памяти (табл. 5.6).
Таблица 5.6. Модификаторы модели памяти
Значение модификатора
Назначение
use 16
Сегменты выбранной модели используются как 16-разрядные (если соответствующей директивой указан процессор i80386 или i80486)
продолжение
Таблица 5.6 (продолжение)
Значение модификатора
Назначение
use32
Сегменты выбранной модели используются как 32-разрядные (если соответствующей директивой указан процессор i80386 или i80486)I
dos
Программа будет работать в MS-DOS
Необязательные параметры язык и модификатор_языка определяют некоторые особенности вызова процедур. Необходимость в использовании этих параметров появляется при написании и связывании программ на разных языках программирования. Этого вопрос будет подробнее обсужден в главе 15 при рассмотрении средств модульного программирования на ассемблере.
Описанные ранее стандартные и упрощенные директивы сегментации не исключают друг друга. Стандартные директивы используются, когда программист желает получить полный контроль над размещением сегментов в памяти и их комбинированием с сегментами других модулей. Упрощенные директивы целесообразно использовать для простых программ, а также для программ, предназначенных для связывания с программными модулями, написанными на языках высокого уровня. Это позволяет компоновщику эффективно связывать модули разных языков за счет стандартизации связей и управления.
Таким образом, в ходе предыдущего изложения была описана структура программы на ассемблере. Было показано, что программа разбивается на несколько сегментов, каждый из которых имеет свое функциональное назначение. Для такого разбиения используются директивы сегментации. Трансляторы TASM и МASM предоставляют два типа таких директив: стандартные и упрощенные. Далее в учебнике будут использоваться упрощенные директивы сегментации, если не появится необходимость в применении стандартных директив.
Вторую часть главы мы посвятим вопросу описания данных в программе на ассемблере и организации доступа к ним.
Простые типы данных ассемблера
Любая программа предназначена для обработки некоторой информации, поэтому вопрос о том, какие типы данных языка программирования доступны для использования и какие средства языка привлекаются для их описания, обычно встает одним из первых. Трансляторы TASM и MASM предоставляют широкий набор средств описания и обработки данных, который вполне сравним с аналогичными средствами большинства языков высокого уровня.
Понятие типа данных носит двойственный характер. С точки зрения размерности процессор аппаратно поддерживает следующие основные типы данных (рис. 5.17).
綴 Байт — восемь последовательно расположенных битов, пронумерованных от О до 7, при этом бит 0 является самым младшим значащим битом.
Байт
Слово
Старший : Младший байтбайт
Ц
1—
Учетверенное словоДвойное слово
Старшее слово
Младшее слово
Старшее двойное слово
Младшее двойное слово
63473115■ 70
Упакованный тип данных
L
:
127956331О
Рис. 5.17. Основные типы данных процессора
Слово — последовательность из двух байтов, имеющих последовательные адреса. Размер слова — 16 битов; биты в слове нумеруются от 0 до 15. Байт, содержащий нулевой бит, называется младшим байтом, а байт, содержащий 15-й бит, — старшим. Процессоры Intel имеют важную особенность — младший байт всегда хранится по меньшему адресу. Адресом слова считается адрес его младшего байта. Адрес старшего байта может быть использован для доступа к старшей половине слова.
Двойное слово — последовательность из четырех байтов (32 бита), расположенных по последовательным адресам. Нумерация этих битов производится от О до 31. Слово, содержащее нулевой бит, называется младшим словом, а слово, содержащее 31-й бит, — старшим словом. Младшее слово хранится по меньшему адресу. Адресом двойного слова считается адрес его младшего слова. Адрес старшего слова может быть использован для доступа к старшей половине двойного слова.
я Учетверенное слово — последовательность из восьми байтов (64 бита), расположенных по последовательным адресам. Нумерация битов производится от О до 63. Двойное слово, содержащее нулевой бит, называется младшим двойным словом, а двойное слово, содержащее 63-й бит, — старшим двойным словом. Младшее двойное слово хранится по меньшему адресу. Адресом учетверенного слова считается адрес его младшего двойного слова. Адрес старшего двойного слова может быть использован для доступа к старшей половине учетверенного слова.
* 128-битный упакованный тип данных. Этот тип данных появился в процессоре Pentium III. Для работы с ним в процессор введены специальные команды.
Кроме трактовки типов данных с точки зрения их разрядности, процессор на уровне команд поддерживает логическую интерпретацию этих типов, как показано на рис. 5.18 (Зн означает знаковый бит).
ж Целый тип со знаком — двоичное значение со знаком размером 8,16 или 32 бита. Знак в этом двоичном числе содержится в 7, 15 или 31 бите соответственно. Ноль в этих битах в операндах соответствует положительному числу, а единица —
Указательближнеготипа
| 丁 : GRTe山EH^E :|
311570
Указатель дальнего типа
| СЕЛЕКТОР|.СМЕЩЕНИЕ
|
4731157
О
Типы данных сопроцессора
целые оез знакаI1СловоДвойное слово|311570Байтовая строкаI гДо 4 Гбайт1570Неупакованное десятичное (BCD) число10000; BCD I - : -1570целые оез знакаI1СловоДвойное слово|311570Байтовая строкаI гДо 4 Гбайт1570Неупакованное десятичное (BCD) число10000; BCD I - : -1570Целые со знакомСловоБайт[3н1Двойное слово(Зн||3н|31Битовое поле15701 : 1 : 1:Целые со знакомСловоБайт[3н1Двойное слово(Зн||3н|31Битовое поле15701 : 1 : 1:31157 О31157 ОУпакованное десятичное (BCD) число|bcd:bcd| - | - Jbcd;bcd|bcdWcd|157 ОУпакованное десятичное (BCD) число|bcd:bcd| - | - Jbcd;bcd|bcdWcd|157 О
целые оез знакаI1
Слово
Двойное слово|
311570
Байтовая строка
I г
До 4 Гбайт1570
Неупакованное десятичное (BCD) число
10000; BCD I - : -
1570
целые оез знакаI1
Слово
Двойное слово|
311570
Байтовая строка
I г
До 4 Гбайт1570
Неупакованное десятичное (BCD) число
10000; BCD I - : -
1570
Целые со знаком
Слово
Байт
[3н1
Двойное слово
(Зн|
|3н|
31
Битовое поле
15
70
1 : 1 : 1:
Целые со знаком
Слово
Байт
[3н1
Двойное слово
(Зн|
|3н|
31
Битовое поле
15
70
1 : 1 : 1:
31157 О
31157 О
Упакованное десятичное (BCD) число
|bcd:bcd| - | - Jbcd;bcd|bcdWcd|
157 О
Упакованное десятичное (BCD) число
|bcd:bcd| - | - Jbcd;bcd|bcdWcd|
157 О
Типы данных MMX (целочисленные)
| Типы данныхMMX (с плавающей точкой)
Рис. 5.18. Основные логические типы данных процессора
отрицательному. Отрицательные числа представляются в дополнительном коде. Числовые диапазоны для этого типа данных следующие:
8-разрядное целое — от -128 до +127;
16-разрядное целое — от -32 768 до +32 767;
D 32-разрядное целое — от -231 до +231 - 1.
:聲Целый тип без знака — двоичное значение без знака размером 8,16 или 32 бита. Числовой диапазон для этого типа следующий:
байт — от 0 до 255;
П слово — от 0 до 65 535;
П двойное слово— от 0 до 232 -
蹴 Указательна память бывает двух типов:
П ближний тип — 32-разрядный логический адрес, представляющий собой относительное смещение в байтах от начала сегмента; указатели подобного типа могут также использоваться в сплошной (плоской) модели памяти, где сегментные составляющие одинаковы;
П дальний тип — 48-разрядный логический адрес, состоящий из двух частей: 16-разряднойсегментной части (селектора) и 32-разрядного смещения.
Цепочка представляет собой некоторый непрерывный набор байтов, слов или двойных слов максимальной длиной до 4 Гбайт.
ж Битовое поле представляет собой непрерывную последовательность битов, в которой каждый бит является независимым и может рассматриваться как отдельная переменная. Битовое поле может начинаться с любого бита любого байта и содержать до 32 битов.
幾 Неупакованный двоично-десятичный тип — байтовое представление десятичной цифры от 0 до 9. Неупакованные десятичные числа хранятся как байтовые значения без знака по одной цифре в каждом байте. Значение цифры определяется младшим полубайтом.
缠 Упакованный двоично-десятичный тип представляет собой упакованное представление двух десятичных цифр от 0 до 9 в одном байте. Каждая цифра хранится в своем полубайте. Цифра в старшем полубайте (биты 4-7) является старшей.
經 Типы данных с плавающей точкой. Сопроцессор имеет несколько собственных типов данных, несовместимых с типами данных целочисленного устройства. Подробно этот вопрос обсуждается в главе 17.
* Типы данных MMX-расширения Pentium MMX/II/1II/I Данный тип данных появился в процессоре Pentium MMX. Он представляет собой совокупность упакованных целочисленных элементов определенного размера.
9 Типы данных MMX-расширения Pentium III/IV. Этот тип данных появился в процессоре Pentium III. Он представляет собой совокупность упакованных элементов с плавающей точкой фиксированного размера.
Описанные ранее данные можно определить как данные простого типа. Описать их можно с помощью специального вида директив — директив резервирования и инициализации данных. Эти директивы, по сути, являются указаниями транслятору на выделение определенного объема памяти. Если проводить аналогию с языками высокого уровня, то директивы резервирования и инициализации данных являются определениями переменных. Машинного эквивалента этим (впрочем, как и другим) директивам нет; просто транслятор, обрабатывая каждую такую директиву, выделяет необходимое количество байтов памяти и при необходимости инициализирует эту область некоторым значением. Формат директив резервирования и инициализации данных простых типов показан на рис. 5.19.
Выражение |Выражение |ВыважениеВыважениеЗначение мнмцуалкзоиммЗначение мнмцуалкзоиммРис. 5.19. Директивы описания данных простых типовРис. 5.19. Директивы описания данных простых типов
Выражение |
Выражение |
Выважение
Выважение
Значение мнмцуалкзоимм
Значение мнмцуалкзоимм
Рис. 5.19. Директивы описания данных простых типов
Рис. 5.19. Директивы описания данных простых типов
На рисунке использованы следующие обозначения.
* Знак вопроса (?) показывает, что содержимое поля не определено, то есть при задании директивы с таким значением выражения содержимое выделенного участка физической памяти изменяться не будет. Фактически, создается неинициализированная переменная.
Ж Значение инициализации — значение элемента данных, которое будет занесено в память после загрузки программы. Фактически, создается инициализированная переменная, в качестве которой могут выступать константы, строки символов, константные и адресные выражения в зависимости от типа данных.
緩 Выражение —итеративнаяконструкция, осинтаксисекоторойможносудитьпо рисунку. В частности, она позволяет повторить занесение в физическую память выражения в скобках столько раз, сколько повторений указано.
犠 Имя — некоторое символическое имя метки или ячейки памяти в сегменте данных, используемое в программе.
Далее представлены поддерживаемые TASM и MASM директивы резервирования и инициализации данных, а также информация о возможных типах и диапазонах значений, которые можно описывать или задавать с их помощью.
я DB — резервирование памяти для данных размером 1 байт. Директивой DBмож- но задавать следующие значения:
D выражение или константу, принимающую значение из диапазона-128...+127 (для чисел со знаком) или 0...255 (для чисел без знака);
□ 8-разрядное относительное выражение, использующее операции HIGH и LOW;
D символьную строку из одного или более символов, которая заключается в кавычки (в этом случае определяется столько байтов, сколько символов в строке).
til DW — резервирование памяти для данных размером два байта. Директивой DW можно задавать следующие значения:
□ выражение или константу, принимающую значение из диапазона -32 768...32 767 (для чисел со знаком) или 0...65 535 (для чисел без знака);
D выражение, занимающее 16 или менее битов, в качестве которого может выступать смещение в 16-битовом сегменте или адрес сегмента;
D 1- или 2-байтовая строка, заключенная в кавычки.
綫 DD — резервирование памяти для данных размером четыре байта. Директивой DDможнозадавать следующиезначения:
D выражение или константу, принимающую значение из диапазона -32 768...+32 767 (для чисел со знаком и процессора i8086), 0...65 535 (для чисел без знака и процессора i8086), -2 147 483 648…+2 147 483 647 (для чисел со знаком и процессора i386 и выше) или 0...4 294 967 295 (для чисел без знака и процессора i386 и выше);
□ относительное или адресное выражение, состоящее из 16-разрядного адреса сегмента и 16-разрядного смещения;
О строку длиной до 4 символов, заключенную в кавычки.
發 DF и DP — резервирование памяти для данных размером 6 байтов. Директивами DF и DPможнозадавать следующиезначения:
□ выражение или константу, принимающую значение из диапазона -32 768...+32 767 (для чисел со знаком и процессора i8086), 0…65 535 (для чисел без знака и процессора i8086), -2 147 483 648…+2 147 483 647 (для чисел со знаком и процессора i386 и выше) или 0...4 294 967 295 (для чисел без знака и процессора i386и выше);
П относительное или адресное выражение, состоящее из 32 или менее битов (для i80386) или 16 или менее битов (для первых моделей процессоров Intel);
П адресное выражение, состоящее из 16-разрядного сегмента и 32-разрядного смещения;
П константу со знаком из диапазона—247.27 - 1;
П константу без знака из диапазона 0...248 -
П строку длиной до 6 байтов, заключенную в кавычки.
蕊 DQ — резервирование памяти для данных размером 8 байтов. Директивой DQ можно задавать следующие значения:
Р выражение или константу, принимающую значение из диапазона -32 768...+32 767 (для чисел со знаком и процессора 18086), 0...65 535 (для чисел без знака и процессора i8086), -2 147 483 648…+2 147 483 647 (для чисел со знаком и процессора i386 и выше) или 0...4 294 967 295 (для чисел без знака и процессора i386 и выше);
П относительное или адресное выражение, состоящее из 32 или менее битов (для i80386) или 16или менее битов (для первых моделей процессоров Intel);
D константу co знаком из диапазона -263...263 - 1;
Р константу без знака из диапазона 0...264 - 1;
Р строку длиной до 8 байтов, заключенную в кавычки.
DT — резервирование памяти для данных размером 10 байтов. Директивой DT можно задавать следующие значения:
Р выражение или константу, принимающую значение из диапазона -32 768...+32 767 (для чисел со знаком и процессора i8086), 0...65 535 (для чисел без знака и процессора i8086), -2 147 483 648…+2 147 483 647 (для чисел со знаком и процессора i386 и выше) или 0...4 294 967 295 (для чисел без знака и процессора i386и выше);
Р относительное или адресное выражение, состоящее из 32 или менее битов (для i80386) или 16 или менее битов (для первых моделей);
Р адресное выражение, состоящее из 16-разрядного сегмента и 32-разрядного смещения;
Р константу со знаком из диапазона -279...279 - 1;
Р константу без знака из диапазона 0...280 - 1;
Р строку длиной до 10 байтов, заключенную в кавычки;
Р упакованную десятичную константу в диапазоне 0...99 999 999 999 999 999 999.
Заметим, что все директивы позволяют задавать строковые значения, но нужно помнить, что в памяти эти значения могут выглядеть совсем не так, как они были описаны в директиве. Причиной этому является упоминавшийся ранее принцип «младший байт по младшему адресу». Для определения строк лучше использовать директиву DB. Задаваемые таким образом строки должны заключаться в кавычки. Эти кавычки могут быть одинарными ('') или двойными (""). Если задать в строке подряд два таких ограничителя, то вторая кавычка (одинарная или двойная) будет частью строки.
Для иллюстрации принципа «младший байт по младшему адресу» рассмотрим листинг 5.3, в котором определим сегмент данных. В этом сегменте данных приведено несколько директив описания простых типов данных.
Листинг 5.3. Пример использования директив резервирования и инициализации данных
masm model small .stack 100h .data
message db "Запустите эту программу в отладчике",
perem_l db 0ffh
perem_2 dw 3a7fh
perem_3 dd 0f54d567ah
masdb 10 dup ("")
pole_l db 5 dup (?)
adrdw perem_3
adr_full"dd perem_3
fin db "Конец сегмента данных программы
.code
start:
mov ax,@data
mov ds,ax
mov ah,09h
mov dx .offset message
int21h
mov ax,4c00h
int 21h
endstart
ВНИМАНИЕЕсли при дальнейшем изложении вам что-то будет непонятно, можно по
рекомендовать на время прерваться и вновь вернуться к этому материалу после изучения главы 6.
Итак, наша цель — посмотреть, как выглядит в памяти компьютера сегмент данных программы, представленной в листинге 5.3. Это даст нам возможность обсудить практическую реализацию обозначенного нами принципа размещения данных.
Запустите отладчик td.exe, входящий в комплект поставки транслятора TASM.
Введите код, представленный в листинге 5.3, и сохраните его в виде файла с названием prg_5_3.asm. Все манипуляции с файлом будем производить в каталоге work, гдедолжны содержаться все необходимые для компиляции, компоновки и отладки файлы пакета TASM.
Запустите процесс трансляции файла следующей командой:
tasm.exe /zi prg_5__2. asm , , ,
После устранения синтаксических ошибок запустите процесс компоновки объектного файла:
/v
Теперь можно производить отладку:
td prg_5_2.exe
Если все было сделано правильно, то в отладчике откроется окнос ис
ходным текстом программы. Для того чтобы с помощью отладчика просмотреть область памяти, содержащую наш сегмент данных, необходимо открыть окно Dump. Это делается с помощью команды View Dump главного меню.
Но одного открытия окна недостаточно, нужно еще настроить его на адрес начала сегмента данных. Этот адрес должен содержаться в сегментном регистре DS, но перед началом выполнения программы адрес в DS не соответствует началу сегмента данных. Нужно перед первым обращением к любому символическому имени произвести загрузку действительного физического адреса сегмента данных. Обычно это действие не откладывают и производят первыми двумя командами в сегменте кода. Действительный физический адрес сегмента данных извлекают как значение предопределенной переменной @data. В нашей программе эти действия выполняют команды mov ax,@data mov ds,ax
Длячтобы посмотреть содержимое нашего сегмента данных, нужно оста
новить выполнение программы после этих двух команд. Это можно сделать, если перевести отладчик в пошаговый режим с помощью клавиши F7 или F8. Нажмите два раза клавишу F8. Теперь можно открыть окно Dump.
В окне Dump вызовите контекстное меню, щелкнув правой кнопкой мыши, и выберите команду Goto. Появится диалоговое окно, в котором нужно ввести начальный адресначиная с которого информация будет выводиться в окне
Синтаксис задания этого адреса должен соответствовать синтаксису задания адресного операнда в программе на ассемблере. Если нужно увидеть содержимое памяти для сегментаначиная с начала, введите(рис. 5.20). Для
удобства, если сегмент довольно велик, это окно можно развернуть на весь экран. Для этого нужно щелкнуть на значке в виде стрелкив правом верхнем углу
окна Dump.
На рисунке представлено содержание сегмента данных программы из листинга 5.3 в двух представлениях: шестнадцатеричном и символьном. Видно, со смещением 0000 расположены символы, входящие в строку message. Она занимает 34 байта. После нее следует байт, имеющий в сегменте данных символическое имя perem_l, содержимое этого байта —Теперь обратите внимание на то, как раз
мещены в памяти байты, входящие в слово, обозначенное символическим именем perem_2. Сначала следует байт со значением 7fh, а затем — со значением 3ah. Как видим, в памяти действительно сначала расположен младший байт значения, а затем старший. Теперь проанализируйте порядок размещения байтов для поля, обозначенного символическим именем perem_3. Оставшаяся часть сегмента данных не представляет трудности для самостоятельного анализа. Есть смысл остановиться лишь на двух специфических особенностях использования директив резервиро-
ЯПЯП—Module: p=g$_2 File: C:\TASM\WORK\prg5^2*азш 10-"1—Module: p=g$_2 File: C:\TASM\WORK\prg5^2*азш 10-"1收搭Ri舞收搭Ri舞••••••.datamessage pecem 1 pe亡em_2 perem_3 mas db.datamessage pecem 1 pe亡em_2 perem_3 mas dbdbэту программу в отладчике Тdbэту программу в отладчике Тpole 1 adcdwadc full findb.codestact:mov mov movint mov intpole 1 adcdwadc full findb.codestact:mov mov movint mov intdb dw dd 10 dbdb dw dd 10 dbOffh 3a7fhOf54d567ah dup')5 dup (?)Offh 3a7fhOf54d567ah dup')5 dup (?)pecem 3dd pecem 3тКонец сегментаpecem 3dd pecem 3тКонец сегментаax,©datads , axah,D9hdx^offset messa2ax,4c00h2ax,©datads , axah,D9hdx^offset messa2ax,4c00h2■[■]・Dump ds:0000 ds 0008 ds 0010 ds 0018 ds 0020 ds 0028 ds 0030 ds 0038 ds 0040 ds 0048 ds 0050■[■]・Dump ds:0000 ds 0008 ds 0010 ds 0018 ds 0020 ds 0028 ds 0030 ds 0038 ds 0040 ds 0048 ds 005087AS AE А2 А8 56 20 008А A3 АО87AS AE А2 А8 56 20 008А A3 АОАО 2U A3 20AA4D 20 00 АЕ АС ADАО 2U A3 20AA4D 20 00 АЕ АС ADAF ED EO AE A5 F5 20 27 ADA5ADAF ED EO AE A5 F5 20 27 ADA5ADE3E2АО E2 24 20 20 00 A5 AD EBE3E2АО E2 24 20 20 00 A5 AD EBEl E3 AC AB FF 20 20 27 E6 E2 E5El E3 AC AB FF 20 20 27 E6 E2 E5E2 20 AC АО 7F 20 00 00 20 АО 20E2 20 AC АО 7F 20 00 00 20 АО 20A8 AF E3 А4 ЗА 20 00 42 El 20 AFA8 AF E3 А4 ЗА 20 00 42 El 20 AFE2EO 20 E7 7A 20 00IB A5A4 EOE2EO 20 E7 7A 20 00IB A5A4 EO2TНП〕Запустит e эту пр ограмму в отладчD:zVMl2TНП〕Запустит e эту пр ограмму в отладчD:zVMl''се дпр''се дпрendstactendstact二侬=[…二魁[质二侬=[…二魁[质,舒显编j Paint,舒显编j Paint844844
ЯП
ЯП
—Module: p=g$_2 File: C:\TASM\WORK\prg5^2*азш 10-"1
—Module: p=g$_2 File: C:\TASM\WORK\prg5^2*азш 10-"1
收搭Ri舞
收搭Ri舞
•
•
•
•
•
•
.data
message pecem 1 pe亡em_2 perem_3 mas db
.data
message pecem 1 pe亡em_2 perem_3 mas db
dbэту программу в отладчике Т
dbэту программу в отладчике Т
pole 1 adcdw
adc full findb
.codestact:
mov mov mov
int mov int
pole 1 adcdw
adc full findb
.codestact:
mov mov mov
int mov int
db dw dd 10 db
db dw dd 10 db
Offh 3a7fh
Of54d567ah dup')
5 dup (?)
Offh 3a7fh
Of54d567ah dup')
5 dup (?)
pecem 3
dd pecem 3
тКонец сегмента
pecem 3
dd pecem 3
тКонец сегмента
ax,©data
ds , ax
ah,D9h
dx^offset messa
2
ax,4c00h
2
ax,©data
ds , ax
ah,D9h
dx^offset messa
2
ax,4c00h
2
■[■]・Dump ds:0000 ds 0008 ds 0010 ds 0018 ds 0020 ds 0028 ds 0030 ds 0038 ds 0040 ds 0048 ds 0050
■[■]・Dump ds:0000 ds 0008 ds 0010 ds 0018 ds 0020 ds 0028 ds 0030 ds 0038 ds 0040 ds 0048 ds 0050
87
AS AE А2 А8 56 20 00
8А A3 АО
87
AS AE А2 А8 56 20 00
8А A3 АО
АО 2U A3 20
AA
4D 20 00 АЕ АС AD
АО 2U A3 20
AA
4D 20 00 АЕ АС AD
AF ED EO AE A5 F5 20 27 AD
A5
AD
AF ED EO AE A5 F5 20 27 AD
A5
AD
E3
E2
АО E2 24 20 20 00 A5 AD EB
E3
E2
АО E2 24 20 20 00 A5 AD EB
El E3 AC AB FF 20 20 27 E6 E2 E5
El E3 AC AB FF 20 20 27 E6 E2 E5
E2 20 AC АО 7F 20 00 00 20 АО 20
E2 20 AC АО 7F 20 00 00 20 АО 20
A8 AF E3 А4 ЗА 20 00 42 El 20 AF
A8 AF E3 А4 ЗА 20 00 42 El 20 AF
E2
EO 20 E7 7A 20 00
IB A5
A4 EO
E2
EO 20 E7 7A 20 00
IB A5
A4 EO
2TНП〕
Запустит e эту пр ограмму в отладч
D:z
VMl
2TНП〕
Запустит e эту пр ограмму в отладч
D:z
VMl
''
се д
пр
''
се д
пр
endstact
endstact
二侬=[…二魁[质
二侬=[…二魁[质
,舒显编j Paint
,舒显编j Paint
844
844
Рис. 5.20. Окно дампа памяти
вания и инициализации памяти. Речь идет об указании в поле операндов директив DW и DD идентификатора из поля имени этой или другой директивы резервирования и инициализации памяти. В нашем примере сегмента данных это директивы с идентификаторами adr иКогда транслятор встречает директивы описа
ния памяти с подобными операндами, то он формирует в памяти значения адресов тех переменных, чьи идентификаторы указаны в качестве операндов. В зависимости от директивы, применяемой для получения такого адреса, формируется либо полный адрес (директива DD) в виде двух байтов сегментного адреса и двух байтов смещения, либо только смещение (директива dw). Для тренировки найдите в представленном дампе поля, соответствующие идентификаторам adr и adr_full, и проанализируйте их содержимое.
Любой переменной, объявленной с помощью директив описания простых типов данных, ассемблер присваивает три атрибута:
сегмент (seg) — адрес начала сегмента, содержащего переменную;
смещение (offset) в байтах от начала сегмента с переменной;
тип (type) — объем памяти, выделяемой переменной в соответствии с директивой объявления переменной.
Получить и использовать значение этих атрибутов в программе можно с помощью операторов ассемблера SEG, OFFSET и TYPE.
В заключение отметим, что в языке ассемблера существуют средства для описания сложных типов данных, основой которых являются описанные в этой главе простые типы данных. Подробному обсуждению сложных типов данных посвящена глава 13.
Итоги
挞 Программа на ассемблере, отражая особенности архитектуры процессора, состоит из сегментов — блоков памяти, допускающих независимую адресацию.
Каждый сегмент может состоять из предложений языка ассемблера четырех типов: командассемблера, макрокоманд,директивассемблераистроккоммен- тариев.
Формальное описание синтаксиса языков программирования, в том числе ассемблера, удобновыполнять сиспользованиемтакихметасинтаксическихязы- ков, как синтаксические диаграммы и нормальные формы Бэкуса-Наура. Синтаксические диаграммы более наглядны, а расширенные формы Бэкуса-Наура более компактны.
Предложения ассемблера формируются из лексем, представляющих собой синтаксически неразделимые последовательности допустимых символов языка, имеющие смысл для транслятора.
Ассемблер допускает большое разнообразие типов операндов, которые могут задаваться неявно или содержаться непосредственно в команде, в регистрах и в памяти. Вдвухоперандной машинной команде возможны следующие сочетания операндов:
□ регистр — регистр;
D регистр — память;
D память — регистр;
D непосредственный операнд — регистр;
П непосредственный операнд — память.
総Операндами в команде могут быть числа, регистры, ячейки памяти, символьные идентификаторы. При необходимости операнд может быть задан выражением.
工 Ассемблер позволяет организовать гибкую прямую и косвенную адресацию операндов в памяти.
Исходный текст программы разбивается на сегменты с помощью директив сегментации, которые делятся на стандартные и упрощенные.
缠 Упрощенные директивы сегментации позволяют унифицировать интерфейс с языками высокого уровня и облегчают разработку программ, повышая наглядность кода.
緩 Транслятор TASM поддерживает разнообразные типы данных, которые делятся на простые (базовые) и сложные. Простые типы служат основой для постро- ениясложныхтиповданных.
III Директивы описания простых типов данных позволяют резервировать и при необходимости инициализировать области памяти заданной длины.
趣Доступ к данным в памяти должен производиться с учетом принципа их размещения процессорами IA-32 — «младший байт по младшему адресу».
綴Каждой переменной, объявленной с помощью директивы описания данных, ассемблер назначает атрибуты, доступ к которым можно получить с помощью соответствующих операторов ассемблера.
Глава 6
Первая программа
► Жизненный цикл программы на ассемблере
> Пример простой программы
孰 Разработка программ на ассемблере с использованием пакета TASM
擲1 Разработка программ на ассемблере с использованием пакета MASM
檢Трансляция и компоновка программы
^ Назначение и структура выходных файлов, формируемых транслятором
Отладка программ
Менеджер проекта — утилита МАКЕ
В этой главе мы, во-первых, познакомимся с процессом разработки программ на ассемблере, во-вторых, научимся использовать специальные программные средства, предназначенные для преобразования исходных текстов на ассемблере в машинные коды для их исполнения компьютером.
Жизненный цикл программы
Процесс разработки программы на ассемблере, включая постановку задачи, получение первых результатов и дальнейшее сопровождение программы, мало чем отличается от традиционного подхода с использованием любого из существующих языков программирования. После адаптации основных положений этого процесса к особенностям ассемблера получится вполне стандартный перечень работ.
Постановка и формулировка задачи:
□ изучение предметной области и сбор материала в проблемно-ориентированном контексте;
П определение назначения программы, выработка требований к ней и представление требований, если возможно, в формализованном виде;
П формулирование требований к представлению исходных данных и выходных результатов;
D определение структур входных и выходных данных;
П формирование ограничений и допущений на исходные и выходные данные.
Этап проектирования:
П формирование «ассемблерной» модели задачи;
П выбор метода реализации задачи;
П разработка алгоритма реализации задачи;
D разработка структуры программы в соответствии с выбранной моделью памяти.
Этап кодирования:
П уточнение структуры входных и выходных данных и определение ассемблерного формата их представления;
П программирование задачи;
D комментирование текста и составление предварительного описания программы.
Этап отладки и тестирования:
□ составление тестов для проверки работоспособности программы;
П обнаружение, локализация и устранение в программе ошибок, выявленных в тестах;
П корректировка кода программы и ее описания.
Этап эксплуатации и сопровождения:
а настройка программы на конкретные условия использования;
П обучение пользователей работе с программой;
П организация сбора сведений о сбоях в работе программы, ошибках в выходных данных, пожеланиях по улучшению интерфейса и удобства работы с программой;
П модификация программы с целью устранения выявленных ошибок и, при необходимости, изменения ее функциональных возможностей.
Порядок и объем работ в приведенном перечне укладываются в понятие жизненного цикла программы на ассемблере. На практике к порядку применения и иол- ноте выполнения перечисленных этапов нужно подходить разумно и творчески. Многое определяется особенностями конкретной задачи, ее назначением, объемами кода и обрабатываемых данных, другими характеристиками исходной задачи. Некоторые из этих этапов могут либо выполняться одновременно с другими, либо
вовсе отсутствовать. Главная цель формирования подобного списка работ — в том, чтобы изначально упорядочить процесс создания нового программного продукта с сохранением концептуальной целостности постановки задачи и исключением анархии в процессе разработки.
Пример простой программы
В данном разделе рассмотрим пример простой, но полноценной программы на ассемблере. В главе 2 при обсуждении архитектуры было перечислено большое количество регистров. Как правило, большинство из них задействовано практически в любой программе. Было бы интересно во время работы программы посмотреть (получить) их содержимое. Это нетрудно, если использовать специальную программу — отладчик. Но как сделать это динамически и автономно (без помощи других программ)? Или, к примеру, как решить обратную задачу — ввести некоторое значение с клавиатуры в регистр? Эта и подобная ей задачи являются задачами преобразования данных. Они довольно часто возникают на практике. Причина здесь в том, что компьютер воспринимает данные только тех типов, которые поддерживаются его системой команд. Поэтому на практике часто возникает необходимость преобразования данных из одного представления в другое. В этой главе в качестве примера рассмотрим частный случай решения одной из таких задач — задачи преобразования шестнадцатеричного числа из двух цифр, вводимого с клавиатуры (то есть в символьном виде), в двоичное число. После выполнения этой операции полученное число можно использовать как операнд в двоичных арифметических операциях.
Для начала нужно продумать алгоритм и изучить предметную область. Ввод информации с клавиатуры и вывод ее на экран осуществляются в символьном виде. Кодирование этой информации производится согласно таблице ASCII. В таблице ASCII каждый символ кодируется одним байтом. Если работа происходит с числами, то при попытке их обработать сразу возникает проблема: команд для арифметической обработки чисел в символьном виде нет. Что делать? Выход очевиден: нужно преобразовать символьную информацию в формат, поддерживаемый машинными командами. После такого преобразования нужно выполнить необходимые вычисления и преобразовать результат обратно в символьный вид. Затем следует отобразить информацию на мониторе.
Рассматриваемая в этом разделе программа является одним из вариантов решения задачи. В основу ее алгоритма положена особенность, связанная с ACSII- кодами символов соответствующих шестнадцатеричных цифр. Шестнадцатеричные цифры формируются из символов 9, А, В, С, D, E, F, а,Ь,с, d, e, f, например: 12Af, 34ad. В таблице ASCII можно найти значения ASCII-кодов, соответствующие этим символам. На первый взгляд, непонятна популярность способа представления информации в виде шестнадцатеричных чисел. Из предыдущих глав известно, что аппаратура компьютера построена на логических микросхемах и работает только с двоичной информацией. Если нам требуется проанализировать, например, состояние некоторой области памяти, то разобраться в нагромождении нулей и единиц будет очень непросто. Для примера рассмотрим двоичную последовательность
010100010101011110101101110101010101000101001010.
Это представление не очень наглядно. В главе 2 отмечалось, что оперативная память состоит из ячеек — байтов — по 8 битов. Приведенная выше цепочка битов при разбиении ее на байты будет выглядеть так:
01010001 01010111 10101101 11010101 01010001 01001010.
С наглядностью стало лучше, но если исследуемая область памяти окажется больше, то разобраться будет все равно сложно. Проведем еще одну операцию: каждый байт разобьем на две части по 4 бита — тетрады:
0101 0001 0101 0111 1010 1101 1101 0101 0101 0001 0100 1010.
И вот тут проявляется замечательное свойство шестнадцатеричных чисел — каждой тетраде можно поставить в соответствие одну шестнадцатеричную цифру (табл. 6.1).
Таблица 6.1. Кодировка шестнадцатеричных цифр
Шестнадцатеричная цифра
ASCII-код
(двоичноепредставление)
Двоичная тетрада
0
30h (00110000)
0000
1
31h (ООН 0001)
0001
2
32h (00110010)
0010
3
33h (ООН ООП)
ООП
4
34h (ООП 0100)
0100
5
35h (00110101)
0101
6
36h (00110110)
оно
7
37h (ООН 0111)
0111
8
38h (ООН 1000)
1000
9
39h (ООН 1001)
1001
А,а
41h(01000001),61h(ОНО 0001)
1010
в,ь
42h (0100 0010), 62h (0110 0010)
1011
с, с
43h (0100 ООН), 63h (0110 0011)
1100
D,d
44h(01000100),64h(01100100)
1101
Е, е
45h (0100 0101), 65h (0100 0101)
1110
F.f
46h (0100 0110), 66h (0110 0110)
1111
Если заменить в последней полученной строке тетрады соответствующими шестнадцатеричными цифрами, то получим последовательность 51 57 ad d5 51 8а.
Каждый байт теперь наглядно представлен двумя шестнадцатеричными цифрами, что оказывается очень удобным, в частности, при работе с отладчиком.
Вернемся к нашей задаче. Если посмотреть внимательно на представление шестнадцатеричных цифр и их кодировку в ACSII-коде (табл. 6.1), то можно увидеть полезные закономерности. К примеру, ASCII-код нуля (код символа 0) в шестнадцатеричном виде равен 30h. В двоичном представлении 30h записывается как
ООН 0000, а соответствующее двоичное число должно быть двоичным представлением нуля 0000 0000. Отсюда следуют некоторые выводы.
Вывод первый. Для шестнадцатеричных цифр 0...9 ACSII-код отличается от соответствующего двоичного представления на ООН 0000, или 30h. Поэтому для преобразования кода символа шестнадцатеричной цифры в соответствующее двоичное число есть два пути:
後 выполнить двоичное вычитание: (АС811-код)Ь - 30h;
Ж обнулить старшую тетраду байта с символом шестнадцатеричной цифры в ACSII-коде.
Видно, что с шестнадцатеричными цифрами в диапазоне от 0 до 9 все просто. Что касается шестнадцатеричных цифр а, Ь, с, d, e, f, то здесь ситуация несколько сложнее. Алгоритм должен распознавать эти символы и производить дополнительные действия при их преобразовании в соответствующее двоичное число. Внимательный взгляд на символы шестнадцатеричных цифр и соответствующие им двоичные представления (см. табл. 6.1)говорит о том, для преобразования уже недостаточно простого вычитания или обнуления старшей тетрады. Анализ таблицы ASCII показывает, что символы прописных букв шестнадцатеричных цифр отличаются от своего двоичного эквивалента на величину 37h. Соответствующие строчные буквы шестнадцатеричных цифр отличаются от своего двоичного эквивалента на 67h.
Отсюда следует вывод второй. Алгоритм преобразования должен различать Прописные и строчные буквенные символы шестнадцатеричных цифр и корректировать значение ASCII-кода на величину 37h или 67h.
Обратите внимание на то, что после записи значения шестнадцатеричной цифры следует символ «ь». Как упоминалось в главе 4, это сделано для того, чтобы транслятор мог различить в программе одинаковые по форме записи десятичные и шестнадцатеричные числа.
В листинге 6,1 приведен исходный текст программы, которая решает задачу преобразования двузначного шестнадцатеричного числа в символьном виде в двоичное представление.
Листинг6.1. Пример программы на ассемблере
<1>
<2>
<3>
<4>
<5>
<б>
<7>
<9>
<10>
<11>
<12>
<13>
<14>
<15>
<16>
<17>
:Prg_6_l. asm:
Программа преобразования двузначного шестнадцатеричного числа ;в символьном виде в двоичное представление.
;Вход: исходное шестнадцатеричное число из двух цифр, ;вводится с клавиатуры.
;Выход: результат преобразования помещается в регистр dL
data mess data stk
stk code main
segment para public "data";сегмент данных
age db"Введите две шестнадцатеричные цифры.$"
ends
segment stack
db 256 dup ("?");сегмент стека
ends
segment para public "code"начало сегмента
procначало процедуры
assume cs:code,ds:data,ss:stk
кода main
<18>mov ах,data;адрес сегмента данных в De「wcTD ах
продолжение 身
Листинг 6.1 (продолжение)
<19>
<20>
mov ds,ах mov ah, 9
;ax в ds
<21>
<22>
raov dx .offset int 21h
message
<23>
xor ax .ax
очистить регистр ах
<24>
mov ah.lh
;Ih в регистр ah
<25>
int 21h
генерация прерывания с номером 21h
<2б>
mov dl, al
содержимое регистра al в регистр dl
<27>
sub dl( 30h
вычитание: (dl)=(dl)-30h
<28>
cmp dl 9h
сравнить (dl) с 9h
<29>
jMl
перейти на метку Ml, если dl<9h или dl=9h
<30>
sub dl,7h
вычитание: (dl)=(dl)-7h
<31>
Ml:
определение метки Ml
<32>
mov cl,4h
пересылка 4h в регистр cl
<33>
shl dl.cl
сдвиг содержимого dl на 4 разряда влево
<34>
int 21h
вызов прерывания с номером 21h
<35>
sub al,30h
вычитание: (dl)=(dl)-30h
<36>
cmp al 9h
сравнить (al) с 9h
<37>
jle M2
перейти на метку М2, если al<9h или al=9h
<38>
sub al ,7h
вычитание: (al)=(al)-7h
<39>
М2:
определение метки М2
<40>
add dl.al
сложение: (dl)=(dl)+(al)
<41>
mov ax ,4c00h
пересылка 4c00h в регистр ах
<42>
int 21h
вызов прерывания с номером 21h
<43>
main endp
конец процедуры main
<44>
code ends
конец сегмента кода
<45>
end main
конец программы с ^точкой входа main
В предыдущих главах говорилось, что процессор аппаратно поддерживает шесть адресно-независимых областей памяти: сегмент кода, сегмент данных, сегмент стека и три дополнительных сегмента данных. Наша программа использует только первые три из них.
Строки 9-11 определяют сегмент данных. В строке 10 описана текстовая строка с сообщением «Введите две шестнадцатеричные цифры».
Строки 12-14 описывают сегмент стека, который является просто областью памяти длиной 256 байт, инициализированной символами «"?"». Отличие сегмен-
та стека от сегментов других типов состоит в использовании и адресации памяти. В отличие от сегмента данных (наличие которого необязательно, если программа не работает с данными), сегмент стека желательно определять всегда.
Строки 15-44 содержат сегмент кода. В этом сегменте в строках 16-43 определена одна процедура main.
Строка 17 содержит директиву ассемблера, которая связывает сегментные регистры с именами сегментов.
Строки 18-19 выполняют инициализацию сегментного регистра DS.
Строки 20-22 выводят на экран сообщение message:
Введите две шестнадцатеричные цифры
Строка 23 подготавливает регистр АХ к работе, обнуляя его. Содержимое АХ после этой операции следующее:
ах = 0000 0000 0000 0000
Строки 24-25 обращаются к средствам операционной системы для ввода символа с клавиатуры. Введенный символ операционная система помещает в регистр AL. К примеру, пусть в ответ на сообщение с клавиатуры были введены две шестнадцатеричные цифры: 5С
В результате после отработки команды в строке 25 появится один символ в ASCII-коде — 5, и состояние регистра АХ станет таким: ах = 0000 0001 0011 0101
Строка 26 пересылает содержимое AL в регистр DL. Это делается для того, чтобы освободить AL для ввода второй цифры. Содержимое регистра DX после этой пересылки следующее:
dx = 0000 0000 0011 0101
Строка 27 преобразует символьную цифру 5 в ее двоичный эквивалент путем вычитания 30h, в результате чего в регистре DL будет двоичное значение числа 5:
dx = 0000 0000 0000 0101
В строках 28-29 выясняется, нужно ли корректировать двоичное значение в DL. Если оно лежит в диапазоне 0...9, то в DL находится правильный двоичный эквивалент введенного символа шестнадцатеричнойцифры. Если значение в DL больше 9, то введенная цифра является одним из символов А, В, С, D, E, F (строчные буквы для экономии места обрабатывать не будем). В первом случае строка 29 передаст управление на метку Ml. При обработке цифры 5 это условие как раз выполняется, поэтому происходит переход на метку Ml (строка 31).
Каждая шестнадцатеричная цифра занимает одну тетраду. У нас две таких цифры, поэтому нужно их разместить так, чтобы старшинство разрядов сохранялось. В строках 32-33 значение в DL сдвигается на 4 разряда влево, освобождая место в младшей тетраде под младшую шестнадцатеричную цифру.
В строке 34 в регистр AL вводится вторая шестнадцатеричная цифра С (ее ASCII- код — 63h):
ах = 0000 0001 0100 0011
В строках 35-37 выясняется, попадает ли двоичный эквивалент второго символа шестнадцатеричной цифры в диапазон 0...9. Наша вторая цифра не попадает в диапазон, поэтому для получения правильного двоичного эквивалента нужно произвести дополнительную корректировку. Это делается в строке 38. Состояние AL после выполнения строки 35 следующее: ах = 0000 0001 0001 0011
В AL записано число 13h, а нужно, чтобы было OCh (помните о правилах записи шестнадцатеричныхчисел!). Так как OCh не попадает в диапазон 0...9, то происходит переход на строку 38. В результате работы команды вычитания в регистре AL получается правильное значение (al) = ах = 0000 0001 0000 1100
И наконец, в строке 40 сдвинутое значение в DL складывается с числом в AL: dx = 0000 0000 0101 0000
+
ах = 0000 0001 0000 1100
dx0000 0000 0101 1100
Таким образом, в регистре DL получен двоичный эквивалент двух введенных символов, изображающих двузначное шестнадцатеричное число:
(dl) =
Строкипредназначены для завершения программы и возврата управле
ния операционной системе.
Этот пример не случайно рассмотрен столь детально. Он отражает многие специфические особенности программирования на ассемблере. На его основе построен материал следующего раздела, посвященный тому, как из исходного модуля получить правильно функционирующий исполняемый модуль.
Процесс разработки программы
На рис.приведена общая схема процесса разработки программы на ассембле
ре. Название программы соответствует рассмотренному ранее примеру программы (см. листинг 6.1). На схеме выделено четыре этапа этого процесса. На первом этапе, когда вводится код программы, можно использовать любой текстовый редактор. В Windows таким редактором может быть Блокнот (Notepad). При выборе редактора нужно учитывать, что он не должен вставлять «посторонних» символов (специальных символов форматирования). С этой точки зрения Microsoft Word в качестве основного редактора ассемблерных программ не годится. Очень интересный редактор — Asm Editor for Windows (http://www.avtlab.ru). Созданный с помощью текстового редактора файл должен иметь расширение
ИСХОДНОГО ТЕКСТА ПРОГРАММЫ
Рис. Схема процесса разработки программы на ассемблере
Для выполнения остальных этапов разработки требуются специализированные программные средства из пакета MASM или TASM. В ходе настоящего обсуждения будут описываться средства обоих пакетов, но в основном на примере TASM, поскольку процесс разработки ассемблерных Программ с использованием этого
пакета более нагляден. В принципе, все пакеты ассемблера выполняют практически одну работу, но по-разному, например, маскируют ее с помощью интегрированный среды или объединяют некоторые этапы разработки. Поняв суть преобразований исходной программы, выполняемых пакетом TASM, освоить другие пакеты ассемблера будет на порядок легче и.
Трансляция программы
Следующий шаг на пути создания исполняемого модуля — трансляция программы. Для трансляции нужен подготовленный и записанный на диск исходный текст программы (см. листинг 6.1).
На этапе трансляции решается несколько задач:
綫 перевод команд ассемблера в соответствующие машинные команды;
построение таблицы символов;
遂 расширение макросов;
is формирование файла листинга и объектного модуля.
Программа, которая реализует эти задачи, называется ассемблером. Итог работы ассемблера — два файла: файл объектного модуля и файл листинга.
Объектный модуль включает в себя представление исходной программы в машинных кодах и некоторую другую информацию, необходимую для отладки и компоновки его с другими модулями. При использовании пакета TASM получение объектного модуля исходного файла производится программой (ассемблером) tasm.exe. Формат командной строки для запуска tasm.exe следующий: TASM [ключи] имя_исходнр го_файла [,имя_объектного_файла] [,имя_файла_листинга] [, имя^_файла_перекрестных_ссылок]
Помнить формат командной строки tasm.exe необязательно. Получить быструю справку о нем на экране можно, если запустить tasm.exe без параметров. Обратите внимание нато, что большинство параметров заключено в квадратные скобки. Это общепринятое соглашение по обозначению необязательных параметров. Из этого следует, что обязательным аргументом командной строки является лишь имя исходного файла. Этот файл должен находиться на диске и обязательно иметь расширение .asm. За именем исходного файла через запятую могут следовать необязательные параметры, обозначающие имена объектного файла, файла листинга и файла перекрестных ссылок. Если не задать их, то соответствующие файлы попросту не будут созданы. Если же их нужно создать, то необходимо учитывать некоторые моменты.
勰 Если имена объектного файла, файла листинга и файла перекрестных ссылок должны совпадать с именем исходного файла (наиболее типичный случай), то нужно просто поставить запятые вместо имен этих файлов:
tasm.exe prg_6_l , , ,
В результате будут созданы файлы с одинаковыми именами и разными расширениями, как показано на рис. 6.1 для второго этапа.
纖 Если имена объектного файла, файла листинга и/или файла перекрестных ссылок не должны совпадать с именем исходного файла, то нужно в командной строке указать имена нужных файлов в соответствующем порядке, к примеру:
5 Зак. 256
tasm.exe prg_6_l , ,prg_list,
В результате на диске будут созданы файлы prg„6_l.obj, prg_list.Lst, prg_6__l.crf.
Если требуется выборочное создание файлов, то вместо ненужных файлов необходимо подставить параметр nuL Например:
tasm.exe prg_6_l , , nul,
В результате на диске будут созданы файлы prg_6_l.obj, prg_6_l.crf.
Необязательный аргумент [ключи] позволяет задавать режим работы транслятора TASM. Этих ключей достаточно много, и все они описаны в приложении В (http://www.piter.com/download).
Перед работой с программой tasm.exe желательно провести некоторые подготовительные операции. После установки пакета TASM в каталоге \TASM\BIN, где находится файл tasm.exe, присутствует большое количество файлов. Можно запустить программу tasm.exe прямо отсюда, но тогда созданные ею файлы объектного кода, листинга и перекрестных ссылок тоже окажутся в этом каталоге. Если вы собирается написать всего одну программу, то неудобство не столь заметно, но при работе с несколькими программами очень скоро этот каталог станет похожим на свалку. Чтобы избежать подобной ситуации, рекомендуется выполнить описанную далее процедуру.
Создайте в каталоге. .\TASM вложенные каталоги . .\WORKи . •\PROGRAM. Каталог
..\PROGRAM будет использоваться для хранения отлаженных кодов программ и их исполняемых модулей (файлов с расширением .ехе). Каталог ..\WORKста- нет рабочим — в нем будут находиться необходимые для получения исполняемого модуля файлы из пакета транслятора TASM и файл исходного модуля, с которым в данный момент ведется работа. После устранения ошибок в исходном модуле его вместе с исполняемым модулем можно будет переписать в каталог . .\PROGRAM, а изкаталога..\WORKудалить все ненужные файлы, чтобы подготовить его для работы со следующим исходным модулем на ассемблере. Таким образом, в каталоге • •'WORK всегда будет находиться рабочая версия программы, а в каталоге ..\PROGRAM — отлаженная версия.
Поместите в каталог ..\WORKфайлы tasm.exe, tlink.exe и rtm.exe. Если в дальнейшем в каталоге ..\WORKне окажется каких-то рабочих файлов вашей программы, программы tasm.exe и tlink.exe выдадут соответствующее сообщение.
Поместите в каталог. .\WORK файл prg_6_l.asm.
После всех этих действий можно начинать работу. Для этого следует перейти в каталог • •'WORK и инициировать трансляцию программы prg_6_Lasm командной строкой вида
tasm.exe /zi prg_6_l , , .
В результате на экране появится последовательность строк. Самая первая из них будет содержать информацию о номере версии пакета TASM, который использовался для трансляции данной программы. В следующей строке будет выведено имя транслируемого файла. Если программа содержит ошибки, то транслятор выдаст на экран строки сообщений, начинающиеся словами «Error» и «Warning». Хотя программа из листинга 6.1 синтаксически правильная, в учебных целях в нее можно внести какую-нибудь бессмыслицу и посмотреть, что получится. Наличие строки со словом «Error» будет говорить о том, что в программе есть недопустимые с точ-
ки зрения синтаксиса комбинации символов. Логика работы программы для транслятора не имеет никакого значения. Можно написать абсолютную чушь, но если она синтаксически правильна, транслятор поспешит обрадовать пользователя, сообщив ему об отсутствии ошибок. Наличие строки «Warning» означает, что конструкция синтаксически правильна, но не соответствует некоторым соглашениям языка, и это может служить источником ошибок в будущем.
Для устранения ошибок нужно определить место их возникновения и проанализировать ситуацию. Местоположение ошибки легко определяется по значению в скобках в сообщении об ошибке. Это значение является номером ошибочной строки. Запомнив его, нужно перейти в файл с исходной программой и по номеру строки найти место ошибки. Этот способ локализации ошибок имеет недостатки. Во-первых, он не нагляден. Во-вторых, не всегда номера строк в сообщении соответствуют действительным номерам ошибочных строк в исходном файле. Такая ситуация будет наблюдаться, например, при использовании макрокоманд. В этом случае транслятор вставляет в файл дополнительные строки в соответствии с описанием применяемой макрокоманды, а в результате происходит сбой в нумерации. По этим соображениям для локализации ошибок лучше использовать информацию из специального создаваемого транслятором файла листинга. Этот файл имеет расширение .1st, а его имя определяется в соответствии с рассмотренными выше соглашениями. В листинге 6.2 приведен полный формат файла листинга для программы, содержащей некоторые ошибки. Файл листинга — это всегда очень важный документ, и ему нужно уделить должное внимание.
Листинг 6.2. Пример файла листинга
Turbo Assembler Version 4.102/03/98 21:23:43 Page 1
Prg_6_l.asm
—;=Pгg__6_ 1. asm
Программа преобразования двузначного шестнадцатеричного числа
;в символьном виде в двоичное представление.
;Вход: исходное шестнадцатеричное число из двух цифр,.
;вводится с клавиатуры.
;Выход: результат помещается
;в регистр 31 .
;
0000data segment para public "data" ;сегмент данных
000082A2A5A4A8E2A5+ messagedb"Введитедвешестнадцатеричные
цифры, S'1
11
20
А4
А2
А5
20
Е8
А5+
12
El
E2
AD
АО
А4
Еб
А0+
13
Е2
А5
Е0
А8
E7
AD
ЕВ+
14
A5
20
E6
А8
E4
E0
ЕВ+
152C 24
16
17
0025 data ends
0000 stk segmentstack
18
0000 0100*(3F) db 256 dup (?);сегмент стека
19
0100 stkends
20
0000 codesegment para public "code";начало сегмента кода
21
0000 mainproc;начало процедуры main
22
assume cs:code,ds:data,ss:stk.
23
0000 B8 0000s mov ax,data;адрес сегмента данных в регистре ах
24
0003 8Е D8 mov ds,ax;ax в ds
25
0005 В4 09 mov ah ,9
26
0007 ВА 0O00mov dx.offset messag
**Error** Prg_6_l.asm(21) Undefined symbol: MESSAG 27 00QA CD 21 int 21h
Листинг 6.2f продолжение)
28000С33С0xor29000EВ401mov300010CD21int3100128AD0mov32001480EA3033001780FA0934001A7EE4jle28000С33С0xor29000EВ401mov300010CD21int3100128AD0mov32001480EA3033001780FA0934001A7EE4jleax. ax ah. lh 21h dl.alax. ax ah. lh 21h dl.alочистить регистр ах;lh в регистр ahгенерация прерывания с номером 21hсодержимое регистра al в регистр dlочистить регистр ах;lh в регистр ahгенерация прерывания с номером 21hсодержимое регистра al в регистр dlsub dl 30h;вычитание: (dl)=(dl)-30hcmp dl,9h ;сравнить (dl) с 9h. MM;перейти на метку Ml, если dl<9h или dl=9h**Er「or** Prg_6_l.asm(29) Undefined symbol: MM35 001C 80 EA 00sub dl,777h ;вычитание: (dl) = (dl)-7hsub dl 30h;вычитание: (dl)=(dl)-30hcmp dl,9h ;сравнить (dl) с 9h. MM;перейти на метку Ml, если dl<9h или dl=9h**Er「or** Prg_6_l.asm(29) Undefined symbol: MM35 001C 80 EA 00sub dl,777h ;вычитание: (dl) = (dl)-7h**Error** Prg_i**Error** Prg_i363738394041424344454647484950363738394041424344454647484950001FM1:001FM1:001F 0021 0023 0025 0027 0029002В001F 0021 0023 0025 0027 0029002ВBl D2 CD 2С ЗС7E 2СBl D2 CD 2С ЗС7E 2С0Q2DM2: 002D 02002F В80Q2DM2: 002D 02002F В80032 0034 0034 end0032 0034 0034 endCDCD,6_l.asm(30),6_l.asm(30)Constant too largeConstant too large04E2213009020704E22130090207D0D0mov cl,4hshl dl.clint 21hsub al,30hcmp al ,9hjle M2sub al.7hmov cl,4hshl dl.clint 21hsub al,30hcmp al ,9hjle M2sub al.7hadd dl.aladd dl.alюпределение метки Ml;пересылка 4h в регистр cl;сдвиг содержимого dl на 4 разряда влево;вызов прерывания с номером 21h;вычитание: (dl)=(dl)-30h;сравнить (al) с 9h 28;перейти на метку М2, если al<9h или al=9hюпределение метки Ml;пересылка 4h в регистр cl;сдвиг содержимого dl на 4 разряда влево;вызов прерывания с номером 21h;вычитание: (dl)=(dl)-30h;сравнить (al) с 9h 28;перейти на метку М2, если al<9h или al=9h;вычитание:;вычитание:(al)=(al)-7h(al)=(al)-7hюпределение метки М2юпределение метки М2;сложение:;сложение:(dl)=(dl)+(al)(dl)=(dl)+(al)4C00mov ax,4c00h;пересылка 4c00h в регистр ах int 21h*4C00mov ax,4c00h;пересылка 4c00h в регистр ах int 21h*2121main code mainmain code mainendpendsendpends;вызов прерывания с номером 21h;конец процедуры main;конец сегмента кода;конец программы с точкой входа main;вызов прерывания с номером 21h;конец процедуры main;конец сегмента кода;конец программы с точкой входа mainTurbo Assembler Version 4.1 02/03/98 21:23:43 Page 2Symbol TableSymbol Name TypeValueCref(defined at #)??DATE Text "02/03/98"Turbo Assembler Version 4.1 02/03/98 21:23:43 Page 2Symbol TableSymbol Name TypeValueCref(defined at #)??DATE Text "02/03/98"??FILENAME??TIME??VERSION??FILENAME??TIME??VERSION@CPUText0101H@CPUText0101HText"Prg_6_l" Text"21:23T43"Number 040AText"Prg_6_l" Text"21:23T43"Number 040A@CURSEG TextCODE #9 #17 #20@FILENAMETextPRG_6_l@WORDSIZE Text2#9 #17 #20Ml NearCODE:001F#36@CURSEG TextCODE #9 #17 #20@FILENAMETextPRG_6_l@WORDSIZE Text2#9 #17 #20Ml NearCODE:001F#36M2 NearCODE:002D 42 #44MAIN NearCODE:0000#21 50MESSAGE ByteDATA:0000#10Groups & Segments Bit Size Align Combine ClassCODE16 0034 ParaPublic CODE#20 22DATA16 0025 ParaPublic DATA#9 22 23STK 16 0100 ParaStack 17 22M2 NearCODE:002D 42 #44MAIN NearCODE:0000#21 50MESSAGE ByteDATA:0000#10Groups & Segments Bit Size Align Combine ClassCODE16 0034 ParaPublic CODE#20 22DATA16 0025 ParaPublic DATA#9 22 23STK 16 0100 ParaStack 17 22Turbo Assembler VersionError Summary**Error** Prg_6_l.asm(21)**frror** Prg二6二l.asm(29)**Error** Prg_6_l.asm(30)Turbo Assembler VersionError Summary**Error** Prg_6_l.asm(21)**frror** Prg二6二l.asm(29)**Error** Prg_6_l.asm(30)4.1 02/03/98 21:23:43PageUndefined symbol: MESSAG Undefined symbol: MM Constant too large4.1 02/03/98 21:23:43PageUndefined symbol: MESSAG Undefined symbol: MM Constant too largeCref(defined at3Cref(defined at3#)#)Файл листинга содержит код ассемблера исходной программы, а также расширенную информацию об этом коде. Для каждой команды ассемблера указываются ее машинный (объектный) код и смещение в кодовом сегменте. Кроме того, в конце листинга TASM формирует таблицы с информацией о метках и сегментах, используемых в программе. Если есть ошибки или сомнительные участки кода, то TASM включает в конец листинга сообщения о них. Если сравнить их с сообщениями, выводимыми на экран, то видно, что они совпадают. Кроме того, что очень удобно, эти же сообщения включаются в текст листинга непосредственно после ошибочной строки.Файл листинга содержит код ассемблера исходной программы, а также расширенную информацию об этом коде. Для каждой команды ассемблера указываются ее машинный (объектный) код и смещение в кодовом сегменте. Кроме того, в конце листинга TASM формирует таблицы с информацией о метках и сегментах, используемых в программе. Если есть ошибки или сомнительные участки кода, то TASM включает в конец листинга сообщения о них. Если сравнить их с сообщениями, выводимыми на экран, то видно, что они совпадают. Кроме того, что очень удобно, эти же сообщения включаются в текст листинга непосредственно после ошибочной строки.
28
000С
33
С0
xor
29
000E
В4
01
mov
30
0010
CD
21
int
31
0012
8A
D0
mov
32
0014
80
EA
30
33
0017
80
FA
09
34
001A
7E
E4
jle
28
000С
33
С0
xor
29
000E
В4
01
mov
30
0010
CD
21
int
31
0012
8A
D0
mov
32
0014
80
EA
30
33
0017
80
FA
09
34
001A
7E
E4
jle
ax. ax ah. lh 21h dl.al
ax. ax ah. lh 21h dl.al
очистить регистр ах
;lh в регистр ah
генерация прерывания с номером 21h
содержимое регистра al в регистр dl
очистить регистр ах
;lh в регистр ah
генерация прерывания с номером 21h
содержимое регистра al в регистр dl
sub dl 30h;вычитание: (dl)=(dl)-30h
cmp dl,9h ;сравнить (dl) с 9h
. MM;перейти на метку Ml, если dl<9h или dl=9h
**Er「or** Prg_6_l.asm(29) Undefined symbol: MM
35 001C 80 EA 00sub dl,777h ;вычитание: (dl) = (dl)-7h
sub dl 30h;вычитание: (dl)=(dl)-30h
cmp dl,9h ;сравнить (dl) с 9h
. MM;перейти на метку Ml, если dl<9h или dl=9h
**Er「or** Prg_6_l.asm(29) Undefined symbol: MM
35 001C 80 EA 00sub dl,777h ;вычитание: (dl) = (dl)-7h
**Error** Prg_i
**Error** Prg_i
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
001FM1:
001FM1:
001F 0021 0023 0025 0027 0029
002В
001F 0021 0023 0025 0027 0029
002В
Bl D2 CD 2С ЗС
7E 2С
Bl D2 CD 2С ЗС
7E 2С
0Q2DM2: 002D 02
002F В8
0Q2DM2: 002D 02
002F В8
0032 0034 0034 end
0032 0034 0034 end
CD
CD
,6_l.asm(30)
,6_l.asm(30)
Constant too large
Constant too large
04
E2
21
30
09
02
07
04
E2
21
30
09
02
07
D0
D0
mov cl,4h
shl dl.cl
int 21h
sub al,30h
cmp al ,9h
jle M2
sub al.7h
mov cl,4h
shl dl.cl
int 21h
sub al,30h
cmp al ,9h
jle M2
sub al.7h
add dl.al
add dl.al
юпределение метки Ml
;пересылка 4h в регистр cl
;сдвиг содержимого dl на 4 разряда влево
;вызов прерывания с номером 21h
;вычитание: (dl)=(dl)-30h
;сравнить (al) с 9h 28
;перейти на метку М2, если al<9h или al=9h
юпределение метки Ml
;пересылка 4h в регистр cl
;сдвиг содержимого dl на 4 разряда влево
;вызов прерывания с номером 21h
;вычитание: (dl)=(dl)-30h
;сравнить (al) с 9h 28
;перейти на метку М2, если al<9h или al=9h
;вычитание:
;вычитание:
(al)=(al)-7h
(al)=(al)-7h
юпределение метки М2
юпределение метки М2
;сложение:
;сложение:
(dl)=(dl)+(al)
(dl)=(dl)+(al)
4C00mov ax,4c00h;пересылка 4c00h в регистр ах int 21h*
4C00mov ax,4c00h;пересылка 4c00h в регистр ах int 21h*
21
21
main code main
main code main
endpends
endpends
;вызов прерывания с номером 21h
;конец процедуры main
;конец сегмента кода
;конец программы с точкой входа main
;вызов прерывания с номером 21h
;конец процедуры main
;конец сегмента кода
;конец программы с точкой входа main
Turbo Assembler Version 4.1 02/03/98 21:23:43 Page 2
Symbol Table
Symbol Name TypeValueCref(defined at #)
??DATE Text "02/03/98"
Turbo Assembler Version 4.1 02/03/98 21:23:43 Page 2
Symbol Table
Symbol Name TypeValueCref(defined at #)
??DATE Text "02/03/98"
??FILENAME
??TIME
??VERSION
??FILENAME
??TIME
??VERSION
@CPUText0101H
@CPUText0101H
Text"Prg_6_l" Text"21:23T43"
Number 040A
Text"Prg_6_l" Text"21:23T43"
Number 040A
@CURSEG TextCODE #9 #17 #20
@FILENAMETextPRG_6_l
@WORDSIZE Text2#9 #17 #20
Ml NearCODE:001F#36
@CURSEG TextCODE #9 #17 #20
@FILENAMETextPRG_6_l
@WORDSIZE Text2#9 #17 #20
Ml NearCODE:001F#36
M2 NearCODE:002D 42 #44
MAIN NearCODE:0000#21 50
MESSAGE ByteDATA:0000#10
Groups & Segments Bit Size Align Combine Class
CODE16 0034 ParaPublic CODE#20 22
DATA16 0025 ParaPublic DATA#9 22 23
STK 16 0100 ParaStack 17 22
M2 NearCODE:002D 42 #44
MAIN NearCODE:0000#21 50
MESSAGE ByteDATA:0000#10
Groups & Segments Bit Size Align Combine Class
CODE16 0034 ParaPublic CODE#20 22
DATA16 0025 ParaPublic DATA#9 22 23
STK 16 0100 ParaStack 17 22
Turbo Assembler Version
Error Summary
**Error** Prg_6_l.asm(21)
**frror** Prg二6二l.asm(29)
**Error** Prg_6_l.asm(30)
Turbo Assembler Version
Error Summary
**Error** Prg_6_l.asm(21)
**frror** Prg二6二l.asm(29)
**Error** Prg_6_l.asm(30)
4.1 02/03/98 21:23:43Page
Undefined symbol: MESSAG Undefined symbol: MM Constant too large
4.1 02/03/98 21:23:43Page
Undefined symbol: MESSAG Undefined symbol: MM Constant too large
Cref(defined at
3
Cref(defined at
3
#)
#)
Файл листинга содержит код ассемблера исходной программы, а также расширенную информацию об этом коде. Для каждой команды ассемблера указываются ее машинный (объектный) код и смещение в кодовом сегменте. Кроме того, в конце листинга TASM формирует таблицы с информацией о метках и сегментах, используемых в программе. Если есть ошибки или сомнительные участки кода, то TASM включает в конец листинга сообщения о них. Если сравнить их с сообщениями, выводимыми на экран, то видно, что они совпадают. Кроме того, что очень удобно, эти же сообщения включаются в текст листинга непосредственно после ошибочной строки.
Файл листинга содержит код ассемблера исходной программы, а также расширенную информацию об этом коде. Для каждой команды ассемблера указываются ее машинный (объектный) код и смещение в кодовом сегменте. Кроме того, в конце листинга TASM формирует таблицы с информацией о метках и сегментах, используемых в программе. Если есть ошибки или сомнительные участки кода, то TASM включает в конец листинга сообщения о них. Если сравнить их с сообщениями, выводимыми на экран, то видно, что они совпадают. Кроме того, что очень удобно, эти же сообщения включаются в текст листинга непосредственно после ошибочной строки.
Строки в файле листинга имеют следующий формат:
глубина_вложенности номер_строки смещение машинный_код исходный_код
Далее описано каждое из этих полей:
纓 глубина__вложенности — уровень вложенности включаемых файлов или макрокоманд в файле.
и номер_строки — номер строки в файле листинга. Номера строк листинга используются для локализации ошибок и формирования таблицы перекрестныхссылок.
ПРИМЕЧАНИЕКак уже упоминалось, номера строк листинга могут не соответство
вать номерам строк в исходном файле. В добавление к сказанному ранее нужно отметить, что в ассемблере имеется директива INCLUDE, которая позволяет включить в файл строки другого файла. Нумерация при этом, как и в случае макрокоманд, будет последовательная для строк обоих файлов. Факт вложенности кода одного файла в другой фиксируется увеличением значения поля «глубина_вложенности» на единицу. Это замечание касается и макрокоманд.
趣 смещение — смещение в байтах текущей команды относительно начала сегмента кода. Это смещение называют также счетчиком адреса. Величину смещения вычисляет транслятор для адресации в сегменте кода.
緻 машинный_код — машинное представление команды ассемблера, представленной далее в этой строке полем исходный_код.
я исходи ый_код — строка кода из исходного файла.
Дальнейшие действия программиста должны зависеть от характера ошибки. По мере накопления опыта ошибки будут происходить чаще всего в результате простых описок. На первых порах особое внимание следует уделять правильности написания синтаксических конструкций, так как ошибки синтаксиса — самые распространенные. Исправив несколько первых ошибок, следует перетранслировать программу и приступить к устранению следующих ошибок. Возможно, что этого делать не придется, так как после исправления одной ошибки могут исчезнуть и последующие (так называемые наведенные ошибки).
О нормальном окончании процесса трансляции можно судить по отсутствию строк с сообщениями об ошибках и предупреждениях.
Изучая внимательно файл листинга, следует обратить внимание на то, что не все строки исходной программы имеют соответствующий машинный код (строки 9, 16, 17, 19...22, 48...50). Это обстоятельство обусловлено тем, что исходный файл на ассемблере в общем случае может содержать конструкции следующих типов: 綴 команды ассемблера — конструкции, которым соответствуют машинные ко
манды;
* директивы ассемблера — конструкции, которые не генерируют машинных команд, а являются указаниями транслятору на выполнение некоторых действий или служат для задания режима его работы;
皴 макрокоманды — конструкции, которые, будучи представлены одной строкой в исходном файле программы, после обработки транслятором генерируют
в объектном модуле последовательность команд, директив или макрокоманд ассемблера.
Формат файла листинга и его полнота не являются жестко регламентированными. Их можно изменить, задавая в исходном файле программы директивы управления листингом (приложение Г, http://www.piter.com/download).
Компоновка программы
После устранения ошибок и получения объектного модуля можно приступать к следующему этапу — созданию исполняемого (загрузочного) модуля, или, как еще называют этот процесс, к компоновке программы. Главная цель этого этапа — преобразовать код и данные в объектных файлах в их перемещаемое выполняемое отображение. Чтобы понять, в чем здесь суть, нужно разобраться, зачем вообще разделяют процесс создания исполняемого модуля на два этапа — трансляцию и компоновку. Это сделано намеренно, чтобы можно было объединять вместе модули, написанные на одном и том же или на разных языках. Формат объектного файла позволяет при определенных условиях объединить несколько отдельно оттранслированных исходных модулей в один модуль. При этом в функции компоновщика входит разрешение внешних ссылок (ссылок на внешние процедуры и переменные) в этих модулях. Результатом работы компоновщика является создание загрузочного файла с расширением .ехе. После этого операционная система может загрузить такой файл в память и выполнить его.
Полный формат командной строки для запуска компоновщика довольно сложен (в этой и в большинстве следующих глав мы в основном будем использовать упрощенный формат):
TLINK [ключи] список_объектных_файлов [,имя_загрузочного_модуля] [,имя_файла_карты] [7имя_файла2библиотеки] Т.имя_файла_определений] i,имя二ресурсного一山aSna] 一 一~
Параметры командной строки для запуска компоновщика перечислены далее. 經ключи — необязательные параметры, управляющие работой компоновщика.
Список наиболее часто используемых ключей приведен в приложении В (http:// www.piter.com/download). Каждому ключу должен предшествовать символ - (дефис) или / (слеш). При задании имен ключей имеет значение регистр символов. 薇 список_объектных_файлов — обязательный параметр, содержащий список компонуемых файлов с расширением .obj. Файлы должны быть разделены пробелами или знаком + (плюс), например:
tlink /v prog + mdf + fdr
При необходимости имена файлов снабжают указанием пути к ним.
毅;имя_за「рузочно「。一моду/ж — необязательный параметр, обозначающий имя формируемого загрузочного модуля. Если оно не указано, то имя загрузочного модуля будет совпадать с первым именем в списке имен объектных файлов.
源 имя_файла_карты — необязательный параметр, наличие которого обязывает компоновщик создать специальный файл с картой загрузки. В ней перечисляются имена, адреса загрузки и размеры всех сегментов, входящих в программу.
it имя_файла_библиотеки — необязательный параметр, который представляет собой путь к файлу библиотеки (•lib). Этот файл создается и обслуживается спе-
циальной утилитой tlib.exe пакета TASM. Утилита позволяет объединить часто используемые подпрограммы в виде объектных модулей в один файл. В дальнейшем можно просто указывать в командной строке tlink.exe имена нужных для компоновки объектных модулей и файл библиотеки, в котором следует искать эти подпрограммы. Если компонуется Windows-приложение, то на месте параметра имя_файла_библ иотеки должно указываться имя библиотеки импорта (глава 16).
犠 имя_файла_определений — необязательный параметр, который представляет собой путь к файлу определений (.def). Этот файл используется при компоновке Windows-приложений (глава 16).
蒙 имя„ресурсного„файла — необязательный параметр, который представляет собой путь к файлу с ресурсами Windows-приложения (•res). Этот файл используется при компоновке Windows-приложений (глава 16).
Рассмотренный нами формат командной строки используется и для 32-разряд- ного варианта компоновщикаtlink32.exe.
Существует возможность задания параметров командной строки компоновщика в текстовом файле. Для этого нужно создать файл с именем tlink.cfg (tlink32.cfg). При вызове компоновщика tlink.exe с параметром tlink.cfg (tlink32.exe tlink32.cfg) ему будет передано содержимое файла tlink.cfg (tlink32.cfg), Например, текст конфигурационного файла tlink32.cfg для создания исполняемого файла Windows- приложения с отладочной информацией должен выглядеть так:
/V
/Twe.
Так же как и в случае команды tasm.exe, совсем не обязательно запоминать подробно синтаксис команды tlink.exe. Для того чтобы получить список ключей программы tlink.exe, достаточно просто запустить ее без параметров.
Для выполнения нашего примера запустим программу tli nk.exe командной строкой вида
tlink. exe /v prg_6_l.obj
В результате вы получите исполняемый модуль с расширением .ехе — prg_6_l.exe.
Получив исполняемый модуль, не спешите радоваться. К сожалению, устранение синтаксических ошибок еще не гарантирует, что программа будет хотя бы запускаться, не говоря уже о ее правильной работе. Поэтому обязательным этапом процесса разработки является отладка.
Отладка программы
На этапе отладки в соответствие с алгоритмом проверяется правильность функционирования как отдельных фрагментов кода, так и программы в целом. Но даже успешное завершение отладки еще не является гарантией того, что программа будет работать правильно со всеми возможными исходными данными. Поэтому нужно обязательно провести тестирование программы, то есть проверить ее работу на «пограничных» и заведомо некорректных исходных данных. Для этого составляются тесты. Вполне возможно, что результаты тестирования не удовлетворят разработчика программы. В этом случае ему придется вносить поправки в код программы, то есть возвращаться к первому шагу процесса разработки (см. рис. 6.1).
Специфика программ на ассемблере состоит в том, что они интенсивно работают с аппаратными ресурсами компьютера. Это обстоятельство заставляет программиста постоянно отслеживать содержимое определенных регистров и областей памяти. Естественно, что человеку трудно следить за этой информацией с большой степенью детализации. Поэтому для локализации логических ошибок в программах используют специальный тип программного обеспечения — программные отладчики.
Отладчики бывают двух типов:
* интегрированные отладчики, реализованные в виде интегрированной среды, напоминающей среду для языков высокого уровня (Turbo Pascal, Visual C++ и т. д.);
蠶 автономные отладчики, представляющие собой отдельные программы.
Ни один из рассматриваемых нами ассемблеров (MASM, TASM) не имеет своей интегрированной среды, поэтому для отладки написанных на языке ассемблера программ используют либо автономные отладчики, либо отладчики некоторой среды программирования (например, Visual C++). С помощью автономного отладчика можно исследовать работу любой программы, для которой создан исполняемый модуль, независимо от того, на каком языке был написан его исходный текст. Для учебных целей ни один из этих подходов не приемлем, так как требует знаний, которыми начинающий программировать на ассемблере, скорее всего, еще не обладает.
ПРИМЕЧАНИЕПочему в учебнике много программ для MS-DOS? Ведь времена мас
сового использования этой операционной системы давно прошли. На сегодняшний день у этой ОС остался один, но очень важный аспект ее применения — методический. Опыт показывает, что при обучении любому языку программирования, в том числе ассемблеру, на первом месте должен быть сам язык, а не программные средства поддержки процесса программирования на нем. В этом контексте отметим два момента. Во-первых, начинающему изучать ассемблер легче объяснить принципы построения и работы ассемблерных программ в среде реального режима (MS-DOS), чем защищенного (Windows). По мере накопления практического опыта и теоретических знаний с целью их наращивания можно переходить к работе с более сложными приложениями, в том числе и для операционной системы Windows. Во-вторых, для большинства изучающих язык ассемблера его освоение является промежуточным этапом на пути к реализации некоторой большей задачи. Поэтому подавляющее большинство глав данного учебника посвящено рассмотрению различных групп команд ассемблера, для детального изучения которых вполне достаточно среды реального режима (MS-DOS).
Пакеты TASM и MASM имеют достаточно эффективные инструменты разработки программ для среды MS-DOS, работу с которыми вполне по силам освоить даже начинающему программисту. Конечно, сейчас мало кто пишет программы для среды MS-DOS, поэтому работу с 16-разрядными инструментами пакетов TASM и MASM нужно рассматривать как часть методики обучения. Аналогичные
рассуждения относятся и к средствам отладки. В этой книге для программ TASM реального режима будет использоваться 16-разрядный отладчик Turbo Debugger (TD), разработанный фирмой Borland International. Это наиболее удачный отладчик для ассемблерных программ реального режима. Принципиально важно, что работа с TD прививает навыки, которые наверняка окажутся полезными при работе с другими отладчиками, например с отладчиком cv.exe из пакета
Отладчик TD представляет собой оконную среду отладки программ на уровне исходного ассемблерного текста. Он позволяет решить две главные задачи:
определить место логической ошибки;
определить причину логической ошибки.
Перечислим некоторые возможности TD:
трассировка программы в прямом направлении, то есть последовательное выполнение программы, при котором за один шаг выполняется одна машинная инструкция;
трассировка программы в обратном направлении, то есть выполнение программы по одной команде за один шаг, но в обратном направлении;
просмотр и изменение состояния аппаратных ресурсов процессора во время трассировки.
Эти действия позволяют определить место и источник ошибки в программе. Нужно сразу оговориться, что TD не позволяет вносить исправления в исходный текст программы. Однако после определения причины ошибочной ситуации можно, не завершая работу отладчика, внести исправления прямо в машинный код и снова запустить программу. Поскольку после завершения работы отладчика эти изменения не сохраняются, на практике подобное прямое внесение изменений в код не применяют. Изменения вносят в исходный текст программы, заново создают загрузочный модуль, который снова загружают в отладчик.
Правильная организация процесса получения исполняемого модуля, пригодного для отладки на уровне исходного текста, рассмотрена ранее. Далее перечислены ключевые моменты этого процесса.
В исходной программе обязательно должна быть определена метка для первой команды, с которой начнется выполнение программы. Такая метка может быть собственно меткой или, как видно из листинга именем процедуры. Имя этой метки обязательно должно быть указано в конце программы в качестве операнда директивы END:
end
В нашем случае эта метка является именем процедуры MAIN.
Исходный модуль должен быть оттранслирован с ключом /zi:
tasm /zi, , ,
Ключ /zi разрешает транслятору сохранить связь символических имен в программе с их смещениями в сегменте кода, что позволяет отладчику выполнять отладку, используя оригинальные имена.
Редактирование модуля должно быть осуществлено с ключом /v:
/v
138Глава 6. Первая программа
Ключ /v указывает на необходимость сохранения отладочной информации в исполняемом файле.
'Запуск отладчика удобнее производить из командной строки с указанием ис
полняемого модуля отлаживаемой программы:
td имя_исполняемого_модуля
Кстати, сам файл отладчика td.exe логично также поместить в наш рабочий каталог ,.\WORK. Изначально файлы отладчика находятся в каталоге ,.\BIN пакета TASM. Если все же файл td.exe и файл исполняемого модуля при запуске будут находиться в разных каталогах, то в командной строке необходимо указать путь к этому модулю, например:
td с:\1а5ш\могк\имя_модуля. ехе
При правильном выполнении перечисленных действий откроется окно отладчика TD под названием Module с исходным текстом программы prg_6_l.a5m. Как он здесь оказался, ведь в командной строке для программы td.exe было указано только имя исполняемого модуля? Это как раз и есть результат действия ключей /zi и /v для tasm и tlink соответственно. Их применение позволило сохранить информацию об использовавшихся в коде на ассемблере символических именах. Для полноты эксперимента можно попытаться получить исполняемый модуль без задания этих ключей и проанализировать результат.
Вернемся к окну Module (рис. 6.2). Внимание следует обратить на так называемый курсор выполнения (в виде треугольника). Он указывает на первую команду, подлежащую выполнению. Этой команде предшествует имя метки (в нашем случае роль метки выполняет имя процедуры). Это так называемая точка входа в программу. Если внимательно посмотреть на конец исходного текста программы, то видно, что это же имя записано в качестве операнда в заключительной директиве END. Это единственный способ сообщить загрузчику ОС о том, где в исходном тексте программы расположена точка входа в нее. В более сложных программах обычно
j:号磔号関:"j:号磔号関:"二 e二更ia" 一 無运: 奥g? 而广 蜕%*而6q碍£ 一百£二岁讲而好启标0…:海1%一二二 e二更ia" 一 無运: 奥g? 而广 蜕%*而6q碍£ 一百£二岁讲而好启标0…:海1%一二F【孑】书$dn1»£ e£Et Fil也: Kckgt.asm 10 一 ""-;Выход: результат преобразования помещается в регистр dl.F【孑】书$dn1»£ e£Et Fil也: Kckgt.asm 10 一 ""-;Выход: результат преобразования помещается в регистр dl.data segment "para public "data";сегмент данныхnos sage db "Введите две шестнадцатеричные циФры^$' data endsstk segment stackdata segment "para public "data";сегмент данныхnos sage db "Введите две шестнадцатеричные циФры^$' data endsstk segment stackdb 256 dupdb 256 dup;сегмент стека;сегмент стекаstk endscode segment para public "code";начало сегмента кодаstk endscode segment para public "code";начало сегмента кодаmain procmain proc«начало процедуры main«начало процедуры mainassume cs:code ^ds:data,ss:st кassume cs:code ^ds:data,ss:st к► mo иmou mou mou intе ХОУ mou► mo иmou mou mou intе ХОУ mouax,data заПрвс сегмента данных в регистр ах ds,ax ;ax в ds ah,9 •dx,offset message2ih..ax,data заПрвс сегмента данных в регистр ах ds,ax ;ax в ds ah,9 •dx,offset message2ih..ax^ax ah,lhax^ax ah,lh-1。ё * 1■-1。ё * 1■,=【iHi,=【iHi气气«очистить регистр ах•:目в регистр :^^^^^^^^^ ^^jv^^v^y-^^A ::^^ л^^^^^^^^^^^^^^г^^^^^^^^^^^^^^^^^^^^^^^ }^^^^■: !^^^^^^' .«очистить регистр ах•:目в регистр :^^^^^^^^^ ^^jv^^v^y-^^A ::^^ л^^^^^^^^^^^^^^г^^^^^^^^^^^^^^^^^^^^^^^ }^^^^■: !^^^^^^' .Н-H^lp 尸絕势*Ft : F3.^1pd:'F4-He?e〔困丁名守目餡芷6*xt:.・ ^Тглсв i^-S ti?p 聲~兼也屯;Fl^Hp^ii:'Н-H^lp 尸絕势*Ft : F3.^1pd:'F4-He?e〔困丁名守目餡芷6*xt:.・ ^Тглсв i^-S ti?p 聲~兼也屯;Fl^Hp^ii:'
j:号磔号関:"
j:号磔号関:"
二 e二更ia" 一 無运: 奥g? 而广 蜕%*而6q碍£ 一百£二岁讲而好启标0…:海1%一二
二 e二更ia" 一 無运: 奥g? 而广 蜕%*而6q碍£ 一百£二岁讲而好启标0…:海1%一二
F【孑】书$dn1»£ e£Et Fil也: Kckgt.asm 10 一 ""-
;Выход: результат преобразования помещается в регистр dl.
F【孑】书$dn1»£ e£Et Fil也: Kckgt.asm 10 一 ""-
;Выход: результат преобразования помещается в регистр dl.
data segment "para public "data";сегмент данных
nos sage db "Введите две шестнадцатеричные циФры^$' data ends
stk segment stack
data segment "para public "data";сегмент данных
nos sage db "Введите две шестнадцатеричные циФры^$' data ends
stk segment stack
db 256 dup
db 256 dup
;сегмент стека
;сегмент стека
stk ends
code segment para public "code";начало сегмента кода
stk ends
code segment para public "code";начало сегмента кода
main proc
main proc
«начало процедуры main
«начало процедуры main
assume cs:code ^ds:data,ss:st к
assume cs:code ^ds:data,ss:st к
► mo и
mou mou mou int
е ХОУ mou
► mo и
mou mou mou int
е ХОУ mou
ax,data заПрвс сегмента данных в регистр ах ds,ax ;ax в ds ah,9 •
dx,offset message
2ih..
ax,data заПрвс сегмента данных в регистр ах ds,ax ;ax в ds ah,9 •
dx,offset message
2ih..
ax^ax ah,lh
ax^ax ah,lh
-1。ё * 1■
-1。ё * 1■
,=【iHi
,=【iHi
气
气
«очистить регистр ах•:目
в регистр :^^^^^^^^^ ^^jv^^v^y-^^A ::^^ л^^^^^^^^^^^^^^г^^^^^^^^^^^^^^^^^^^^^^^ }^^^^■: !^^^^^^' .
«очистить регистр ах•:目
в регистр :^^^^^^^^^ ^^jv^^v^y-^^A ::^^ л^^^^^^^^^^^^^^г^^^^^^^^^^^^^^^^^^^^^^^ }^^^^■: !^^^^^^' .
Н-H^lp 尸絕势*Ft : F3.^1pd:'F4-He?e〔困丁名守目餡芷6*xt:.・ ^Тглсв i^-S ti?p 聲~兼也屯;Fl^Hp^ii:'
Н-H^lp 尸絕势*Ft : F3.^1pd:'F4-He?e〔困丁名守目餡芷6*xt:.・ ^Тглсв i^-S ti?p 聲~兼也屯;Fl^Hp^ii:'
Рис. 6.2. Окно Module отладчика TD
вначале могут идти описания процедур, макрокоманд, и в этом случае без такого явного указания на первую исполняемую команду просто не обойтись.
Основную часть главного окна отладчика обычно занимают одно или несколько дополнительных окон. В каждый момент времени активным может быть только одно из них. Активизация любого окна производится щелчком мышью в любой его видимой точке.
Управление работой отладчика ведется с помощью системы меню. Имеются два типа таких меню:
т главное меню — находится в верхней части экрана и доступно постоянно (вызов меню осуществляется нажатием клавиши F10, после чего следует выбрать нужный пункт меню);
緻 контекстное меню — для каждого окна отладчика можно вызвать его собственное меню, которое учитывает особенности этого окна, щелкнув в окне правой кнопкой мыши (либо активизировав окно и нажав клавиши Alt+F10).
Теперь можно проверить правильность функционирования нашей программы.
Специфика программ на ассемблере состоит в том, что делать выводы о правильности их функционирования можно, только отслеживая работу на уровне процессора. При этом нас будет интересовать прежде всего то, как программа использует процессор и изменяет состояние его ресурсов и компьютера в целом.
Запустить программу в отладчике можно в одном из четырех режимов:
• безусловного выполнения;
纖 выполнения по шагам;
ii выполнения до текущего положения курсора;
綫 выполнения с установкой точек прерывания.
Рассмотрим эти режимы подробнее.
Режим безусловного выполнения программы целесообразно применять, когда требуется посмотреть на общее поведение программы. Для запуска программы в этом режиме необходимо нажать клавишу F9. В точках, где необходимо ввести данные, отладчик, в соответствии с логикой работы применяемого средства ввода, будет осуществлять определенные действия. Аналогичные действия отладчик выполнит при выводе данных. Для просмотра или ввода этой информации можно открыть окно пользователя (выбрав в меню команду Window卜User screen или нажав клавиши Alt+F5). Если программа работает правильно, то наэтом отладку можно и закончить. В случае, если возникают какие-то проблемы или нужно более детально изучить работу программы, применяются три следующих режима отладки.
Режим выполнения программы до текущего положения курсора целесообразно использовать в том случае, если интерес представляет только правильность функционирования некоторого участка программы. Для активизации этого режима необходимо установить курсор на нужную строку программы и нажать клавишу F4. Программа запустится и остановится на отмеченной команде, не выполнив ее. Далее при необходимости вы можете перейти в пошаговый режим.
В режиме выполнения программы с установкой точек прерывания программа после запуска будет останавливаться в строго определенных точках прерывания (breakpoints). Перед выполнением программы необходимо установить эти точки
в программе, для чего следует перейти к нужной строке и нажать клавишу F2. Выбранные строки подсвечиваются. Установленные ранее точки прерывания можно убрать — для этого нужно повторно перейти к нужной строке и нажать клавишу F2. После установки точек прерывания программа запускается клавишей F9 (см. ранее режим безусловного выполнения). На первой точке прерывания программа остановится. После этого можно посмотреть состояние процессора и памяти, а затем продолжить выполнение программы. Сделать это можно в пошаговом режиме или до следующей точки прерывания.
Режим выполнения программы по шагам применяется для детального изучения ее работы. В этом режиме выполнение программы прерывается на каждой машинной (ассемблерной) команде. При этом становится возможным наблюдение за результатом исполнения команд. Для активизации этого режима нужно нажать клавишу F7 (Run ► Trace into) или F8 (Run ► Step over). Обе эти клавиши активизируют пошаговый режим; различие их проявляется в том случае, когда в потоке команд встречаются команды перехода в процедуру или на прерывание. При нажатии клавиши F7 отладчик осуществит переход к процедуре или прерыванию и остановится. Если же нажимается клавиша F8, то вызов процедуры или прерывания отрабатывается как одна команда, и управление передается следующей команде программы. Здесь нужно отметить, что кроме окна Module при работе в этом режиме полезно использовать окноCPU, вызвать которое можно через главное меню командой View ► CPU.
ОкноCPU отражает состояние процессора и состоит из пяти подчиненных окон. 趣 В окне с исходной программой в дизассемблированном виде представлена та же самая программа, что и в окнеModule, ноуже в машинных кодах. Пошаговую отладку можно производить прямо в этом окне; строка с текущей командой подсвечивается.
в В окне регистров процессора (Registers) отражается текущее содержимое регистров (по умолчанию — только регистров процессора i8086). Чтобы увидеть регистры i486 или Pentium, нужно задать режим их отображения. Для этого щелкните правой кнопкой мыши в области окна регистров и выберите в контекстном меню команду Registers 32-bit — Yes.
纖 В окне флагов (Flags) отражается текущее состояние флагов процессора в соответствии с их мнемоническими названиями.
9 В окне стека (Stack) отражается содержимое памяти, выделенной для стека. Адрес области стека определяется содержимым регистров SS и SP.
緩Окно дампа оперативной памяти (Dump) отражает содержимое области памяти по адресу, который формируется из компонентов, указанных в левой части окна. В окне можно увидеть содержимое произвольной области памяти. Для этого нужно в контекстном меню выбрать нужную команду.
Заметим, что окно CPU, по сути, отражает видимую часть программной модели процессора. Некоторые из подчиненных окон окна CPU можно вывести на экран отдельно. Хотя удобнее работать с исходным текстом в окне Module, чем с его ди- зассемблированнымвариантом в окне CPU, часто есть необходимость отслеживать состояние процессора с помощью подчиненных окон окна CPU. Совместить воз-
можности окон Module и CPU можно, выбрав в меню View имена нужных подчинен- ныхоконокнаCPU.
Прервать выполнение программы в любом из режимов можно, нажав клавиши Ctrl+F2.
Особенности разработки программ в MASM
Для успешной работы с ассемблером MASM корпорации Microsoft в современных операционных средах (Windows NT\2000\XP) необходимо иметь версию 6.13 этого пакета или выше. В него входят следующие основные программы:
緩masm.exe — ассемблер;
* ml.exe — ассемблер и компоновщик (Masm and Link);
緩 link.exe — компоновщик;
疊 cv.exe — отладчик (CodeView);
* lib.exejmplib.exe, nmake.exe, cref.exe, h2inc.exe, exehdr.exe, cvpack.exe, helpmake.exe, rm.exe, undel.exe, exp.exe — вспомогательныеутилиты.
В пакете MASM изначально предпринимались попытки совместить удобство средств программирования, свойственных языкам высокого уровня, с традиционными средствами машинно-ориентированных языков. Например, буква «М» в названии пакета означает слово «macro», то есть возможность создания макроопределений (или макросов), представляющих собой именованные группы команд. Их можно вставлять в программу в любом месте, указав только имя группы. Когда-то такая возможность действительно была отличительным свойством этого пакета, но сейчас этим никого не удивишь — макросредства есть во всех ассемблерах для платформы Intel (например, TASM), но, тем не менее, название осталось.
С помощью пакета MASM разработка программ выполняется традиционным для ассемблерного программирования способом — запуском отдельных программ трансляции, компоновки и отладки. Для этого используются программы masm.exe, mLexe, link.exe и cv.exe. Во избежание путаницы необходимо отметить, что трансляция исходного файла может производиться двумя программами: masm.exe и mLexe. В чем разница? До выхода MASM версии 5.1 включительно программа masm.exe была самостоятельным транслятором ассемблера. Начиная с MASM версии 6.0 трансляция ассемблерного файла обычно выполняется программой ml.exe, которая кроме трансляции файла вызывает компоновщик link.exe. Это изменение сделано с целью унификации вызовов компиляторов командной строки для всех поддерживаемых этой фирмой языков программирования. К примеру, компиляцию программы на языке С из командной строки выполняет программа cLexe. Ее задачи — компиляция исходной программы на языках C/C++ и, при отсутствии синтаксических ошибок, компоновка и формирование исполняемого модуля. Аналогичные задачи решает и программа mLexe. Хотя в пакете MASM 6.13 допустимо использование программы masm.exe, нужно иметь в виду, что запуск link.exe в этом случае также должен выполняться отдельно. Наличие программы masm.exe в пакете MASM 6.13 можно объяснить соображениями совместимости с предыдущими версиями пакета, поэтому особого смысла в ее использовании нет.
Далее приведены форматы командных строк для запуска программ ml.exe, masm.exe и link.exe, а описание их ключей можно найти в приложении В (http:〃 www.piter.com/download).
Командная строка ml.exe имеет вид
ml [ключи] исх_файл_1 [[ключи] исх_файл_2] ... [/link ключи_11пк]
Ключи командной строки для ml.exe чувствительны к регистру.
Командная строка masm.exe имеет вид
masm [ключи] исх_файл [, [объектный_файл] [, [файл_листинга]
[,[файл_перекрестных_ссылок]]]]~
Компоновщик компонует (объединяет) объектные файлы и библиотеки в исполняемый файл или динамически компонуемую библиотеку (DLL). Командная строка Link.exe имеет вид link [ключи] объект_файлы [,[исполн_файл][,[файл^карты][,[файлы_библиотек] [,[беСфайл]]]]] [;Г~~-
Так же как и в случае пакета TASM, для эффективной работы с MASM нужно провести некоторую дополнительную работу. Исполняемые файлы пакета MASM 6.13 находятся в двух каталогах: ..\BIN и ..\BINR. Для удобства работы их лучше объединить в одном каталоге, к примеру, в каталоге ,.\WORK. После этого остается поместить туда исходный файл. Пример командной строки для получения пригодного к отладке исполняемого модуля может быть следующим: ML.EXE /Zi /Fl Prg_6_l.asm
Если нет синтаксических ошибок, то можно запускать отладчик:
CV.EXE PRG__6_1.EXE
Особенности разработки программ в Microsoft Visual Studio
С программами на ассемблере можно работать в среде Visual Studio. Доступны два варианта работы:
создание и использование команд меню Tools;
- создание полноценного проекта и настройка его параметров.
Эти варианты не равнозначны. Первый вариант означает, по сути, скрытый вызов программ ml.exe и cv.exe. Он хорошо подходит для разработки 16-разрядных программ и является для них единственно возможным. Второй вариант опирается на все возможности интегрированной среды, но подходит только для разработки 32-разрядных программ (Windows-приложений). Мы ограничимся первым вариантом, поскольку второй требует изучения интегрированной среды Microsoft Visual Studio, что не относится к теме данной книги.
Microsoft Visual Studio является удобной средой для создания и редактирования исходных текстов (и только). Что касается самого процесса получения исполняемого модуля, то все действия, которые раньше выполнялись с помощью командной строки, здесь выполняются выбором соответствующей команды в меню Tools. Таким образом, задача сводится к созданию в меню Tools дополнительных команд, предназначенных для обращения к инструментальным средствам пакета ассемблера.
Для того чтобы обсуждение было однозначным, необходимо условиться о том, что все нужные для работы файлы будут расположены, к примеру, в каталоге g:\MASM613\work. В этот рабочий каталог необходимо поместить содержимое каталогов g:\MASM613\BINR и g:\MASM613\BINR, а также файлы программы, с которой в данное время идет работа. Для примера будем работать с программой из листинга 6.1.
После открытия среды Visual C++ 6.0 выполняется следующая последовательность шагов.
Выберите команду Tools Customize и в появившемся диалоговом окне перейдите на вкладку
Вкладка Tools позволяет добавлять пункты в меню Tools главного окна. Содержимое данной вкладки состоит из списка Menu contents, ряда текстовых полей и переключателей. Каждый пункт списка Menu contents связан со своими значениями остальных элементов вкладки Tools. Логически эти элементы представляют собой набор параметров для вызова некоторой программы. Чтобы убедиться в этом, достаточно пройтись по нескольким элементам списка. Последний элемент списка обозначен пустым прямоугольником. С его помощью производится добавление новых подпунктов в выпадающее меню Tools главного меню.
Чтобы создать команду для вызова программы ml.exe, введите в пустой пункт списка Menu contents название новой команды, например Build ASM Program, и нажмите клавишу Enter.
Настроим теперь параметры вызова программы ml.exe. Для этого в полеCommand введитев поле Arguments — /Zi/link /со,
в поле Initial directory —и установите переключатель Use output window.
Таким же образом можно добавить в меню Tools команду Debug ASM Program. Для этого в поле Command введите g:\MASM613\Work\cv.exe, в поле Arguments —
в поле Initial directory —и установите переключатель Close
window on exiting.
Для запуска полученного исполняемого модуля добавим еще одну команду Execute ASM Program. Для этого в поле Command введите C:\WINDOWS\System32\ cmd.exe, в— /С $(FileName), в поле Initial directory — $(FileDir).
После выполнения этих шагов можно закрыть окно Customize и проверить работоспособность созданных команд меню Tools главного окна. Перед испытанием в среду Visual Studio необходимо загрузить файл с текстом программы
(см. листингПосле открытия файла выберите команду Tools Build ASM Program.
При наличии синтаксических ошибок в окне Output появятся соответствующие сообщения. Для того чтобы перейти к строке исходного текста, вызвавшей недовольство компилятора, достаточно двойного щелчка мышью на строке с текстом ошибки в окне Output. В результате этого курсор в окне с исходным текстом будет установлен на строку с ошибкой.
Если программа ml.exe отработала без ошибок, то полученный с ее помощью исполняемый модуль можно либо сразу запустить, либо предварительно отладить. Запуск производится командой Tools Execute ASM Program. Для отладки выбирается команда Tools > Debug ASM Program, в результате чего вызывается отладчик cv.exe.
Последний компилятор Visual C++ 7.0 из состава Microsoft Visual Studio Net также имеет настраиваемое меню Tools, поэтому при желании его среду также можно настроить для выполнения описанных ранее задач.
Выбор пакета ассемблера
Какой из пакетов ассемблера выбрать для практической работы? Если этот выбор делать с точки зрения долгосрочной перспективы, то, безусловно, MASM. Причи-
на для этого — продолжающаяся поддержка и развитие этого пакета фирмой Microsoft. Для учебных целей более привлекателен TASM. В данном учебнике выбран промежуточный вариант — транслятор TASM в режиме MASM. Этот режим поддерживает почти все возможности транслятора MASM и создан специально для разработки переносимых (на уровне исходных текстов) программ. Такой подход имеет преимущества в том, что читатель, во-первых, получает навыки использования двух пакетов и, во-вторых, по мере познания особенностей пакетов становится свободным в своем выборе.
Процесс разработки программ с помощью пакетов TASM и MASM предполагает, что пользователь интенсивно работает с командной строкой. При этом он должен помнить не только последовательность запуска различных программ, формирующих исполняемый модуль, но и задаваемые при этом параметры программ. Если проект состоит из большого количества файлов, необходимость помнить всю эту информацию довольно утомительна. Для решения этой проблемы можно посоветовать использовать специальную программу — менеджер проекта. В пакете TASM такой программой является утилита make.exe, а в пакете MASM — утилита nmake.exe. Эти утилиты работают со специально оформленными файлами, называемыми файлами описания, или make-файлами. В make-файлах задаются отношения между файлами проекта и действия над этими файлами, которые выполняются в зависимости от возникновения тех или иных условий. Главный принцип, положенный в основу работы утилиты make.exe, заключается в анализе времени изменения или создания файлов. Подробное описание утилиты можно найти среди файлов к книге1.
Итоги
в Структура программы на ассемблере отражает особенности архитектуры процессора. Для процессоров Intel типичная программа состоит из трех сегментов: кода, стека и данных. Но это не обязательное условие; например, если программа не использует стек и для ее работы не требуется определения данных, то она может состоять всего лишь из одного сегмента кода.
is Программа на ассемблере работает на уровне аппаратных средств, входящих в программную модель процессора, которая описана в главе 2.
,附: При разработке алгоритма работы программы и его реализации на ассемблере программист сам должен беспокоиться о размещении данных в памяти, об эффективном использовании ограниченного количества регистров, об организации связи с операционной системой и другими программами.
Специфика разработки программы на ассемблере состоит в том, что программист должен уделять внимание не только и не столько особенностям моделирования предметной области, сколько тому, как при этом наиболее эффективно и корректно использовать ресурсы процессора.
Все прилагаемые к книге файлы можно найти по адресу http://www.piter.com/download. — Примеч. ред.
Итоги145
羅 В результате работы транслятора создаются файл объектного модуля и файл листинга программы, содержащий разнообразную информацию о программе: объектный код, сообщения о синтаксических ошибках, таблицу символов и т. д. Имея небольшой опыт, из файла листинга можно извлечь массу полезной информации.
篠 После получения корректного объектного модуля программу необходимо скомпоновать. Для этого применяется утилита-компоновщик, одним из основных назначений которой является разрешение внешних ссылок. Если целевая программа состоит из нескольких отдельно оттранслированных модулей и в них есть взаимные ссылки на переменные или модули, то компоновщик разрешает их, формируя тем самым правильные перемещаемые адреса.
奪 Результатом работы компоновщика является исполняемый (загрузочный) модуль, имеющий расширение .ехе. Его уже можно запускать в надежде, что он правильно выполнит задуманные программистом действия. Но чаще всего при первых запусках программы этого не случается, что говорит о наличии в программе логических ошибок, поиск которых без специальных средств может быть долгим. Для поиска и устранения логических ошибок предназначен специальный вид программного обеспечения — отладчики. С их помощью в большинстве случаев довольно быстро удается снять большинство программных проблем подобного рода.
Глава 7
Команды обмена данными
新Линейные алгоритмы
Команды пересылки данных
Ввод из порта и вывод в порт
Команды работы с адресами памяти
Команды работы со стеком
Наверняка вы уже знакомы с понятием алгоритма, представляющего собой формализованное описание логики работы программы. Способы такой формализации весьма разнятся: от текстового описания последовательности действий до алгоритма развитых case-систем. Последовательность действий, описываемых алгоритмом, может быть:
И линейной — все действия выполняются поочередно, друг за другом;
緻 нелинейной — в алгоритме есть точки ветвления, в которых должно приниматься решение о месте, с которого программа продолжит свое выполнение, причем решение может носить условный или безусловный характер.
Линейные участки алгоритма обычно содержат команды манипуляции данными, вычисления значений выражений, преобразования данных. В точках ветвления размещают команды сравнения, различных видов перехода, вызова подпрограмм и некоторые другие.
Еще раз обратимся к функциональной классификации целочисленных машинных команд процессора (см. рис. 3.3). Из всей совокупности этих команд на линейных участках работают следующие группы:
И команды пересылки данных;
ж арифметические команды;
логические команды;
盡 команды управления состоянием процессора.
В этой главе мы рассмотрим только группу команд пересылки данных. Эти команды осуществляют пересылку данных из одного места в другое, запись и чтение информации из портов ввода-вывода, преобразование информации, манипуляции с адресами и указателями, обращение к стеку. Для некоторых из этих команд операция пересылки является только частью алгоритма. Другая его часть выполняет некоторые дополнительные операции над пересылаемой информацией. Поэтому для удобства практического применения и отражения их специфики данные команды будут рассмотрены в соответствии с их функциональным назначением, согласно которому они делятся на команды:
煞 собственно пересылки данных;
丁 ввода из порта и вывода в порт;
曜 работы с адресами и указателями;
後 преобразования данных;
侬 работы со стеком.
Пересылка данных
К группе команд пересылки данных относятся следующие команды:
mov <операнд назначения〉,<операнд-источник> xchg <операнд1>,<операнд2>
MOV — это основная команда пересылки данных. Она реализует самые разнообразные варианты пересылки. Отметим особенности применения этой команды.
議务 Командой MOV нельзя осуществить пересылку из одной области памяти в другую. Если такая необходимость возникает, то нужно использовать в качестве промежуточного буфера любой доступный в данный момент регистр общего назначения. К примеру, рассмотрим фрагмент программы для пересылки байта из ячейки fls в ячейку fid:
masm
modelsmall
.data
fls db 5
fid db 1 .code start:
mov al,fls mov fid,al
end start
験 Нельзя- загрузить в сегментный регистр значение непосредственно из памяти. Для такой загрузки требуется промежуточный объект. Это может быть регистр общего назначения или стек. Если вы посмотрите на листинг 5.1, то увидите в начале сегмента кода две команды MOV, выполняющие настройку сегментного регистра DS. При этом из-за невозможности напрямую загрузить в сегментный
регистр значение адреса сегмента, содержащееся в предопределенной переменной @data, приходится использовать регистр общего назначения АХ.
II Нельзя переслать содержимое одного сегментного регистра в другой сегментный регистр. Это объясняется тем, что в системе команд нет соответствующего кода операции. Но необходимость в таком действии часто возникает. Выполнить такую пересылку можно, используя в качестве промежуточных все те же регистры общего назначения. Вот пример инициализации регистра ES значением из регистра DS: mov ax.ds mov es,ax
Но есть и другой, более «красивый» способ выполнения данной операции — использование стека и команд PUSH и POP:
push ds ;поместить значение регистра ds в стек pop es ;записать в es число из стека
• Нельзя использовать сегментный регистр CS в качестве операнда назначения. Причина здесь простая. Дело в том, что в архитектуре процессора IA-32 пара CS:IP содержит адрес команды, которая должна выполняться следующей. Изменение командой MOV содержимого регистра CS фактически означало бы операцию перехода, а не пересылки, что недопустимо.
В связи с командой MOV отметим один тонкий момент. Пусть в регистре ВХ содержится адрес некоторого поля (то есть мы используем косвенную базовую адресацию). Его содержимое нужно переслать в регистр АХ. Очевидно, что нужно применить команду MOV в форме mov ax,[bx]
Транслятор задает себе вопрос: что адресует регистр ВХ в памяти — слбво или байт? Обычно в этом случае он принимает решение сам, по размеру большего операнда, но может и выдать предупреждающее сообщение о возможном несовпадении типов операндов.
Или другой случай — возможно, более показательный. Рассмотрим команды инкремента INC (увеличения операнда на 1) и декремента DEC (уменьшения операнда на 1): inc [Ьх]
dec [bx]
Что адресуется регистром ВХ в памяти — байт, слово или двойное слово?
Допустим также, что требуется инициализировать поле, адресуемое регистром
ВХ, значением 0. Очевидно, что одно из решений — применение команды MOV: mov [bx] ,0
И опять у транслятора вопрос: какую машинную команду ему конструировать? Для инициализации байта? Для инициализации слова? Для инициализации двойного слова?
Во всех этих случаях необходимо уточнять тип используемых операндов. Для этого существует специальный оператор ассемблера PTR (см. приложение). Правильно записать приведенные ранее команды можно следующим образом: mov ax,word ptr[bx] ;если [bx] адресует слово в памяти inc byte ptr[bx];если [bx] адресует байт в памяти
dec dword ptr[bx];если [Ьх] адресует двойное слово в памяти
mov word ptr[bx],0 ;если [bx] адресует слово в памяти
Можно рекомендовать использовать оператор PTR во всех сомнительных относительно согласования размеров операндов случаях. Также этот оператор нужно применять, когда требуется принудительно поменять размерность операндов. К примеру, требуется переслать значение Offh во второй байт поля ftp: masm modelsmall
.data
ftp dw 0
.code
start:
mov byte ptr (flp+1),®ffh
end start
Несмотря на то что поле flp имеет тип WORD, мы сообщаем ассемблеру, что поле нужно трактовать как байтовое, и заставляем вычислить значение эффективного адреса второго операнда как смещение flp плюс единица. Тем самым мы получаем доступ ко второму байту поля flp.
Для двунаправленной пересылки данных применяют команду XCHG. Для этой операции можно, конечно, применить последовательность из нескольких команд MOV, но из-за ТОГО что операция обмена используется довольно часто, разработчики системы команд процессора посчитали нужным ввести отдельную команду обмена — XCHG. Естественно, что операнды должны иметь один тип. Не допускается (как и для всех команд ассемблера) напрямую обменивать между собой содержимое двух ячеек памяти. К примеру, xchgax, bx;обменять содержимое регистров ах и bx
;обменять содержимое регистра ах и слова в памяти по адресу в [si]: xchgax.word ptr [si]
Ввод из порта и вывод в порт
В главе 5 при обсуждении вопроса о том, где могут находиться операнды машинной команды, мы упоминали порт ввода-вывода. Физически порт ввода-вывода представляет регистр разрядностью 8, 16 или 32 бита. Доступ к устройствам ввода-вывода, системным устройствам компьютера осуществляется посредством этих регистров, причем каждый из этих регистров должен иметь возможность уникальной идентификации. С этой целью архитектурно процессор поддерживает так называемое адресное пространство вводп-вывода. Адресное пространство ввода- вывода физически независимо от пространства оперативной памяти и имеет ограниченный объем, составляющий 216t или 65 536, адресов ввода-вывода.
Таким образом, порт ввода-вывода можно определить как 8-, 16- или 32-раз- рядный аппаратный регистр, имеющий определенный адрес в адресном пространстве ввода-вывода. Вся работа системы с устройствами на самом низком уровне выполняется с использованием портов ввода-вывода. Посмотрите на рис. 7.1. На нем показана сильно упрощенная концептуальная схема управления оборудованием компьютера.
Рис. 7.1. Концептуальная схема управления оборудованием компьютера
Как видно из рисунка, самым нижним уровнем является уровень BIOS, на котором работа с оборудованием ведется напрямую через порты. Тем самым реализуется концепция независимости от оборудования. При замене оборудования потребуется лишь подправить соответствующие функции BIOS, переориентировав их на новые адреса и логику работы портов.
Принципиально управлять устройствами напрямую через порты несложно. Сведения о номерах портов, их разрядности, формате управляющей информации приводятся в техническом описании устройства. Необходимо знать лишь конечную цель своих действий, алгоритм, в соответствии с которым работает конкретное устройство, и порядок программирования его портов. То есть, фактически, нужно знать, что и в какой последовательности нужно послать в порт (при записи в него) или считать из него (при чтении) и как следует трактовать эту информацию. Для этого достаточно всего двух команд, присутствующих в системе команд процессора:
發 in <аккумулятор>,<номер„порта> — ввод в аккумулятор из порта с номером <но- мер_порта>;
熒 out <номер_порта>,<аккумулятор> — вывод содержимого аккумулятора в порт с номером <номер_порта>.
Возможные значения операндов этих команд приведены в приложении. Необходимо отметить, что использовать эти команды вы сможете без проблем только в программе, предназначенной для MS-DOS. При попытке их запуска в программе для Windows вы получите ошибку. Это не означает невозможности запуска исполняемого модуля описанной далее программы в сеансе Windows. Более того, Windows поддержит реализацию полного цикла разработки данной программы, но сделано это будет в специальном режиме работы — режиме виртуального iipo- цессора х86.
В качестве примера рассмотрим, как на уровне аппаратуры заставить компьютер издавать звуки через свой внутренний динамик. На большинстве компьютеров читателей это будет некоторый треск. Изменяя различные параметры программы, в идеале, вы можете получить звук, напоминающий сирену.
Вначале мы перечислим, какие аппаратные ресурсы будут задействованы и как ими надо управлять.
В большинстве компьютеров есть внутренний динамик. Раньше он использовался для того, чтобы издавать звуки при работе самых различный приложений, вплоть до игровых. Сейчас у него осталась единственная важная функция — воспроизведение звуков, которые генерирует BIOS на этапе тестирования и начальной загрузки.
ПРИМЕЧАНИЕНесмотря на то что прямой доступ к портам ввода-вывода доступен
только из среды MS-DOS, сведения о номерах портов и особенностях работы с ними полезны и при программировании для Windows. Если системы Windows 95/98 практически не закрывают доступ к портам, то в Windows NT/2000/XP любая попытка обращения к ним приведет к возникновению ошибки. Причина в том, что порты являются критически важным ресурсом, и механизмы защиты Windows NT/2000/XP не могут допустить их монополизацию каким-либо приложением. Операционная система Windows NT/2000/XP предоставляет программисту функции API для работы с устройствами, посредством которых в конечном итоге и осуществляется доступ к портам посредством команд IN и OUT. Попытка использовать эти команды в программе пользователя в среде Windows NT/2000/XP приведет к возникновению исключения по недопустимому коду операции. Важно понимать, что на низком уровне управление аппаратурой компьютера ведется с использованием тех же портов, что и при работе в MS-DOS, то есть физика остается, меняется логика управления аппаратурой. Если программе удастся получить уровень привилегий ядра (такой уровень имеют драйверы устройств), то в этом случае она может беспрепятственно использовать команды IN и OUT и работать с устройством так же, как в среде MS-DOS. Существуют специальные программы, которые позволяют получить доступ к портам ввода/вывода из программы пользователя, исключая необходимость написания драйвера. Среди файлов, прилагаемых к книге, содержатся две наиболее известные из таких программ — UserPort и PortTalk1.
Как это ни удивительно, но специальной схемы генерации звука для внутреннего динамика нет. Сигнал для управления динамиком формируется в результате совместной работы следующих микросхем:
программируемого периферийного интерфейса (ППИ) i8255;
таймера i8253.
Общая схема формирования такого сигнала показана на рис. 7.2.
Обсудим представленную схему. Основная работа по генерации звука производится микросхемой таймера. Микросхема таймера (далее просто таймер) имеет три канала с совершенно одинаковыми внутренней структурой и принципом работы. На каналы таймера подаются импульсы от микросхемы системных часов, которые, по сути, представляют собой генератор импульсов, работающий с частотой 1,19 МГц. Каждый канал имеет два входа и один выход. Выходы канала замкнуты на вполне определенные устройства компьютера. Так, канал 0 замкнут на
Все прилагаемые к книге файлы можно найти по адресу http://www.piter.com/download. — Примеч. ред.
Рис. 7.2. Схема формирования звука для встроенного динамика
контроллер прерываний, являясь источником аппаратного прерывания от таймера, которое возникает 18,2 раза в секунду. Канал 1 связан с микросхемой прямого доступа к памяти (DMA). И наконец, канал 2 выходит на динамик компьютера. Как мы отметили, каналы таймера имеют одинаковую структуру, основу которой составляют три регистра: регмсттгр ввода-вывода разрядностью 8 битов, регистр-фиксатор (latch register) ирегистр-счетчик (counter register), оба по 16 битов. Все регистры связаны между собой следующим образом. В регистр ввода-вывода извне помещается некоторое значение. Источником этого значения может быть либо системное программное обеспечение, либо программа пользователя. Каждый регистр ввода-вывода имеет адрес в адресном пространстве ввода-вывода (номер порта ввода-вывода). Регистр ввода-вывода канала 2 имеет номер порта ввода- вывода 42h. Помещаемые в него значения немедленно попадают в регистр-фиксатор, где значение сохраняется до тех пор, пока в регистр ввода-вывода не будет записано новое значение. Но как согласуются эти регистры по их разрядности, ведь один из них 8-, а другой 16-разрядный? Для этого предназначен регистр управления (ему соответствует порт 43h), который является частью механизма управления всей микросхемой таймера. Он содержит слово состояния, с помощью которого производятся выбор канала, задание режима работы канала и типа операции передачи значения в канал.
Далее описана структура слова состояния:
經 Бит 0 определяет тип константы пересчета: 0 — константа задана двоичным числом, 1 — константа задана двоично-десятичным (BCD) числом. Константа пересчета — значение, загружаемое извне в регистр-фиксатор; в нашем случае загружаться будет двоичное число, поэтому значение этого поля будет равно 0.
ii Биты 1-3 определяют режим работы микросхемы таймера. Всего можно определить шесть режимов, но обычно используется третий, поэтому для нашего случая значение поля — Oil.
т Биты 4-5 определяют тип операции: 00 — передать значение счетчика в регистр- фиксатор (то есть возможны не только операция записи значения в канал, но и извлечение значения регистра-счетчика из него), 10 — записать в регистр-фиксатор только старший байт, 01 — записать в регистр-фиксатор только младший байт, И — записать в регистр-фиксатор сначала старший байт, затем младший. В нашем случае значение поля будет 11. Поэтому формирование 16-разрядного регистра-фиксатора через 8-разрядный регистр ввода-вывода производится следующим образом: запись производится в два приема, первый байт из регистра ввода-вывода записывается на место старшего байта регистра-фиксатора, второй байт — на место младшего байта. Нетрудно догадаться, что в регистр ввода-вывода эти байты помещаются командами IN и OUT.
II Биты 6-7 определяют номер программируемого канала. В нашем случае они равны 10.
Для формирования любого звука необходимо задать его длительность и высоту. После того как значение из регистра ввода-вывода попало в регистр-фиксатор, оно моментально записывается в регистр-счетчик. Сразу же после этого значение регистра-счетчика начинает уменьшаться на единицу с приходом каждого импульса от системных часов. На выходе любого из трех каналов таймера стоит схема логического умножения. Эта схема имеет два входа и один выход. Значение регистра- счетчика участвует в формировании сигнала на одном из входов схемы логического умножения И. Сигнал на втором входе этой схемы зависит от состояния бита О регистра микросхемы интерфейса с периферией (порт 61h). В свое время мы подробно разберемся с логическими операциями, сейчас следует лишь пояснить, что единица на выходе схемы логического умножения может появиться только в одном случае — когда на обоих входах единицы. Когда значение в регистре-счетчике становится равным нулю, на соответствующем входе схемы И формируется такая единица. И если при этом на втором входе, значение которого зависит от бита О порта 61h, также 1, то импульс от системных часов проходит на выход канала 2. Одновременно с пропуском импульса в канале 2 немедленно производится загрузка содержимого регистра-фиксатора (которое не изменилось, если его не изменили извне) в регистр-счетчик. Весь процесс с уменьшением содержимого регистра-счетчика повторяется заново. Теперь вы понимаете, что чем меньшее значение загружено в регистр-фиксатор, тем чаще будет происходить обнуление регистра-счетчика и тем чаще импульсы будут проходить на выход канала 2. А это означает большее значение высоты звука. Понятно, что максимальное значение частоты на входе 1 динамика — 1,19 МГц. Таким образом, импульс с выхода канала 2 попадает
на динамик, и если на последний подан ток, то возникает долгожданный звук. Подачей тока на динамик управляет бит 1 порта 61h. Как прервать звучание? Очевидно, для этого возможны два пути: первый — отключить ток, сбросив бит 1 порта 61h, второй — сбросить бит 0 порта 61h. Эти две возможности используют для создания различных звуковых эффектов. Сбрасывая и устанавливая эти биты, мы фактически определяем длительность звучания.
Если вы внимательно следили за всеми рассуждениями, то, наверное, без труда сможете понять, почему первый канал таймера формирует сигналы аппаратного прерывания от таймера 18,2раза в секунду (на основании этих сигналов программы отслеживают время). Для этого BIOS во время загрузки после включения компьютера загружает в первый канал соответствующее значение.
Таким образом, наметились три последовательных действия, необходимые для программирования звукового канала таймера (они применимы и к остальным каналам).
Посредством порта 43 h выбрать канал, задать режим работы и тип операции передачи значения в канал. В нашем случае соответствующее значение будет равно 10110110 = 0b6h.
Подать ток на динамик, установив бит 1 порта 61h.
Используя регистр АХ, поместить нужное значение в порт 42h, определив тем самым нужную высоту тона.
Далее в листинге 7.1, приведена программа, реализующая некоторые звуки. Многие команды вам уже знакомы, некоторые мы пока еще не рассматривали, поэтому поясним их функциональное назначение. Подробно они будут рассмотрены в последующих главах. Для удобства в программе была использована макрокоманда delay, выполняющая задержку работы программы на заданное время. Подробнее механизм макроподстановок будет рассмотрен в главе 14. Сейчас только отметьте для себя, что введенная таким образом макрокоманда в тексте программы синтаксически ничем не отличается от других команд ассемблера, и это позволяет программисту при необходимости расширить стандартный набор команд ассемблера. Введите текст макрокоманды delay (строки 13-25) и воспринимайте ее чисто по функциональному назначению (задержка выполнения программы на промежуток времени, задаваемый значением ее операнда). Стоит отметить, что данная макрокоманда чувствительна к производительности процессора, из-за чего звуки на компьютерах с разными моделями процессоров могут не совпадать. Сегмент кода, как обычно, начинается с настройки сегментного регистра DS (строки 32-33) на начало сегмента данных. После этого строками 37-38 мы выполняем действия первого этапа — настройку канала 2, которая заключается в записи в регистр управления (порт 43h) байта состояния OB6h. На втором шаге мы должны установить биты 0 и 1 порта 61h. Предварительно необходимо извлечь содержимое этого порта. Это делается для того, чтобы выполнять установку битов 0 и 1, не изменяя содержимого остальных битов порта 61h (строки 39-41). Принцип формирования сигнала сирены заключается в том, что в цикле на единицу изменяется содержимое регистра-счетчика и делается небольшая задержка для того, чтобы сигнал некоторое время звучал с нужной высотой. Постепенное повышение, а за-
Ввод из порта и вывод в порт155
тем понижение высоты и дает нам эффект сирены. Строки 43-53 соответствуют циклу, в теле которого высота повышается, а строки 55-62 — циклу понижения тона. Оба цикла повторяются последовательно 5 раз. Контроль осуществляется с помощью переменной cnt, содержимое которой увеличивается на 1 в строке 69 и контролируется на равенство 5 в строках 71-72. Если cnt = 5, то команда СМР устанавливает определенные флаги. Последующая команда условного перехода JNE анализирует эти флаги и в зависимости от их состояния передает управление либо на метку, указанную в качестве операнда этой команды, либо на следующую за JNE команду. Цикл в программе ассемблера можно организовать несколькими способами; все они будут подробно рассмотрены в главах 10 и 11. В данном случае цикл организуется командой ШОР, которая в качестве операнда имеет имя метки. На эту метку и передается управление при выполнении команды ШОР. Но до того как передать управление, команда ШОР анализирует содержимое регистра ЕСХ/СХ, и если оно равно нулю, управление передается не на метку, а на следующую за LOOP команду. Если содержимое регистра ЕСХ/СХ не равно нулю, то оно уменьшается на единицу и управление передается на метку. Таким образом в ЕСХ/СХ хранится счетчик цикла. В нашей программе он загружается в ЕСХ/СХ в строках 42 и 54.
Листинг7.1. Реализация сирены
;Prg_7_l. asm
;Про「рам1йа'7HMитмрукпцая звук сирены.
;Изменение высоты звука от 450 до 2100 Гц.
;Используется макрос delay (задержка).
;При необходимости
4<14<1masmmodelsmallstack100h;макрос задержки, его текст ограничивается директивами macro и endm.;На входе — значениезадержки (в MKC)delay macro timelocal ext,i terpush cxmovex,timeext:push cxmovex,5000iter:loopiterpopcxloopextPopcxendm .dataсегмент данныхtonelow dw 2651нижняя граница звучания 450 Гцcntdb 0счетчик для выхода из программыtempdw 7верхняя граница звучания.code;сегмент кодаmain:точка входа в программуmov ax ,@dataсвязываем регистр ds с сегментомmov ds,axданных через регистр ахmov ax,0очищаем ахmasmmodelsmallstack100h;макрос задержки, его текст ограничивается директивами macro и endm.;На входе — значениезадержки (в MKC)delay macro timelocal ext,i terpush cxmovex,timeext:push cxmovex,5000iter:loopiterpopcxloopextPopcxendm .dataсегмент данныхtonelow dw 2651нижняя граница звучания 450 Гцcntdb 0счетчик для выхода из программыtempdw 7верхняя граница звучания.code;сегмент кодаmain:точка входа в программуmov ax ,@dataсвязываем регистр ds с сегментомmov ds,axданных через регистр ахmov ax,0очищаем ахgo: :им слово состояния 10110110b(0B6h) в командный регистр (порт 43h)~ продолжение决go: :им слово состояния 10110110b(0B6h) в командный регистр (порт 43h)~ продолжение决>>>>>>>> 01234567 22222222 <<<<<<<<>>>>>>>> 01234567 22222222 <<<<<<<<>>>>>>>>> 89012345б 223333333 <<<<<<<<<>>>>>>>>> 89012345б 223333333 <<<<<<<<<;можно поменять значение задержки (по умолчанию - для процессора Pentium).
4
<1
4
<1
masm
modelsmall
stack100h
;макрос задержки, его текст ограничивается директивами macro и endm.
;На входе — значение
задержки (в MKC)
delay macro time
local ext,i ter
push cx
movex,time
ext:
push cx
movex,5000
iter:
loopiter
popcx
loopext
Popcx
endm .data
сегмент данных
tonelow dw 2651
нижняя граница звучания 450 Гц
cntdb 0
счетчик для выхода из программы
tempdw 7
верхняя граница звучания
.code
;сегмент кода
main:
точка входа в программу
mov ax ,@data
связываем регистр ds с сегментом
mov ds,ax
данных через регистр ах
mov ax,0
очищаем ах
masm
modelsmall
stack100h
;макрос задержки, его текст ограничивается директивами macro и endm.
;На входе — значение
задержки (в MKC)
delay macro time
local ext,i ter
push cx
movex,time
ext:
push cx
movex,5000
iter:
loopiter
popcx
loopext
Popcx
endm .data
сегмент данных
tonelow dw 2651
нижняя граница звучания 450 Гц
cntdb 0
счетчик для выхода из программы
tempdw 7
верхняя граница звучания
.code
;сегмент кода
main:
точка входа в программу
mov ax ,@data
связываем регистр ds с сегментом
mov ds,ax
данных через регистр ах
mov ax,0
очищаем ах
go: :им слово состояния 10110110b(0B6h) в командный регистр (порт 43h)~ продолжение决
go: :им слово состояния 10110110b(0B6h) в командный регистр (порт 43h)~ продолжение决
>>>>>>>> 01234567 22222222 <<<<<<<<
>>>>>>>> 01234567 22222222 <<<<<<<<
>>>>>>>>> 89012345б 223333333 <<<<<<<<<
>>>>>>>>> 89012345б 223333333 <<<<<<<<<
Листинг 7«1 (продолжение)
<37>
raov al,0B6h
<38>
out 43h,al
<39>
in al,61h
;получим значение порта 61h в al
<40>
or al,3
;инициализируем динамик и подаем ток в порт 61h
<41>
out 61h,al
<42>
mov ex,2083
;количество шагов ступенчатого изменения тона
<43>
musicup:
<44>
;в ах значение нижней границы частоты
<45>
mov ax,tonelow
<46>
out 42h,al
;в порт 42h младшее слово ax: al
<47>
xchg al, ah
;обмен между al и ah
<48>
out 42h.al
;в порт 42h старшее слово ax: ah
<49>
add tonelow,1
повышаем тон
<50>
delay 1
;задержка на 1 мкс
<51>
mov dx,tonelow
:в dx текущее значение высоты
<52>
mov temp.dx
;temp — верхнее значение высоты
<53>
loop musicup
;повторить цикл повышения
<54>
mov ex,2083
;восстановить счетчик цикла
<55>
musiedown:
<56>
mov ax,temp
;в ах верхнее значение высоты
<57>
out 42h,al
;в порт 42h младшее слово ах: al
<58>
mov al, ah
;обмен между al и ah
<59>
out 42h,al
;в порт 42h старшее слово ах: ah
<60>
sub temp,1
;понижаем высоту
<61>
delay 1
;задержка на 1 мкс
<62>
loop musicdown
;повторить цикл понижения
<63>
nosound:
<64>
in al,61h
;получим значение порта 61h в AL
<65>
and al,0FCh
выключить динамик
<66>
out 61h,al
;в порт 61h
<67>
mov dx,2651
;для последующих циклов
<68>
mov tonelow,dx
<69>
inc cnt
увеличиваем счетчик проходов, то есть
<70>
количество звучаний сирены
<71>
emp ent,5
;5 раз ?
<72>
jne go
если нет, идти на метку go
<73>
exit:
<74>
mov ax,4c00h стандартный выход
<75>
i nt 21h
<76>
end main;конец программы
Среди файлов, прилагаемых к книге, в каталоге данной главы есть еще один пример использования команд ввода из порта и вывода в порт — это редактор CMOS-памяти. На данном этапе изучения ассемблера не стоит пытаться разобраться с ним, но впоследствии обязательно стоит к нему вернуться.
Работа с адресами и указателями
При написании программ на ассемблере производится интенсивная работа с адресами операндов, находящимися в памяти. Для поддержки такого рода операций есть специальная группа команд, в которую входят следующие команды.
綫: lea <приемник>,<источник> — загрузка эффективного адреса;
綴 Ids <приемник>,<источник> — загрузка указателя в регистр сегмента данных ds;
я les <приемник>,<источник> — загрузка указателя в регистр дополнительного сегмента данных es;
я 1д5<приемник>,<источник> — загрузка указателя в регистр дополнительного сегмента данных gs;
* 1Г5<приемник>,<источник> — загрузка указателя в регистр дополнительного сегмента данных fs;
綫孑 Iss <приемник>,<источник> — загрузка указателя в регистр сегмента стека ss.
КомандаLEAпохожанакоманду MOV тем, что онатакже производитпересылку, однако команда LEA производит пересылку не данных, а эффективного адреса данных (то есть смещения данных относительно начала сегмента данных) в регистр, указанный операндом <приемник>.
Часто для выполнения некоторых действий в программе недостаточно знать значение одного лишь эффективного адреса данных, а необходимо иметь полный указатель на данные. Вы помните, что полный указатель на данные состоит из сегментной составляющей и смещения. Все остальные команды этой группы позволяют получить в паре регистров такой полный указатель на операнд в памяти. При этом имя сегментного регистра, в который помещается сегментная составляющая адреса, определяется кодом операции. Соответственно, смещение помещается в регистр общего назначения, указанный операндом <приемник>. Но не все так просто с операндом <источник>. На самом деле в команде в качестве источника нельзя указывать непосредственно имя операнда в памяти, на который мы бы хотели получить указатель. Предварительно необходимо получить само значение полного указателя в некоторой области памяти и задать в команде получения полного адреса имя этой области. Для выполнения этого действия необходимо вспомнить директивы резервирования и инициализации памяти (см. главу 5). При применении этих директив возможен частный случай, когда в поле операндов указывается имя другой директивы определения данных (фактически, имя переменной). В этом случае в памяти формируется адрес этой переменной. Какой адрес будет сформирован (эффективный или полный), зависит от применяемой директивы. Если это DW, то в памяти формируется только 16-разрядное значение эффективного адреса, если же DD — в память записывается полный адрес. Размещение этого адреса в памяти следующее: в младшем слове находится смещение, в старшем — 16-разрядная сегментная составляющая адреса. Посмотрите на листинг 5.3 и рис. 5.20 (глава 5). В сегменте данных программы из листинга 5.3 переменные adr и adr_full иллюстрируют наш случай получения частичного и полного указателей на данные в памяти.
Например, при организации работы с цепочкой символов удобно поместить ее начальный адрес в некоторый регистр и далее в цикле модифицировать это значение для последовательного доступа к элементам цепочки. В листинге 7.2 производится копирование строки байтов str_l в строку байтов str_2. В строках 13 и 14 в регистры SI и DI загружаются значения эффективных адресов переменных str_l и str_2. В строках 18,19 производится пересылка очередного байта из одной строки в другую. Указатели на позиции байтов в строках определяются содержимым регистров SI и DI. Для пересылки очередного байта необходимо увеличить на единицу регистры SI и DI, что и делается командами сложения INC (строки20,21).После этого программу необходимо зациклить до обработки всех символов строки.
Листинг 72 Копирование строки
1>; Prg_7_2.asm
2> masm
3> model small
4> .data
5>
6> str_l db "Ассемблер - базовый язык компьютера "
7> str_2 db 35 dup
8> full_pnt dd str_l
9>
10>.code
11>start:
13>lea si ,str_l
14>lea di ,str二2
15> les bx,full~Pnt ;полный указатель на strl в пару es:bx
16> mov ex,35 ;счетчик цикла для loop ml (глава 10)
17> ml:
18>moval ,[si]
19>mov[di],al
20>incsi
21>incdi
22>;цикл на метку ml до пересылки всех символов (loop ml)
23>
24>end start
Необходимость использования команд получения полного указателя данных в памяти, то есть адреса сегмента и значения смещения внутри сегмента, возникает, в частности, при работе с цепочками. Мы рассмотрим этот вопрос в главе 12. В строке 8 листинга 7.2 в двойном слове fulLpnt формируются сегментная часть адреса и смещение для переменной str_l. При этом два байта смещения занимает младшее слово fulLpnt, а значение сегментной составляющей адреса — старшее словоfulLpnt. В строке 15 командой LES этикомпоненты адреса помещаются в регистры ВХ и ES.
Преобразованиеданных
К группе команд преобразования данных можно отнести множество команд процессора, но большинство из них имеют те или иные особенности, которые требуют отнести их к другим функциональным группам (см. рис 3.3). Поэтому из всей совокупности команд процессора непосредственно к командам преобразования данных можно отнести только одну команду xlat [адрес_таблицы_перекодировки]
Это очень интересная и полезная команда. Ее действие заключается в том, что она замещает значение в регистре AL другим байтом из таблицы в памяти, расположенной по адресу, указанному операндом адрес_таблицы_перекодировки. Слово «таблица» весьма условно; по сути, это просто строка байтов. Адрес байта в строке, которым будет производиться замещение содержимого регистра AL, определяется суммой (ВХ) + (AL), то есть содержимое AL играет роль индекса в байтовом массиве.
При работе с командой XLAT обратите внимание на следующий тонкий момент. Хотя в команде указывается адрес строки байтов, из которой должно быть извлечено новое значение, этот адрес должен быть предварительно загружен (например, с помощью команды LEA) в регистр ВХ. Таким образом, операнд адрес_табли-
данных
на самом деле не нужен (на это указывают квадратные скобки). Что касается строки байтов (таблицы перекодировки), то она представляет собой область памяти размером от 1 до 255 байт (диапазон числа без знака в 8-разрядном регистре).
В качестве иллюстрации работы данной команды мы рассмотрим программу из листинга (см. главу 6). Вы помните, что эта программа преобразовывала двузначное шестнадцатеричное число, вводимое с клавиатуры (то есть в символьном виде), в эквивалентное двоичное представление в регистре AL. В листинге 7.3 приведен вариант этой программы с использованием команды XLAT.
Листинг 7.3. Использование таблицы перекодировки
1> ;Prg_7_3 . asm
2>преобразования двузначногочисла
3> ;в двоичное представление с использованием команды
4> ;Вход: исходное шестнадцатеричное число; вводится с клавиатуры.
5>результат преобразования в регистре
6>
7> model small
8>данных
<9> message db "Введите две
db 487(0)
db26 dup (0)
<12>
db
0ah,Obh,0ch,0dh,0eh,0fh,
152 dup (0)
<13>
.stack
256стека
<14>
.code
<15>
;начало
сегмента кода
<16>
main procпроцедуры
main
<17>
mov
адрес
сегмента данных в регистр ах
<18>
mov
;ax записываем в
ds
<19>
lea
;загрузка адреса
строки байт в регистр Ьх
<20>
mov
ah, 9
<21>movmessage
<22>
int 21h
приглашение к вводу
<23>
xor ax,ax
;очистить регистр ах
<24>
mov
;значение lh в регистр ah
<25>
int 21h
первую цифру в al
<26>
xlat
первого введенного символа в al
<27>
mov
<28>
влево для освобождения места для младшей цифры
<29>
int 21h
;ввод второго символа в al
<30>
xlat
второго введенного символа в al
<31>
add
для получения результата
<32>
movв регистр ах
<33>
int
;завершение программы
<34>
endp main
;конец процедуры main
<36>end mainпрограммы с точкой входа main
Сама по себе программа проста; сложность вызывает обычно формирование таблицы перекодировки. Обсудим этот вопрос подробнее. Прежде всего нужно определиться со значениями тех байтов, которые вы будете изменять. В нашем случае это символы цифр. В главе 6 мы рассматривали их ASCII-коды. Поэтому мы конструируем в сегменте данных таблицу, в которой на места байтов, соответствующих символам шестнадцатеричных цифр, помещаем их новые значения, то есть двоичные эквиваленты шестнадцатеричных цифр. Строки 10-12 листинга 7.3 демонстрируют, как это сделать. Байты этой таблицы, смещения которых не совпадают со значением кодов шестнадцатеричных цифр, нуле-
вые. Таковыми являются первые 48 байт таблицы, промежуточные байты и часть в конце таблицы. Желательно определить все 256 байт таблицы. Дело в том, что если мы ошибочно поместим в AL код символа, отличный от символа шестнадцатеричной цифры, то после выполнения команды XLAT получим непредсказуемый результат. В случае программы из листинга 7.3 это будет ноль, что не совсем корректно, так как непонятно, что же в действительности было в AL: код символа 0 или что-то другое. Поэтому, наверное, есть смысл здесь поставить «защиту от дурака», поместив в неиспользуемые байты таблицы какой-нибудь определенный символ. После каждого выполнения команды XLAT нужно будет просто контролировать значение в AL на предмет совпадения с этим символом, и если оно имело место, выдавать сообщение об ошибке.
После того как таблица составлена, с ней можно работать. В сегменте команд строка 19 инициализирует регистр ВХ значением адреса таблицы TABL Далее все очень просто. Поочередно вводятся символы двух шестнадцатеричных цифр и производится их перекодировка в соответствующие двоичные эквиваленты. В остальном программа аналогична программе из листинга 6.1.
Для закрепления знаний и исследования трудных моментов запустите программу из листинга 7.3 под управлением отладчика.
Работа со стеком
Стек — это область памяти, специально выделяемая для временного хранения данных программы. Важность стека определяется тем, что для него в структуре программы предусмотрен отдельный сегмент. На тот случай, если программист забыл описать сегмент стека в своей программе, компоновщикtlink выдаст предупреждающее сообщение.
Для работы со стеком предназначены три регистра:
И SS — регистр сегмента стека;
蠶SP/ESP — регистр указателя стека;
纖ВР/ЕВР — регистр указателя базы кадра стека.
Размер стека зависит от режима работы процессора и ограничивается значением 64 Кбайт (или 4 Гбайт в защищенном режиме). В каждый момент времени доступен только один стек, адрес сегмента которого содержится в регистре SS. Этот стек называется текущим. Для того чтобы обратиться к другому стеку («переключить стек»), необходимо загрузить в регистр SS другой адрес. Регистр SS автоматически используется процессором для выполнения всех команд, работающих со стеком.
Перечислим еще некоторые особенности работы со стеком.
• Запись и чтение данных в стеке осуществляются в соответствии с принципом LIFO (Last In First Out — «последним пришел, первым ушел»).
* По мере записи данных в стек последний растет в сторону младших адресов. Эта особенность заложена в алгоритм команд работы со стеком.
и При использовании регистров ESP/SP и ЕВР/ВР для адресации памяти ассемблер автоматически считает, что содержащиеся в нем значения представляют собой смещения относительно сегментного регистра SS.
0000:00000000:0000Оперативная памятьОперативная памятьСегмент кодаСегмент кодаСегмент данныхСегмент стекаСегмент данныхСегмент стекаss:spss:spВершина стекаВершина стекаНаправлениероста стекаНаправлениероста стека
0000:0000
0000:0000
Оперативная память
Оперативная память
Сегмент кода
Сегмент кода
Сегмент данных
Сегмент стека
Сегмент данных
Сегмент стека
ss:sp
ss:sp
Вершина стека
Вершина стека
Направлениероста стека
Направлениероста стека
Дно стекаДно стекаss:ffff
Дно стека
Дно стека
Старшие адреса on
Рис. 7.3. Концептуальная схема организации стека
В общем случае стек организован так, как показано на рис. 7.3.
Регистры SS, ESP/SP и EBP/BPf используются комплексно, и каждый из них пме- ет свое функциональное назначение. Регистр ESP/SP всегда указывает на вершину стека, то есть содержит смещение, по которому в стек был занесен последний элемент. Команды работы со стеком неявно изменяют этот регистр так, чтобы он указывал всегда на последний записанный в стек элемент. Если стек пуст, то значение ESP равно адресу последнего байта сегмента, выделенного под стек. При занесении элемента в стек процессор уменьшает значение регистра ESP, а затем записывает элемент по адресу новой вершины. При извлечении данных из стека процессор копирует элемент, расположенный по адресу вершины, а затем увеличивает значение регистра указателя стека ESP. Таким образом, получается, что стек растет вниз, в сторону уменьшения адресов.
Что нужно сделать для получения доступа к элементам не на вершине, а внутри стека? Для этого применяют регистр ЕВР. Регистр ЕВР — регистр указателя базы кадра стека. Например, типичным приемом при входе в подпрограмму является передача нужных параметров путем записи их в стек. Если подпрограмма тоже активно работает со стеком, то доступ к этим параметрам становится проблематичным. Выход в том, чтобы после записи нужных данных в стек сохранить адрес вершины стека в указателе базы кадра стека — регистре ЕВР. Значение в ЕВР в дальнейшем можно использовать для доступа к переданным параметрам.
Начало стека расположено в старших адресах памяти. На рис. 7.3 этот адрес обозначен парой SS:fflf. Смещение ffffприведено здесь условно. Реально это значение определяется величиной, которую программист задает при описании сегмента стека в своей программе. К примеру, для программы из листинга 7.1 началу стека будет соответствовать пара SS:OlOOh. Адресная пара SS:fffl— это максимальное для реального режима значение адреса начала стека, так как размер сегмента в нем ограничен величиной 64 Кбайт (Offffh). .
I Какой из регистров применяется для адресации, 32-разрядный или 16-разрядный, зависит от значе- ИИЯ модификатора usel6 или use32 в директиве сегментации segment (см. главу 5). При наличии директив упрощенного описания сегментов модификаторыuse 16или use32 используются вдирективе model.
б Зак. 256
Для организации работы со стеком существуют специальные команды записи и чтения.
Команда PUSH выполняет запись значения <источник> в вершину стека: push <источник>
Интерес представляет алгоритм работы этой команды, который включает два действия (рис. 7.4):
Значение SP уменьшается на 2:
(SP) = (SP) - 2
Значение источника записывается по адресу, указываемому парой SS:SP.
Оперативная памятьОперативная памятьОперативная памятьОперативная память
Оперативная память
Оперативная память
Оперативная память
Оперативная память
Старшие адреса ОПдо командыPUSH операндСтаршие адреса ОПдо командыPUSH операндСтаршие адреса ОП
Старшие адреса ОПдо команды
PUSH операнд
Старшие адреса ОПдо команды
PUSH операнд
после команды |Операнп|
PUSH операндИсточник
Рис. 7.4. Принцип работы команды PUSH
Команда POP выполняет запись значения из вершины стека по месту, указанному операндом <приемник> (значение при этом «снимается» с вершины стека): pop <приемник>
Алгоритм работы команды POP обратен алгоритму команды PUSH (рис. 7.5).
Запись содержимого вершины стека по месту, указанному операндом <приемник>.
Увеличение значения SP:
(SP) = (SP) +2
Команда PUSHA предназначена для групповой записи в стек. По этой команде в стек последовательно записывается содержимое регистров АХ, СХ, DX, BX, SP, BP, SI, DI. Заметим, что записывается оригинальное содержимое SP, то есть то, которое было до выдачи команды PUSHA (рис. 7.6).
Команда PUSHAW почти идентична команде PUSHA. В чем разница? В главе 5 мы обсуждали один из атрибутов сегмента — атрибут размера сегмента. Он может приниматьзнаденияизе16 или use32:
Ж US616 — алгоритм работы PUSHAW аналогичен алгоритму PUSHA;
經 use32 — алгоритм работы команды PUSHAW не меняется (то есть она нечувствительна к разрядности сегмента и всегда работает с регистрами размером в слово — АХ, СХ, DX, BX, SP, BP, SI, DI), а команда PUSHA чувствительна к разрядности
Оперативная памятьОперативная памятьОперативная памятьОперативная памятьназначениеназначениеРис. 7.5. Принцип работы команды POPРис. 7.5. Принцип работы команды POP
Оперативная память
Оперативная память
Оперативная память
Оперативная память
назначение
назначение
Рис. 7.5. Принцип работы команды POP
Рис. 7.5. Принцип работы команды POP
Оперативная памятьОперативная памятьОперативная памятьОперативная памятьСтаршие адреса ОП Стек до команды PUSHAСтаршие адреса ОП Стек до команды PUSHAСтаршие адреса ОП Стек после команды PUSHAСтаршие адреса ОП Стек после команды PUSHAРис. 7.6. Принцип работы команды PUSHAРис. 7.6. Принцип работы команды PUSHA
Оперативная память
Оперативная память
Оперативная память
Оперативная память
Старшие адреса ОП Стек до команды PUSHA
Старшие адреса ОП Стек до команды PUSHA
Старшие адреса ОП Стек после команды PUSHA
Старшие адреса ОП Стек после команды PUSHA
Рис. 7.6. Принцип работы команды PUSHA
Рис. 7.6. Принцип работы команды PUSHA
сегмента и при указании 32-разрядного сегмента работает с соответствующими 32-разрядными регистрами (то есть EAX, ЕСХ, EDX, EBX, ESP, EBP, ESI, EDI).
Команда PUSHAD — выполняется аналогично команде PUSHA, но есть некоторые особенности, которые вы можете найти в приложении.
Следующие три команды выполняют действия, обратные действиям описан
НЫХ ранее команд:
* POPA;
О POPAW;
т POPAD.
Представленная далее группа команд позволяет сохранить в стеке регистр флагов и записать слово или двойное слово. Отметим, что перечисленные команды — единственные в системе команд процессора, которые позволяют получить доступ (и которые нуждаются в этом доступе) ко всему содержимому регистра флагов.
Команда PUSH F сохраняет регистр флагов в стеке. Работа этой команды зависит от атрибута размера сегмента:
獭use 16 — в стек записывается регистр FLAGS размером два байта;
燃 use3 2 — в стек записывается регистр E FLAGS размером четыре байта.
Команда PUSHFWсохраняет в стеке регистр флагов размером в слово. С атрибутом usel6 всегда работает так же, как команда PUSHF.
Команда PUSHFD сохраняет в стеке регистр флагов FLAGS или EFLAGS в зависимости от атрибута размера сегмента (то есть то же, что и PUSHF).
Следующие три команды также выполняют действия, обратные действиям рассмотренных выше команд:
畿 POPF;
* POPFW;
и POPFD.
Работать со стеком приходится постоянно, поэтому к этому вопросу мы будем возвращаться еще не раз. Отметим основные виды операций, когда использование стека практически неизбежно:
ш вызов подпрограмм;
緻 временное сохранение значений регистров;
綴 определение локальных переменных в процедуре.
Итоги
* Основная команда пересылки данных — MOV. Операнды этой команды (как и большинства других команд, берущих значения из памяти) должны быть согласованы по разрядности. Хотя обычно действуют правила умолчания, в сомнительных ситуациях лучше явно указывать разрядность операндов с помощью оператора PTR.
Ш Управление периферией компьютера в общем случае осуществляется с использованием всего двух команд ввода-вывода — IN и OUT.
ш В процессе работы программы динамически можно получить как эффективный, так и полный (физический) адрес памяти. Для этого язык ассемблера предоставляет группу команд получения указателей памяти.
8 Архитектура процессора предоставляет в распоряжение программиста специфическую, но весьма эффективную структуру — стек. Система команд поддерживает все необходимые операции со стеком. Подробнее со стеком мы познакомимся при изучении модульного программирования на ассемблере.
Глава 8
Арифметические команды
Форматы арифметических данных
Арифметические операции над двоичными числами
^ Арифметические операции над десятичными (BCD) числами
Одной из причин, постоянно заставляющих человека совершенствовать средства для выполнения вычислений, — желание эффективно, быстро и без ошибок решать различные счетные задачи. Для начала мечтой людей была автоматизация выполнения простейших арифметических действий. Первая реализованная попытка — начало XVII в., 1623 г. Ученый В. Шикард создает машину, умеющую складывать и вычитать числа. Знаменитый французский ученый и философ Блез Паскаль в 1642 г. изобрел первый арифмометр, основным элементом в котором было зубчатое колесо. Изобретение этого колеса уже само по себе было ключевым событием в истории вычислительной техники, подобно лампам и транзисторам в наше время. Правнуки этого колеса еще совсем недавно, каких-нибудь два-три десятка лет назад, использовались в арифмометрах (соответствующая модель была создана в 1842 г.) на столах советских бухгалтеров. Тот, кому довелось поработать на этих арифмометрах, вряд ли вспомнят о высокой эффективности вычислительного процесса — слишком велика была зависимость от человеческого фактора. Снизить эту зависимость удалось лишь в середине прошлого века, когда появились первые ЭВМ на лампах, потом на транзисторах и, наконец, на микросхемах различной интеграции. Таким образом, путь к эффективному автоматизированному решению для проведения расчетов растянулся почти на три столетия. Тем не менее, именно благодаря стремлению разгрузить голову от рутины человек имеет сегодня определенные достижения в области компьютерной техники.
Любой компьютер, от самого примитивного до супермощного, имеет в своей системе команд команды для выполнения арифметических действий. Работая
с компьютером при помощи языков высокого уровня, мы воспринимаем возможность проведения расчетных действий как нечто должное, забывая при этом, что компилятор даже очень развитого языка программирования превращает все самые высокоуровневые действия в унылую последовательность машинных команд. Конечно, мало кому придет в голову писать серьезную расчетную задачу на ассемблере. Но даже в системных программах часто требуется проведение небольших вычислений. Поэтому важно разобраться с этой группой команд. К тому же она, на удивление, очень компактна и не избыточна.
Процессор может выполнять целочисленные операции и операции с плавающей точкой. Для этого в его архитектуре есть два отдельных устройства, каждое из которых имеет свою систему команд. В принципе, целочисленное устройство может взять на себя многие функции устройства с плавающей точкой, но это потребует больших вычислительных затрат. Устройство с плавающей точкой и его система команд будут рассмотрены в главе 17. Для большинства задач, использующих язык ассемблера, достаточно целочисленной арифметики.
Обзор
Целочисленное вычислительное устройство поддерживает чуть больше десятка арифметических команд. На рис. 8.1 приведена классификация команд этой группы.
Обзор167
Группа арифметических целочисленных команд работает с двумя типами чисел: * целыми двоичными числами, которые могут иметь или не иметь знаковый разряд, то есть быть числами со знаком или без знака;
緻 целыми десятичными числами.
В главе 3 мы обсуждали вопрос о форматах данных, поддерживаемых архитектурой IA-32. Рассмотрим теперь форматы данных, с которыми работают арифметические команды.
Целые двоичные числа
Целое двоичное число — это число, закодированное в двоичной системе счисления. В архитектуре IA-32 размерность целого двоичного числа может составлять 8, 16 или 32 бита. Знак двоичного числа определяется тем, как интерпретируется старший бит в представлении числа. Это 7-й, 15-й или 31-й биты для чисел соответствующей размерности (см. главу 5). При этом интересно то, что среди арифметических команд есть всего две, которые действительно учитывают этот старший разряд как знаковый, — это команды целочисленного умножения IMUL и деления IDIV. В остальных случаях ответственность за действия со знаковыми числами и, соответственно, со знаковым разрядом ЛОЖИТСЯ на программиста. К этому вопросу мы вернемся чуть позже. Диапазон значений двоичного числа зависит от его размера и трактовки старшего бита либо как старшего значащего бита числа, либо как бита знака числа (табл. 8.1).
Авто
,L,L^^翼¥3""'闌£考"^^?^茹 ^V鶴自蹋启以遥.]施矗寫
,L
,L
—Module: pcg_8_l File: C:\TA5M\WORK\prg_8_l.asm 15- ;prg_8_l.asm
masm model stack.data per_l pec_2 pec_3 pec_4.code main:masm model stack.data per_l pec_2 pec_3 pec_4.code main:small256;сегмент данныхsmall256;сегмент данныхdb dw dd dwdb dw dd dw239856 9875645 29857239856 9875645 29857T・]・Dump ds:0000 ds:0008 ds:0010 ds:0018T・]・Dump ds:0000 ds:0008 ds:0010 ds:00181774000017740000800000008000000026 00000026 000000BD 00 00 00BD 00 00 00BO 00 00 00BO 00 00 0096000000960000000000000000000000-3-[f] [|Al 1A£J|U 600 t00 00-3-[f] [|Al 1A£J|U 600 t00 00;сегмент кода;точка входа в программу;сегмент кода;точка входа в программуmovmov exit:mov int end mainmovmov exit:mov int end mainax,@data ;связываем регистр dx с сегментом ds,ax ;данных через регистр ах;посмотрите в отладчике дамп сегмента данных ax, 4c00h стандартный выход21h;конец программыax,@data ;связываем регистр dx с сегментом ds,ax ;данных через регистр ах;посмотрите в отладчике дамп сегмента данных ax, 4c00h стандартный выход21h;конец программы
masm model stack
.data per_l pec_2 pec_3 pec_4
.code main:
masm model stack
.data per_l pec_2 pec_3 pec_4
.code main:
small
256
;сегмент данных
small
256
;сегмент данных
db dw dd dw
db dw dd dw
23
9856 9875645 29857
23
9856 9875645 29857
T・]・Dump ds:0000 ds:0008 ds:0010 ds:0018
T・]・Dump ds:0000 ds:0008 ds:0010 ds:0018
17
74
00
00
17
74
00
00
80
00
00
00
80
00
00
00
26 00
00
00
26 00
00
00
BD 00 00 00
BD 00 00 00
BO 00 00 00
BO 00 00 00
96
00
00
00
96
00
00
00
00
00
00
00
00
00
00
00
-3-[f] [|
Al 1A£J|U 6
00 t
00 00
-3-[f] [|
Al 1A£J|U 6
00 t
00 00
;сегмент кода
;точка входа в программу
;сегмент кода
;точка входа в программу
mov
mov exit:
mov int end main
mov
mov exit:
mov int end main
ax,@data ;связываем регистр dx с сегментом ds,ax ;данных через регистр ах
;посмотрите в отладчике дамп сегмента данных ax, 4c00h стандартный выход
21h
;конец программы
ax,@data ;связываем регистр dx с сегментом ds,ax ;данных через регистр ах
;посмотрите в отладчике дамп сегмента данных ax, 4c00h стандартный выход
21h
;конец программы
簪鹏酒期慧柴楚盜案簪鹏酒期慧柴楚盜案
簪鹏酒期慧柴楚盜案
簪鹏酒期慧柴楚盜案
女蹂电£*费质;协思:■■' ? ' '¥風跋尽玄#%%"::思滋吟•点皂收泌
緣М^》総邈^窓懒В您譎^涵^!战戀亵^翦观找水做ййг^:
Рис. 8.2. Дамп памяти для сегмента данных листинга 8.1
Таблица 8.1. Диапазон значений двоичных чисел
Размерность поля
Целое без знака
Целое со знаком
Байт
0...255
-128...+ 127
Слово
0...65 535
-32 768...+32 767
Двойное слово
0...4 294 967 295
-2 147 483 648…+2 147 483 647
Как описать целые двоичные числа в программе? Это делается с использовани- емдирективописанияданныхDB, DW иDD. В главе 5описанывозможныеварианты содержимого полей операндов этих директив и диапазоны их значений. К примеру, последовательность описаний двоичных чисел из сегмента данных листинга 8.1 (помните о принципе «младший байт по младшему адресу») будет выглядеть в памяти так, как показано на рис. 8.2.
Листинг8.1. Числа с фиксированной точкой
:prg„8_l.asm masm
modelsmall
stack256
.data;сегмент данных
per_ldb23
per_2dw9856
per_3dd9875645
per_4dw29857
.code;сегмент кода
main:;точка входа в программу
mov ax,@data;связываем регистр dx с сегментом
mov ds,ax ;данных через регистр ах
exit:;посмотрите в отладчике дамп сегмента данных
mov ax,4c00h стандартный выход int 21h
end main;конец программы
Десятичные числа
Десятичные числа — специальный вид представления числовой информации, в основу которого положен принцип кодирования каждой десятичной цифры числа группой из четырех битов. При этом каждый байт числа содержит одну или две десятичные цифры в так называемом двоично-десятичном коде (Binary-Coded Decimal, BCD). Процессор хранит BCD-числа в двух форматах (рис. 8.3).
• В упакованном формате каждый байт содержит две десятичные цифры. Десятичная цифра представляет собой двоичное значение в диапазоне от 0 до 9 размером четыре бита. При этом код старшей цифры числа занимает старшие четыре бита. Следовательно, диапазон представления десятичного упакованного числа в одном байте составляет от 00 до 99.
趣 В неупакованном формате каждый байт содержит одну десятичную цифру в четырех младших битах. Старшие четыре бита имеют нулевое значение. Это так называемая зона. Следовательно, диапазон представления десятичного неупакованного числа в одном байте составляет от 0 до 9.
Как описать двоично-десятичные числа в программе? Для этого можно использовать только две директивы описания и инициализации данных — DB и DT. Воз-
Упакованное десятичное число 5674304:
0.5 6.7
4[3 0 4
0000 0101
0110 0111
0100 0011
0000 0100
можность применения только этих директив для описания BCD-чисел обусловлена тем, что к таким числам также применим принцип «младший байт по младшему адресу», что, как мы увидим далее, очень удобно для их обработки. И вообще, при использовании такого типа данных, как BCD-числа, порядок описания этих чисел в программе и алгоритм их обработки — это дело вкуса и личных пристрастий программиста, что станет более ясным после того, как мы далее рассмотрим основы
Рис. 8.4. Дамп памяти для сегмента данных листинга 8.2
работы с BCD-числами. К примеру, приведенная в сегменте данных листинга 8.2 последовательность описаний BCD-чисел будет выглядеть в памяти так, как показано на рис. 8.4.
Листинг 8.2. BCD-числа
;prg_8_2.asm
masm
modelsmall
stack 256
.data
;сегмент данных
per_l
db
2,3,4,6,8,2
;неупакованное BCD-число 286432
per_3
dt
9875645
;упакованное BCD-число 9875645
.code
;сегмент кода
main:
;точка входа в программу
mov
ax,@data
;связываем регистр dx с сегментом
mov
ds.
ax
:данных через регистр ах
exit:
;посмотрите в отладчике дамп сегмента данных
mov
ax,4c00h
стандартный выход
int 21h
end main
;конец программы
После столь подробного обсуждения форматов данных, с которыми работают арифметические операции, можно приступить к рассмотрению средств их обработки на уровне системы команд процессора.
Арифметические операции над целыми двоичными числами
В данном разделе мы рассмотрим особенности каждого из четырех основных арифметических действий для целых двоичных чисел со знаком и без знака. Дополнительные сведения о возможностях ассемблера по обработке целых двоичных чисел можно получить в [8].
Сложение двоичных чисел без знака
Процессор выполняет сложение операндов по правилам сложения двоичных чисел. Проблем не возникает до тех пор, пока значение результата не превышает размерности поля операнда (см. табл. 8.1). Например, при сложении операндов размером в байт результат не должен превышать 255. Если это происходит, то результат оказывается неверен. Рассмотрим, почему. К примеру, выполним сложение: 254 + + 5 = 259 в двоичном виде:
11111110 + 0000101 - 1 00000011.
Результат вышел за пределы восьми битов, и правильное его значение укладывается в 9 битов, а в 8-разрядном поле операнда осталось значение 3, что, конечно, неверно. В процессоре подобный исход сложения прогнозируется, и предусмотрены специальные средства для фиксации подобных ситуаций и их обработки. Так, для фиксации ситуации выхода за разрядную сетку результата, как в данном случае, предназначен флаг переноса CF. Он располагается в бите 0 регистра флагов EFLAGS/FLAGS. Именно установкой этого флага фиксируется факт переноса единицы из старшего разряда операнда. Естественно, что программист должен предусматривать возможность такого исхода операции сложения и средства для кор-
ректировки. Это предполагает включение фрагментов кода после операции сложения, в которых анализируется состояние флага CF. Этот анализ можно провести различными способами. Самый простой и доступный — использовать команду условного перехода JC. Эта команда в качестве операнда имеет имя метки в текущем сегменте кода. Переход на метку осуществляется в случае, если в результате работы предыдущей команды флаг CF установился в 1. Команды условных переходов будут рассматриваться в главе 10.
Если теперь посмотреть на рис. 8.1, то видно, что в системе команд процессора имеются три команды двоичного сложения:
幾 команда инкремента, то есть увеличения значения операнда на 1:
i nc операнд
戀 команда сложения (операнд__1 =+ операнд_2):
add операнд_1,операнд_2
総 команда сложения с учетом флага переноса CF(операнд_1 = операнд_1 +операнд_2
+ значение_СЕ):
adc операнд_1,операнд_2
Обратите внимание на последнюю команду — это команда сложения, учитывающая перенос единицы из старшего разряда. Механизм появления такой единицы мы уже рассмотрели. Таким образом, команда ADC является средством процессора для сложения длинных двоичных чисел, размерность которых превосходит поддерживаемые процессором размеры стандартных полей.
Рассмотрим пример вычисления суммы чисел (листинг 8.3).
Листинг 8.3. Вычисление суммы чисел
<1> ;prg 8 3.asm<1> ;prg 8 3.asm2> masm3> model4> stack5> .data6> adb7> .code8> main:9>mov<10><11><12><14><15>2> masm3> model4> stack5> .data6> adb7> .code8> main:9>mov<10><11><12><14><15>small 256254ax,@data mov ds,axxor ax, ax add al, 17 add al, a jnc ml adc ah,0small 256254ax,@data mov ds,axxor ax, ax add al, 17 add al, a jnc ml adc ah,0;сегмент кода;сегмент кода;если нет переноса, то перейти на ml ;в ах сумма с учетом переноса;если нет переноса, то перейти на ml ;в ах сумма с учетом переноса17> ml:18>exit:19>mov ax ,4c00h стандартныйвыход20>int 21h21>end main;конец программы17> ml:18>exit:19>mov ax ,4c00h стандартныйвыход20>int 21h21>end main;конец программы
<1> ;prg 8 3.asm
<1> ;prg 8 3.asm
2> masm
3> model
4> stack
5> .data
6> adb
7> .code
8> main:
9>mov
<10>
<11>
<12>
<14>
<15>
2> masm
3> model
4> stack
5> .data
6> adb
7> .code
8> main:
9>mov
<10>
<11>
<12>
<14>
<15>
small 256
254
ax,@data mov ds,ax
xor ax, ax add al, 17 add al, a jnc ml adc ah,0
small 256
254
ax,@data mov ds,ax
xor ax, ax add al, 17 add al, a jnc ml adc ah,0
;сегмент кода
;сегмент кода
;если нет переноса, то перейти на ml ;в ах сумма с учетом переноса
;если нет переноса, то перейти на ml ;в ах сумма с учетом переноса
17> ml:
18>exit:
19>mov ax ,4c00h стандартныйвыход
20>int 21h
21>end main;конец программы
17> ml:
18>exit:
19>mov ax ,4c00h стандартныйвыход
20>int 21h
21>end main;конец программы
В строках 13-14 создана ситуация, когда результат сложения выходит за границы операнда. Эта возможность учитывается строкой 15, где команда JNC (хотя можно было обойтись и без нее) проверяет состояние флага CF. Если он установлен в 1, то результат операции получился большим по размеру, чем операнд, и для его корректировки необходимо выполнить некоторые действия. В данном случае мы просто полагаем, что границы операнда расширяются до размера АХ, для чего
учитываем перенос в старший разряд командойADC (строка 16). Напомню, что исследовать работу команд сложения без учета знака вы можете в отладчике. Для этого введите в текстовом редакторе текст листинга 8.3, получите исполняемый модуль, запустите отладчик и откройте в нем окна командами View ► Dump и View ► Registers. Далее, в пошаговом режиме отладки можно более наглядно проследить за всеми процессами, происходящими в процессоре во время работы программы.
Сложение двоичных чисел со знаком
Теперь настала пора раскрыть небольшой секрет. Дело в том, что на самом деле процессор не подозревает о различии между числами со знаком и числами без знака. Вместо этого у него есть средства фиксации возникновения характерных ситуаций, складывающихся в процессе вычислений. Некоторые из них мы рассмотрели ранее при обсуждении команд сложения чисел без знака — это учет флага переноса CF. Установка этого флага в 1 говорит о том, что произошел выход за пределы разрядности операндов. Далее с помощью командыADC можно учесть возможность такого выхода (переноса из младшего разряда) во время работы программы. Другое средство фиксации характерных ситуаций в процессе арифметических вычислений — регистрация состояния старшего (знакового) разряда операнда, которое осуществляется с помощью флага переполнения OF в регистре EFLAGS (бит И).
В главе 4 мы рассматривали, как представляются числа в компьютере. При этом отмечали, что положительные числа представляются в двоичном коде, а отрицательные — в дополнительном. Рассмотрим различные варианты сложения чисел. Примеры призваны показать поведение двух старших битов операндов и правильность результата операции сложения.
Первый вариант сложения чисел:
30566 = 01110111 01100110 +
00687 = 00000010 10101111
31253= 0111101000010101.
Следим за переносами из 14-го и 15-горазрядов и за правильностью результата: переносов нет, результат правильный.
Второй вариант сложения чисел:
30566 = 01110111 01100110 +
30566 =0111011101100110
61132 = 11101110 11001100.
Произошел перенос из 14-го разряда; из 15-го разряда переноса нет. Результат неправильный, так как имеется переполнение — значение числа получилось больше, чем то, которое может иметь 16-разрядное число со знаком (+32 767).
Третий вариант сложения чисел:
-30566 = 10001000 10011010 +
-04875 = 11101100 11110101
-35441 =01110101 10001111.
Произошел перенос из 15-го разряда, из 14-го разряда нет переноса. Результат неправильный, так как вместо отрицательного числа получилось положительное (в старшем бите находится 0).
Четвертый вариант сложения чисел:
-4875 = 11101100 11110101
+
-4875= 1110110011110101.
-9750 = 11011001 11101010.
Есть переносы из 14-го и 15-горазрядов. Результат правильный.
Таким образом, мы исследовали все случаи и выяснили, что ситуация переполнения (установка флага OF в 1) происходит при переносе:
* из 14-го разряда (для положительных чисел со знаком);
в из 15-го разряда (для отрицательных чисел).
И, наоборот, переполнения не происходит (то есть флаг OF сбрасывается в 0), если есть перенос из обоих разрядов или перенос отсутствует в обоих разрядах.
Таким образом, ситуация переполнение регистрируется процессором с помощью флага переполнения OF. Дополнительно к флагу OF при переносе из старшего разряда устанавливается в 1 и флаг переноса CF. Так как процессор не знает о существовании чисел со знаком и без знака, то вся ответственность за правильность действий с получившимися числами ложится на программиста. Теперь, наверное, понятно, почему мы столько внимания уделили тонкостям сложения чисел со знаком. Учтя все это, мы сможем организовать правильный процесс сложения чисел — будем анализировать флаги CF и OF и принимать правильное решение! Проанализировать флаги CF и OF можно командами условного перехода JC\JNC и JO\JNO соответственно (глава 10).
Что же касается команд сложения чисел со знаком, то из изложенного ранее понятно, что в архитектуре IA-32 сами команды сложения чисел со знаком те же, что и для чисел без знака.
Вычитание двоичных чисел без знака
Аналогично анализу операции сложения, порассуждаем над сутью процессов, происходящих при выполнении вычитания.
緩 Если уменьшаемое больше вычитаемого, то проблем нет, — разность положительна, результат верен.
纖 Если уменьшаемое меньше вычитаемого, возникает проблема: результат меньше 0, а это уже число со знаком. В этом случае результат необходимо завернуть. Что это означает? При обычном вычитании (в столбик) делают заем 1 из старшего разряда. Процессор поступает аналогично, то есть занимает 1 из разряда, следующего за старшим в разрядной сетке операнда. Поясним на примере.
Первый вариант вычитания чисел:
05 = 0000000000000101
-10 = 0000000000001010.
Для того чтобы произвести вычитание, произведем воображаемый заем из старшего разряда:
100000000 00000101
0000000000001010
11111111 11111011.
Тем самым, по сути, выполняется действие (65 536 + 5) - 10 = 65 531, 0 здесь как бы эквивалентен числу 65 536. Результат, конечно, неверен, но процессор считает, что все нормально, тем не менее, факт заема единицы он фиксирует, устанавливая флаг переноса CF. Посмотрите еще раз внимательно на результат операции вычитания. Это же число -5 в дополнительном коде! Проведем эксперимент: представим разность в виде суммы 5 + (-10).
Второй вариант вычитания чисел:
5=00000000 00000101
+
(-10) -11111111 11110110
11111111 11111011.
То есть мы получили тот же результат, что и в предыдущем примере. Таким образом, после команды вычитания чисел без знака нужно анализировать состояние флага CF. Если он установлен в 1, это говорит о том, что произошел заем из старшего разряда и результат получился в дополнительном коде.
Аналогично командам сложения группа команд вычитания состоит из минимально возможного набора. Эти команды выполняют вычитание по алгоритмам, которые мы сейчас рассматриваем, а учет особых ситуаций должен производиться самим программистом.
綫 Команда декремента выполняетменыПения значения операнда на 1:
dec операнд
綫 Команда вычитания (операнд_1 =- операнд__2):
sub операнд_1,операнд_2
8 Команда вычитания с учетом заема, то есть флага CF (операнд_1 =-
операнд_2 - значение_СЕ):
sbb операнд_1,операнд_2
Как видите, среди команд вычитания есть команда SBB, учитывающая флаг переноса CF. Этакоманда подобнаADC, нотеперьуже флагCFиграетрольиндикатора заема 1 из старшего разряда при вычитании чисел.
Рассмотрим пример (листинг 8.4) программной обработки ситуации, рассмотренной ранее для второго варианта вычитания чисел.
Листинг 8.4. Проверка при вычитании чисел без знака
<1><2><3><4><5><6><7><8><1><2><3><4><5><6><7><8>;p「g_8_4.asm;p「g_8_4.asmmasm model stack .data .codemain:masm model stack .data .codemain:small 256small 256;сегмент кода;точка входа в программу;сегмент кода;точка входа в программу<9><10><11><12><13><14><15><16><17><18><9><10><11><12><13><14><15><16><17><18>xorxorml:ml:endendax, axmov al ,5 sub al,10 jnc ml neg alax, axmov al ,5 sub al,10 jnc ml neg al;нет переноса?;в al модуль результата;нет переноса?;в al модуль результатаexit:mov ax , 4c00h;стандартный выходexit:mov ax , 4c00h;стандартный выходint 21h mainint 21h main;конец программы;конец программы
<1>
<2>
<3>
<4>
<5>
<6>
<7>
<8>
<1>
<2>
<3>
<4>
<5>
<6>
<7>
<8>
;p「g_8_4.asm
;p「g_8_4.asm
masm model stack .data .code
main:
masm model stack .data .code
main:
small 256
small 256
;сегмент кода
;точка входа в программу
;сегмент кода
;точка входа в программу
<9>
<10>
<11>
<12>
<13>
<14>
<15>
<16>
<17>
<18>
<9>
<10>
<11>
<12>
<13>
<14>
<15>
<16>
<17>
<18>
xor
xor
ml:
ml:
end
end
ax, ax
mov al ,5 sub al,10 jnc ml neg al
ax, ax
mov al ,5 sub al,10 jnc ml neg al
;нет переноса?
;в al модуль результата
;нет переноса?
;в al модуль результата
exit:
mov ax , 4c00h;стандартный выход
exit:
mov ax , 4c00h;стандартный выход
int 21h main
int 21h main
;конец программы
;конец программы
В этом примере в строке 1 1 выполняется вычитание. С указанными для этой
команды вычитания исходными данными результат получается в дополнительном коде (отрицательный). Для того чтобы преобразовать результат к нормальному виду (получить его модуль), применяется команда NEG, с помощью которой получается дополнение операнда. В нашем случае мы получили дополнение дополнения, или модуль отрицательного результата. А тот факт, что это на самом деле число отрицательное, отражен в состоянии флага CF. Дальше все зависит от алгоритма обработки. Исследуйте программу в отладчике.
Вычитание двоичных чисел со знаком
Вычитание двоичных чисел со знаком выполнять несколько сложнее. Последний пример показал то, что процессору незачем иметь два устройства — сложения и вычитания. Достаточно наличия только одного — устройства сложения. Но для вычитания способом сложения чисел со знаком оба операнда (и уменьшаемое, и вычитаемое) необходимо представлять в дополнительном коде. Результат тоже нужно рассматривать как значение в дополнительном коде. Но здесь возникают сложности. Прежде всего, они связаны с тем, что старший бит операнда рассматривается как знаковый. Рассмотрим пример вычитания 45 - (-127).
Первый вариант вычитания чисел со знаком:
45=00101101
127 = 10000001
44 =1010 1100.
Судя по знаковому разряду, результат получился отрицательный, что, в свою очередь, говорит о том, что число нужно рассматривать как дополнение, равное -44. Правильный результат должен быть равен 172. Здесь мы, как и в случае знакового сложения, встретились с переполнением мантиссы, когда значащий разряд числа изменил знаковый разряд операнда. Отследить такую ситуацию можно по содержимому флага переполнения OF. Его установка в 1 говорит о том, что резуль-
тат вышел за диапазон представления знаковых чисел (то есть изменился старший бит) для операнда данного размера и программист должен предусмотреть действия по корректировке результата.
Следующее вычитание чисел со знаком выполним способом сложения:
45 - 45 - -45 + (-45)Н -90.
415 = 1101 ООН
+
45 =11010011
-90 = 10100110.
Здесь все нормально, флаг переполнения OFсброшен в 0, а 1 в знаковом разряде говорит о том, что значение результата — число в дополнительном коде.
Вычитание и сложение операндов большой размерности
Если вы заметили, команды сложения и вычитания работают с операндами фиксированной размерности: 8, 16, 32 бита. А что делать, если нужно сложить числа большей размерности, например 48 битов, используя 16-разрядные операнды? К примеру, сложим два 48-разрядных числа (рис. 8.5).
1-еслагаемое001000111001010101001011 111110001111111100110001+2-еслагаемое0100010010001011101001010010010001001001100001101-й шаг: сложение младших 16бит - >1 01001000101101112-й шаг: сложение средних 16 бит- (с учетом переноса из младшего разряда):1-еслагаемое001000111001010101001011 111110001111111100110001+2-еслагаемое0100010010001011101001010010010001001001100001101-й шаг: сложение младших 16бит - >1 01001000101101112-й шаг: сложение средних 16 бит- (с учетом переноса из младшего разряда):01001011 1111100001001011 11111000Перенос в старшийразрядПеренос в старшийразряд10100101001001001010010100100100
1-еслагаемое001000111001010101001011 111110001111111100110001
+
2-еслагаемое010001001000101110100101001001000100100110000110
1-й шаг: сложение младших 16бит - >1 0100100010110111
2-й шаг: сложение средних 16 бит- (с учетом переноса из младшего разряда):
1-еслагаемое001000111001010101001011 111110001111111100110001
+
2-еслагаемое010001001000101110100101001001000100100110000110
1-й шаг: сложение младших 16бит - >1 0100100010110111
2-й шаг: сложение средних 16 бит- (с учетом переноса из младшего разряда):
01001011 11111000
01001011 11111000
Перенос в старшийразряд
Перенос в старшийразряд
1010010100100100
1010010100100100
11110001 00011100
11110001 0001110111110001 000111010000000000000001
11110001 00011101
11110001 00011101
3-й шаг:сложение старших 16бит (переноса из младшего разряда нет):
0010001110010101
0100010010001011
Результат сложения: 011010000010000011110001 000111010100100010110111
Рис. 8.5. Сложение операндов большой размерности
Рисунок по шагам иллюстрирует технологию сложения длинных чисел. Видно, что процесс сложения многобайтных чисел происходит так же, как и при сложении двух чисел «в столбик», — с осуществлением при необходимости переноса 1 в старший разряд. Если нам удастся запрограммировать этот процесс, то мы значительно расширим диапазон двоичных чисел, над которыми можно выполнять операции сложения и вычитания.
Принцип вычитания чисел с диапазоном представления, превышающим стандартные разрядные сетки операндов, тот же, что и при сложении, то есть использу-
ется флаг переноса CF. Нужно только представлять себе процесс вычитания в столбик и правильно комбинировать команды процессора с командой SBВ. Чтобы написать достаточно интересную программу, моделирующую этот процесс, необходимо привлечь те конструкции языка ассемблера, которые мы еще не обсуждали. Среди прилагаемых к книге файлов в каталоге данной главы находятся исходные тексты подпрограмм, реализующих четыре основных арифметических действия для двоичных операндов произвольной размерности. Не поленитесь внимательно изучить их, так как они являются хорошей иллюстрацией к материалу, изучаемому в этой и последующих главах. К этим примерам можно будет обратиться в - полной мере после того, как будут изучены механизмы процедур и макрокоманд (главы 14 и 15). Кроме того, в [8] обсуждаются вопросы программирования целочисленных арифметических операций, но в расширенном контексте.
В завершение обсуждения команд сложения и вычитания отметим, что кроме флагов CF и OF в регистре EFLAGS есть еще несколько флагов, которые можно использовать с двоичными арифметическими командами. Речь идет о следующих флагах:
;幾 ZF — флаг нуля, который устанавливается в 1, если результат операции равен О, и в 0, если результат не равен 0;
緻 SF — флаг знака, значение которого после арифметических операций (и не только) совпадает со значением старшего бита результата, то есть с битом7,15или 31 (таким образом, этот флаг можно использовать для операций над числами со знаком).
Умножение двоичных чисел без знака
Для умножения чисел без знака предназначена команда
mul сомножитель_1
Как видите, в команде указан всего лишь один операнд-сомножитель. Второй операнд-сомножитель задан неявно. Его местоположение фиксировано и зависит от размера сомножителей. Так как в общем случае результат умножения больше, чем любой из его сомножителей, то его размер и местоположение должны быть тоже определены однозначно. Варианты размеров сомножителей и мест размещения второго операнда и результата приведены в табл. 8.2.
Таблица 8.2. Расположение операндов и результата при умножении
Первый сомножитель
Второй сомножитель
Результат
Байт
AL
16битов в АХ: AL — младшая часть результата;
АН — старшая часть результата|
Слово
АХ
32 бита в паре DX:AX: AX — младшая часть результата; DX — старшая часть результата
Двойное слово
ЕАХ
64 бита в паре EDX:EAX: ЕАХ — младшая часть результата; EDX — старшая часть результата
Из таблицы видно, что произведение состоит из двух частей и в зависимости от размера операндов размещается в двух местах — на месте второго сомножителя (младшая часть) и в дополнительных регистрах АН, DX, EDX (старшая часть). Как же динамически (то есть во время выполнения программы) узнать, что результат достаточно мал и уместился в одном регистре или что он превысил размерность регистра и старшая часть оказалась в другом регистре? Для этого привлекаются уже известные нам флаги переноса CF и переполнения OF:
в если старшая часть результата нулевая, то после завершения операции CF = О и OF = 0;
隱 если же флаги CF и OF ненулевые, это означает, что результат вышел за пределы младшей части произведения и состоит из двух частей, что и нужно учитывать при дальнейшей работе.
Рассмотрим следующий пример программы (листинг 8.5).
Листинг 8.5. Умножение
<1><2> r<3> i<4><5><6><7><8><9><10><11><1><2> r<3> i<4><5><6><7><8><9><10><11>;P「g_8_5.asm;P「g_8_5.asmmasm model stack .datamasm model stack .datasmall 256small 256rez label rez_ldbrez_hdb.codemain:rez label rez_ldbrez_hdb.codemain:word 45 0word 45 0;сегмент данных;сегмент данных;сегмент кода;точка входа в программу;сегмент кода;точка входа в программу<12><13><14><15><16><17><18><19><20><21><22><12><13><14><15><16><17><18><19><20><21><22>хог mov mul jncхог mov mul jncax, ax al,25 rez_lmlax, ax al,25 rez_lml;если нет переполнения, то на Ml;если нет переполнения, то на Mlmov rez_h,ah :старшую часть результата в rez_h ml:mov rez_l,alexit:mov ax, 4c00h:стандартный выходint 21hmov rez_h,ah :старшую часть результата в rez_h ml:mov rez_l,alexit:mov ax, 4c00h:стандартный выходint 21hend mainend main;конец программы;конец программы
<1>
<2> r
<3> i
<4>
<5>
<6>
<7>
<8>
<9>
<10>
<11>
<1>
<2> r
<3> i
<4>
<5>
<6>
<7>
<8>
<9>
<10>
<11>
;P「g_8_5.asm
;P「g_8_5.asm
masm model stack .data
masm model stack .data
small 256
small 256
rez label rez_ldb
rez_hdb
.code
main:
rez label rez_ldb
rez_hdb
.code
main:
word 45 0
word 45 0
;сегмент данных
;сегмент данных
;сегмент кода
;точка входа в программу
;сегмент кода
;точка входа в программу
<12>
<13>
<14>
<15>
<16>
<17>
<18>
<19>
<20>
<21>
<22>
<12>
<13>
<14>
<15>
<16>
<17>
<18>
<19>
<20>
<21>
<22>
хог mov mul jnc
хог mov mul jnc
ax, ax al,25 rez_l
ml
ax, ax al,25 rez_l
ml
;если нет переполнения, то на Ml
;если нет переполнения, то на Ml
mov rez_h,ah :старшую часть результата в rez_h ml:
mov rez_l,al
exit:
mov ax, 4c00h:стандартный выход
int 21h
mov rez_h,ah :старшую часть результата в rez_h ml:
mov rez_l,al
exit:
mov ax, 4c00h:стандартный выход
int 21h
end main
end main
;конец программы
;конец программы
В строке 14 производится умножение значения в rez_l на число в регистре AL. Согласно информации из табл. 8.2, результат умножения будет располагаться в регистре AL (младшая часть) и в регистре АН (старшая часть). Для выяснения размера результата в строке 15 командой условного перехода JNC анализируется состояние флага CF, и если оно не равно 1, то результат остается в рамках регистра AL. Если же CF = 1, то выполняется команда в строке 16, которая формирует в поле rez_h старшее слово результата. Команда в строке 18 формирует младшую часть результата. Теперь обратите внимание на сегмент данных, а именно на строку 6. В этой строке содержится директива label. Мы еще не раз будем сталкиваться с этой директивой. В данном случае она назначает еще одно символическое имя rez адресу, на который уже указывает другой идентификатор rez_l. Различие заключается в типах этих идентификаторов — rez имеет тип слова, который ему назначается директивой label (имя типа указано в качестве операнда label). Введя эту директиву в программе, мы подготовились к тому, что, возможно, результат операции
умножения будет занимать в памяти целое слово. Обратите внимание на то, что мы не нарушили принципа: младший байт по младшему адресу. Далее, используя имя rez, можно обращаться к значению в этой области как к слову.
В заключение можно исследовать в отладчике программу на разных наборах сомножителей.
Умножениедвоичных чисел со знаком
Для умножения чисел со знаком предназначена команда imul операндах[,операнд^,операнд_3]
Эта команда выполняется так же, как и команда MUL Отличительной особенностью команды IMUL является только формирование знака. Если результат мал и умещается в одном регистре (то есть если CF= 0F = 0), то содержимое другого регистра (старшей части) является расширением знака — все его биты равны старшему биту (знаковому разряду) младшей части результата. В противном случае (если CF = OF = 1) знаком результата является знаковый бит старшей части результата, а знаковый бит младшей части является значащим битом двоичного кода результата. Если вы найдете в приложении команду IMUL, то увидите, что у нее имеются более широкие возможности по заданию местоположения операндов. Это сделано для удобства использования.
Деление двоичных чисел без знака
Для деления чисел без знака предназначена команда div делитель
Делитель может находиться в памяти или в регистре и иметь размер 8, 16 или 32 бита. Местонахождение делимого фиксировано и так же, как в команде умножения, зависит от размера операндов. Результатом команды деления являются значения частного и остатка. Варианты местоположения и размеров операндов операции деления показаны в табл. 8.3.
Таблица 8.3. Расположение операндов и результата при делении
Делимое
Делитель
Частное
Остаток
Слово (16 бит) в регистре АХ
Байт в регистре или в ячейке памяти
Байт в регистре AL
Байт в регистре АН
Двойное слово (32 бита), в DX — старшая часть, в АХ — младшая часть
Слово (16 бит) в регистре или в ячейке памяти
Слово (16 бит) в регистре АХ
Слово (16 бит) в регистре DX
Учетверенное слово (64 бит), в EDX — старшая часть, в ЕАХ — младшая часть
Двойное слово (32 бита) в регистре или в ячейке памяти
Двойное слово (32 бита) в регистре ЕАХ
Двойное слово (32 бита) в регистре EDX
После выполнения команды деления содержимое флагов неопределенно, но возможно возникновение прерывания с номером 0, называемого «деление на ноль». Этот вид прерывания относится к так называемым исключениям и возникает внутри процессора из-за некоторых аномалий в вычислительном процессе. К вопросу
об исключениях мы еще вернемся. Прерывание 0 (деление на ноль) при выполнении команды DIV может возникнуть по одной из следующих причин:
經 делитель равен нулю;
* частное не входит в отведенную под него разрядную сетку, что может случиться:
при делении делимого величиной в слово на делитель величиной в байт, причем значение делимого более чем в 256 раз больше значения делителя;
П при делении делимого величиной в двойное слово на делитель величиной в слово, причем значение делимого более чем в 65 536 раз больше значения делителя;
П при делении делимого величиной в учетверенное слово на делитель величиной в двойное слово, причем значение делимого более чем в 4 294 967 296 раз больше значения делителя.
К примеру, выполним деление значения в области del на значение в области delt (листинг 8.6).
;сегмент кода;точка входа в программу;сегмент кода;точка входа в программуmain:main:<12><13><14><15><16><17><18><12><13><14><15><16><17><18>хог ax, axхог ax, ax;последующие две mov ah,del_b;последующие две mov ah,del_bкоманды можно заменить одной mov ax,delкоманды можно заменить одной mov ax,delmov al,del_b+l div deltmov al,del_b+l div deltend mainend mainстарший байт делимого в ah младший байт делимого в a Iстарший байт делимого в ah младший байт делимого в a Iв alв al--частное, в ah - остатокчастное, в ah - остатокконец программыконец программыЛистинг8.6. Деление чисел
;сегмент кода
;точка входа в программу
;сегмент кода
;точка входа в программу
main:
main:
<12>
<13>
<14>
<15>
<16>
<17>
<18>
<12>
<13>
<14>
<15>
<16>
<17>
<18>
хог ax, ax
хог ax, ax
;последующие две mov ah,del_b
;последующие две mov ah,del_b
команды можно заменить одной mov ax,del
команды можно заменить одной mov ax,del
mov al,del_b+l div delt
mov al,del_b+l div delt
end main
end main
старший байт делимого в ah младший байт делимого в a I
старший байт делимого в ah младший байт делимого в a I
в al
в al
-
-
частное, в ah - остаток
частное, в ah - остаток
конец программы
конец программы
<1> ;prg_8.6.asm
<2> masm~
<3>
model
small
<4>
stack
256
<5>
.data
<6>
del_b
label
byte
<7>
del dw
29876
<8>
deltdb
45
<9>
<10>
.code
Выполнение программы в таком виде приведет к ошибке деления на ноль. Причина описана ранее — частное не входит в отведенную под него разрядную сетку. Это происходит в случаях, когда делимое больше делителя на определенную величину. А что же делать, если соотношение делимое и делителя именно такое? Подробно деление, как, впрочем, и умножение, целых чисел произвольной разрядности описано в книге [8]. Чтобы исправить пример из листинга 8.6, необходимо изменить разрядность делимого, исходя из разрядности делителя и требований KO- манды DIV. К примеру, делимое можно сделать равным 298. Пример будет выполнен без ошибок.
Деление двоичных чисел со знаком
Для деления чисел со знаком предназначена команда idiv делитель
Для этой команды справедливы все рассмотренные ранее рассуждения, касающиеся команд и чисел со знаком. Отметим лишь особенности возникновения ис-
ключения 0 (деление на ноль) в случае чисел со знаком. Оно возникает при выполнении команды IDIV по одной из следующих причин:
緻 делитель равен нулю;
爾 частное не входит в отведенную для него разрядную сетку, что, в свою очередь, может произойти:
при делении делимого величиной в слово со знаком на делитель величиной в байт со знаком, причем значение делимого более чем в 128раз больше значения делителя (таким образом, частное не должно находиться вне диапазо- наот -128до +127);
при делении делимого величиной в двойное слово со знаком на делитель величиной в слово со знаком, причем значение делимого более чем в 32 768 раз больше значения делителя (таким образом, частное не должно находиться вне диапазона от -32 768 до +32 768);
П при делении делимого величиной в учетверенное слово со знаком на делитель величиной в двойное слово со знаком, причем значение делимого более чем в 2 147 483 648 раз больше значения делителя (таким образом, частное не должно находиться вне диапазона от -2 147 483 648 до +2 147 483 647).
Вспомогательные команды для арифметических вычислений
В системе команд процессора есть несколько команд, которые облегчают программирование алгоритмов, производящих арифметические вычисления, а также помогают разрешать проблемы, возникающие при подобных вычислениях.
Команды преобразования типов
Что делать, если размеры операндов, участвующих в арифметических операциях, разные? Например, предположим, что в операции сложения один операнд занимает слово, а другой — двойное слово. Ранее было сказано, что в операции сложения должны участвовать операнды одного формата. Если числа без знака, то выход найти просто. В этом случае можно на базе исходного операнда сформировать новый операнд (формата двойного слова), старшие разряды которого просто заполнить нулями. Сложнее ситуация для чисел со знаком: как динамически, в ходе выполнения программы, учесть знак операнда? Для решения подобных проблем в системе команд процессора есть так называемые команды преобразования типа. Эти команды расширяют байты в слова, слова — в двойные слова и двойные слова —в учетверенные слова (64-разрядные значения). Команды преобразования типа особенно полезны при преобразовании целых со знаком, так как они автоматически заполняют старшие биты вновь формируемого операнда значениями знакового бита исходного объекта. Эта операция приводит к целым значениям того же знака и той же величины, что и исходная, но уже в более длинном формате. Подобное преобразование называется операцией распространения знака.
Существуют два вида команд преобразования типа.
II Команды без операндов — эти команды работают с фиксированными регистрами:
П CBW (Convert Byte to Word) — команда преобразования байта (в регистре AL) в слово (в регистре АХ) путем распространения значения старшего бита AL на все биты регистра АН;
П CWD (Convert Word to Double) — команда преобразования слова (в регистре АХ) в двойное слово (в регистрах DX:AX) путем распространения значения старшего бита ах на все биты регистра DX;
О CWDE (Convert Word to Double) — команда преобразования слова (в регистре АХ) в двойное слово (в регистре ЕАХ) путем распространения значения старшего бита АХ на все биты старшей половины регистра ЕАХ;
CDQ (Convert Double Word to Quarter Word) — команда преобразования двойного слова (в регистре ЕАХ) в учетверенное слово (в регистрах EDX:EAX) путем распространения значения старшего бита ЕАХ на все биты регистра EDX;
it Команды обработки строк (также глава 12). Эти команды обладают полезным свойством в контексте нашей проблемы:
П movsx операнд_1,операнд_2 — команда пересылки с распространением знака расширяет 8- или 16-разрядное значение операнд_2, которое может быть регистром или операндом в памяти, до 16- или 32-разрядного значения в одном из регистров, используя знаковый бит для заполнения старших позиций значения операнд_1 (данную команду удобно использовать для подготовки операндов со знаками к выполнению арифметических операций);
П movzx операнд_1,операнд_2 — команда пересылки с расширением нулем расширяет 8- или 16-разрядное значение операнд_2 до 16- или 32-разрядного с очисткой (заполнением) нулями старших позиций значения операнд_2 (данную команду удобно использовать для подготовки операндов без знака к выполнению арифметических действий).
К примеру, вычислим значение у (а + Ь)/с, где а, Ь, с — байтовые знаковые переменные (листинг 8.7).
Листинг 8.7. Вычисление простого выражения
1> ;prg_8_7.asm
2> masm
3> modelsmall
4> stack256
5> .data
б> adb5
7> bdb10
8> сdb2
9> уdw0
;точка входа в программу;точка входа в программу1G>.code
;точка входа в программу
;точка входа в программу
11>main:
<12>
13>хогax, ax
14>moval,a
15>cbw
.386
16>movsx bx,b
17>addax,bx
18>idivc;в al - частное, в ah - остаток
19>exit:
20>mov ax ,4c00h; стандартный выход
<21>1 nt 21h
22>end main:конец программы
В этой программе делимое для команды IDIV (строка 18) готовится заранее. Так как делитель имеет размер байта, то делимое должно быть словом. С учетом этого сложение осуществляется параллельно с преобразованием размера результата в слово (строки 14-17). Например, расширение операндов со знаком производится двумя разными командами — CBW и MOVSX.
Другие полезные команды
В системе команд процессора есть две команды — XADD и NEG, которые могут быть полезны, в частности, для программирования вычислительных действий:
ж xadd приемник,источник — обмен местами и сложение. Команда позволяет выполнить последовательно два действия:
обменять значения приемник и источник;
поместить на место операнда приемник сумму: приемник = приемник + источник.
В neg операнд — отрицание с дополнением до двух. Команда выполняет инвертирование значения операнд. Физически команда выполняет одно действие: операнд = 0 - операнд, то есть вычитает операнд из нуля. Команду NEG можно применять для смены знака и вычитания из константы. Дело в том, что команды SUB и SBB не позволяют вычесть что-либо из константы, так как константа не может служить операндом-приемником в этих операциях. Поэтому данную операцию можно выполнить с помощью двух команд:
neg ax ;смена знака (ах)
add ах,340 фактически вычитание: (ах)=340-(ах)
Арифметические операции
над двоично-десятичными числами
Определение и формат BCD-чисел были рассмотрены в начале этой главы. У вас справедливо может возникнуть вопрос: а зачем нужны BCD-числа? Ответ может быть следующим: BCD-числа нужны в коммерческих приложениях, то есть там, где числа должны быть большими и точными. Как мы уже убедились, выполнение операций с двоичными числами для языка ассемблера довольно проблематично: в Значения величин в формате слова и двойного слова имеют ограниченный диапазон. Если программа предназначена для работы в области финансов, то ограничение суммы в рублях величиной 65 536 (для слова) или даже 4 294 967 296 (для двойного слова) существенно сужает сферу ее применения (да еще в наших экономических условиях — тут уж никакая деноминация не поможет).
* Двоичные числа дают ошибки округления. Представляете себе программу, работающую где-нибудь в банке, которая не учитывает величину остатка при дей-
ствиях с целыми двоичными числами и оперирует при этом миллиардами. Не хотелось бы быть автором такой программы. Применение чисел с плавающей точкой не спасет — там существует та же проблема округления.
я Большой объем результатов в коммерческих программах требуется представлять в символьном виде (ASCII-коде). Коммерческие программы не просто выполняют вычисления; одной из целей их применения является оперативная выдача информации пользователю. Для этого, естественно, информация должна быть представлена в символьном виде. Перевод чисел из двоичного кода в ASCII-код, как мы уже видели, требует определенных вычислительных затрат. Число с плавающей точкой еще труднее перевести в символьный вид. А вот если посмотреть на шестнадцатеричное представление неупакованной десятичной цифры (в начале данной главы) и на соответствующий ей символ в таблице ASCII, то видно, что они различаются на величину 30h. Таким образом, преобразование в символьный вид и обратно получается намного проще и быстрее. Эти доводы убеждают в важности овладения хотя бы основами действий с десятичными числами. Далее рассмотрим особенности выполнения основных арифметических операций с такими числами. Для предупреждения возможных вопросов сразу отметим тот факт, что отдельных команд сложения, вычитания, умножения и деления BCD-чисел нет. Сделано это по вполне понятным причинам — размерность таких чисел может быть сколь угодно большой. Складывать и вычитать можно двоично-десятичные числа как в упакованном формате, так и в неупакованном, а вот делить и умножать можно только неупакованные BCD-числа. Почему, будет видно из дальнейшего обсуждения.
Неупакованные BCD-числа
Сложение
Рассмотрим два случая сложения.
Результат сложения не больше 9:
6 =0000 0110 +
= 0000 ООН
9 = 0000 1001.
Переноса из младшей тетрады в старшую нет. Результат правильный.
Результат сложения больше 9:
06 =0000 0110 +
07 = 00000111
13 =0000 1101.
То есть мы получили уже не BCD-число. Результат неправильный. Правильный результат в неупакованном BCD-формате в двоичном представлении должен быть таким: 0000 0001 0000 ООН (или 13 в десятичном). Проанализировав данную проблему при сложении BCD-чисел (и подобные проблемы при выполнении дру-
гих арифметических действий), а также возможные пути ее решения, разработчики системы команд процессора решили не вводить специальные команды для работы с BCD-числами, а ввести несколько корректировочных команд. Назначение этих команд — корректировка результата работы обычных арифметических команд для случаев, когда операнды в них являются BCD-числами. В случае сложения во втором примере видно, что полученный результат нужно корректировать. Для коррекции операции сложения двух однозначных неупакованных BCD-чисел и представления результата сложения в символьном виде в системе команд процессора существует специальная команда ААА (ASCII Adjust for Addition).
Команда ААА не имеет операндов.работает неявно только с регистром AL
и анализирует значение его младшей тетрады. Если это значение меньше 9, то флаг CF сбрасывается в 0 и осуществляется переход к следующей команде. Если это значение больше 9, то выполняются следующие действия.
К содержимому младшей тетрады AL (но не к содержимому всего регистра!) прибавляется 6, тем самым значение десятичного результата корректируется в правильную сторону.
Флаг CF устанавливается в 1, тем самым фиксируется перенос в старший разряд для того, чтобы его можно было учесть в последующих действиях.
Так, во втором примере сложения, предполагая, что значение суммы 0000 находится в AL, после выполнения команды ААА в регистре будет 1101 + 0110 = = то есть двоичное значение 0000или десятичное значение 3, а флаг CF
установится в 1, то есть перенос запоминается в процессоре. Далее программисту нужно использовать команду сложения DC, которая учтет перенос из предыдущего разряда. Приведем пример программы сложения двух неупакованных BCD-чисел (листинг 8.8).
<1><2><3> <4> <5><6><7> <8> <9><ie> <и> <12> <13><14><15> <16> <17> <18> <19> <20><21><1><2><3> <4> <5><6><7> <8> <9><ie> <и> <12> <13><14><15> <16> <17> <18> <19> <20><21>;prg_8_8.asm;prg_8_8.asmЛистингСложение неупакованных BCD-чисел
<1>
<2>
<3> <4> <5>
<6>
<7> <8> <9>
<ie> <и> <12> <13>
<14>
<15> <16> <17> <18> <19> <20>
<21>
<1>
<2>
<3> <4> <5>
<6>
<7> <8> <9>
<ie> <и> <12> <13>
<14>
<15> <16> <17> <18> <19> <20>
<21>
;prg_8_8.asm
;prg_8_8.asm
числа; неупакованное число 71 число 54(0);точка входа в программучисла; неупакованное число 71 число 54(0);точка входа в программу.data
числа
; неупакованное число 71 число 54
(0)
;точка входа в программу
числа
; неупакованное число 71 число 54
(0)
;точка входа в программу
len 2
b db 1,7
с db 4,5
sum db 3 dup
.code
main:
xor
mov
ml:
movb[bx]
adc
aaa
exit:exit:mov inc bx loop ml adc
exit:
exit:
В листинге 8.8 есть несколько интересных моментов, над которыми стоит поразмыслить. Начнем с описания BCD-чисел. Из строк 5 и 6 видно, что порядок их
ввода обратен нормальному, то есть цифры младших разрядов расположены по меньшему адресу. Это вполне логично по нескольким причинам: во-первых, такой порядок удовлетворяет общему принципу представления данных для процессоров Intel, во-вторых, это очень удобно для поразрядной обработки неупакованных BCD-чисел, так как каждое из них занимает один байт. Хотя, повторюсь, программист сам волен выбирать способ описания BCD-чисел в сегменте данных. Строки 14 и 15 содержат команды, которые складывают цифры в очередных разрядах BCD- чисел, при этом учитывается возможный перенос из младшего разряда. Команда ААА в строке 16корректирует результат сложения, формируя в AL BCD-цифру и при необходимости устанавливая в 1 флаг CF. Строка 20 учитывает возможность переноса при сложении цифр из самых старших разрядов чисел. Результат сложения формируется в поле sum, описанном в строке 7.
Вычитание
Ситуация при вычитании вполне аналогична сложению. Рассмотрим те же случаи.
Результат вычитания не больше 9:
6 =0000 0110
3 = 00000011
3 = 00000011.
Как видим, заема из старшей тетрады нет. Результат верный и корректировки не требует.
Результат вычитания больше 9:
6 =00000110
7 =00000111
-1-11111111.
Вычитание проводится по правилам двоичной арифметики. Поэтому результат не является BCD-числом. Правильный результат в неупакованном BCD-формате должен быть 9 (0000 1001 в двоичной системе счисления). При этом предполагается заем из старшего разряда, как при обычной команде вычитания, то есть в случае с BCD-числами фактически должно быть выполнено вычитание 16 - 7. Таким образом, как и в случае сложения, результат вычитания нужно корректировать. Для этого существует специальная команда AAS (ASCII Adjust for Substraction), выполняющая коррекцию результата вычитания для представления в символьном виде.
Команда AAS также не имеет операндов и работает с регистром AL, анализируя его младшую тетраду следующим образом: если ее значение меньше 9, то флаг CF сбрасывается в 0 и управление передается следующей команде. Если значение тетрады в AL больше 9, то команда AAS выполняет следующие действия.
Из содержимого младшей тетрады регистра AL (заметьте, не из содержимого всего регистра) вычитается 6.
Старшая тетрада регистра AL обнуляется.
Флаг CF устанавливается в 1, тем самым фиксируется воображаемый заем из старшего разряда.
Понятно, что команда AAS применяется вместе с основными командами вычитания SUB и SBB. При этом команду SUB есть смысл использовать только один раз при вычитании самых младших цифр операндов, далее должна применяться команда SBВ, которая будет учитывать возможный заем из старшего разряда. В листинге 8.9 мы обходимся одной командой SBВ, которая в цикле производит поразрядное вычитание двух BCD-чисел.
Листинг 8.9. Вычитание неупакованных BCD-чисел
<1> ;P「g_8_9.asm
<2> masm
<3> model
small
<4> stack
256
<5> .data
;сегмент данных
<6> bdb
1,7
;неупакованное число 71
<7> сdb
4,5
;неупакованное число 54
<8> subs db
2 dup (0)
<9> .code
<1в> main:
;точка входа в программу
<11>
mov
ax,@data
;связываем регистр dx с сегментом
<12>
mov
ds, ax
:данных через регистр ах
<13>
xor
ax, ax
;очищаем ах
<14>len
equ
2
;разрядность чисел
<15>
xor
bx, bx
<16>
mov
ex,len
;загрузка в сх счетчика цикла
<17> ml:
<18>
mov
al,b[bx]
<19>
sbb
al,c[bx]
<20>
aas
<21>
mov
subs[bx] ,al
<22>
inc
bx
<23>
loop
ml
<24>
jc
m2
;анализ флага заема
<25>
jmp
exit
<26> m2:
;...
<27> exit
<28>
mov
ax,4c00h
стандартный выход
<29>
int
21h
<30> end
main
;конец программы
Данная программа не требует особых пояснений в случае, когда уменьшаемое больше вычитаемого. Поэтому обратите внимание на строку 24. С ее помощью мы предусматриваем случай, когда после вычитания старших цифр чисел был зафиксирован факт заема. Это говорит о том, что вычитаемое было больше уменьшаемого, в результате чего разность оказалась неправильной. Эту ситуацию нужно как- то обработать. С этой целью в строке 24 командой JC анализируется флаг CF. По результату этого анализа мы уходим на ветку программы, обозначенную меткой т2, где и выполняются нужные действия. Набор этих действий зависит от конкретного алгоритма обработки, поэтому поясним только их суть. Для этого посмотрите в отладчике, как наша программа выполняет вычитание 50 - 74 (правильный ответ 24). То, что вы увидите в окне Dump отладчика (в поле, соответствующем адресу subs), окажется далеким от истины. Что делает в этом случае человек? Он просто мысленно меняет местами вычитаемое и уменьшаемое (74 - 50 = 24), а ре-
зультат рассматривает со знаком «минус». Так и фрагмент программы, обозначенный меткой т2, может, поменяв уменьшаемое и вычитаемое местами и выполнив вычитание, где-то отметить тот факт, что разность, на самом деле, нужно рассматривать как отрицательное число.
Умножение
На примере сложения и вычитания неупакованных чисел мы выяснили, что стандартных алгоритмов для выполнения этих действий над BCD-числами нет и программист должен сам, исходя из требований к своей программе, реализовать эти операции. Реализация двух оставшихся операций — умножения и деления — еще сложнее. В системе команд процессора присутствуют только средства для умножения и деления одноразрядных неупакованных BCD-чисел. Для их умножения необходимо воспроизвести описанную далее процедуру.
Поместить один из сомножителей в регистр AL (как того требует команда MUL).
Поместить второй сомножитель в регистр или память, отведя для него байт.
Перемножить сомножители командой MUL (результат, как и положено, окажется в регистре АХ).
Скорректировать результат, который, конечно, будет представлен в двоичном коде.
Для коррекции результата после умножения в целях представления его в символьном виде применяется специальная команда AAM (ASCII Adjust for Multiplication). Она не имеет операндов и работает с регистром АХ следующим образом.
Делит AL на 10.
Результат деления записывается так: частное — в AL, остаток — в АН.
В результате после выполнения команды AAM в регистрах AL и АН находятся правильные двоично-десятичные цифры произведения двух цифр.
В листинге 8.10 приведен пример умножения BCD-числа произвольной размерности на однозначное BCD-число.
Листинг 8.10. Умножение неупакованных BCD-чисел
<1> masm
<2> model
small
<3> stack
256
<4> .data
<5> bdb
6,7
;неупакованное число 76
<6> сdb
4
;неупакованное число 4
<7> proizv
db 4 dup (0)
<8> .code
<9> main:
;точка входа в программу
<1G>
mov ax,@data
mov ds.ax
xor ax, ax
<13> len
equ 2
размерность сомножителя 1
<14>
xor bx.bx
<15>
xor si ,si
<16>
xo r di .di
<17>
movex,len
;в сх длина наибольшего сомножителя 1
<18>ml:
<19>
moval,b[si]
<20>
с
<21>
аат
;коррекция умножения
<22>
adc
al ,dl
;учли предыдущий перенос
<23>
ааа
;скорректировали результат сложения с переносом
C4>
mov
dl ,ah
;запомнили перенос
<25>
mov
proizv[bx]
,al
<26>
i nc
si
<27>
inc
bx
<28>
loop
ml
;цикл на метку ml
<29>
mov
proizv[bx]
,dT;учли последний перенос
<30>
exit
<31>
mov
ax,4c00h
<32>
int
21h
<33>
end main
Данную программу можно легко модифицировать для умножения BCD-чисел произвольной длины. Для этого достаточно представить алгоритм умножения в «столбик». Листинг 8.10 можно использовать для получения частичных произведений в этом алгоритме. После их сложения со сдвигом получится искомый результат. Попробуйте написать эту программу самостоятельно. Если же данная задача окажется для вас непосильной, ее решение вы можете найти в [8].
Перед окончанием обсуждения команды AAM необходимо отметить, что ее можно применять для преобразования двоичного числа в регистре AL в неупакованное BCD-число, которое окажется в регистре АХ: старшая цифра результата — в АН, младшая — в AL. Понятно, что двоичное число должно быть в диапазоне 0...99.
Деление
Процесс деления двух неупакованных BCD-чисел несколько отличается от других рассмотренных ранее операций с ними. Здесь также требуются действия по коррекции, но они должны выполняться до основной операции, выполняющей непосредственно деление одного BCD-числа на другое BCD-число. Предварительно в регистре АХ нужно получить две неупакованные BCD-цифры делимого. Это делает программист удобным для него способом. Далее для коррекции результата деления в целях представления его в символьном виде нужно выполнить команду AAD (ASCII Adjust for Division).
КомандаAAD не имеет операндов и преобразуетдвузначное неупакованное BCD- число в регистре АХ в двоичное число, которое впоследствии будет играть роль делимого в операции деления. Кроме преобразования, командаAAD помещает полученное двоичное число в регистр AL. Делимое, естественно, является двоичным числом из диапазона 0...99. Алгоритм, по которому командаAAD осуществляет это преобразование, выглядит следующим образом.
Умножить на 10 старшую цифру исходного BCD-числа в АХ (содержимое АН).
Выполнить сложение АН + AL, результат которого (двоичное число) занести в A L.
Обнулить содержимое AH.
Далее программисту нужно выполнить обычную команду деления DIV для деления содержимого АХ на одну BCD-цифру, находящуюся в байтовом регистре или байтовой ячейке памяти. Деление неупакованных BCD-чисел иллюстрирует листинг 8.11.
Листинг8.1 1 ・Деление неупакованных BCD-чисел
<1> ;prg_8_ll.asm
<2>
3> .data
4> bdb 1,7
5> сdb 4
б> .code
7> main:
;сегмент данных
;неупакованное BCD-число 71
;сегмент кода
;точка входа в программу
<8> ...
<9>mov al,b
<10>aad
<11>div с
;коррекция перед делением
;в al BCD — частное, в ah BCD - остаток
<13>exit:
Аналогично AAM, командеAAD можно найти и другое применение — использовать ее для перевода неупакованных BCD-чисел из диапазона 0...99 в их двоичный эквивалент.
Для деления чисел большей разрядности так же, как и в случае умножения, нужно реализовывать, например, алгоритм деления в «столбик» или найти более оптимальный путь. Любопытный и настойчивый читатель, возможно, самостоятельно разработает эти программы. Но это делать совсем необязательно. Среди файлов, прилагаемых к книге, в каталоге данной главы приведены тексты макрокоманд, которые выполняют четыре основных арифметических действия с BCD- числами любой разрядности. Кроме этого в [8] вы найдете дополнительный материал по этому вопросу.
Упакованные BCD-числа
Как уже отмечалось ранее, упакованные BCD-числа можно только складывать и вычитать. Для выполнения других действий над ними их нужно дополнительно преобразовывать либо в неупакованный формат, либо в двоичное представление. Таким образом, сами по себе упакованные BCD-числа представляют не слишком большой интерес для программиста, поэтому мы их рассмотрим кратко.
Сложение
Вначале разберемся с сутью проблемы и попытаемся сложить два двузначных упакованных BCD-числа:
67 =01100111
+
75 =01110101
142= 1101 1100= 220
Как видим, в двоичном виде результат равен 1101 1100 (или 220 в десятичном представлении), что неверно. Это происходит по той причине, что процессор не подозревает о существовании BCD-чисел и складывает их по правилам сложения двоичных чисел. На самом деле результат в двоично-десятичном виде должен быть равен 0001 0100 0010 (или 142 в десятичном представлении). Этот пример иллюстрирует необходимость корректировки результатов арифметических операций с упакованными BCD-числами, так же как это было в случае неупакованных BCD-
чисел. Для корректировки результата сложения упакованных чисел в целях представления его в десятичном виде процессор предоставляет команду DAA (Decimal Adjust for Addition).
Команда DAA преобразует содержимое регистра AL в две упакованные десятичные цифры (по алгоритму, приведенному в приложении А, где данная команда описана более подробно). Получившаяся в результате сложения единица (если результат сложения больше 99) запоминается во флаге CF, тем самым учитывается перенос в старший разряд.
Проиллюстрируем сказанное на примере сложения двух двузначных BCD-чисел в упакованном формате (листинг 8.12).
Листинг Сложение упакованных BCD-чисел
<1>
;prg 8 12.asm
2> . . .
3>данных
4> b17h;упакованное число 17
5> сdb 45h;упакованное число 45
6> sum db 2 dup (0)
7>кода
8> main:;точка входа в программу
9>
10>,b
11>add
12>daa
13>jnc $+6;переход через команду, если результат <= 99
<14>
mov;учет переноса при сложении (результат > 99)
<15>
<16>
movупакованные цифры результата
exit:
В приведенном примере все достаточно прозрачно;на что следу
ет обратить внимание, — это описание упакованных BCD-чисел и порядок формирования результата. Результат формируется в соответствии с основным принципом работы процессоров Intel: младший байт по младшему адресу.
Вычитание
Аналогично сложению, при вычитании процессор рассматривает упакованные BCD-числа как двоичные. Выполним вычитание 67 - 75. Так как процессор выполняет вычитание способом сложения, то и мы последуем этому:
67 =01100111
+
-75
-8= 28.
Как видим, результат равен 28 в десятичной системе счисления, что является абсурдом. В двоично-десятичном коде результат должен быть равен 0000 (или 8 в десятичной системе счисления). При программировании вычитания упакованных BCD-чисел программист, как и при вычитании неупакованных BCD-чисел, должен сам осуществлять контроль за знаком. Это делается с помощью флага CF, который фиксирует заем из старших разрядов. Само вычитание BCD-чисел осуществляется обычной командой вычитания SUB или SBB. Коррекция результата
вычитания для его представления в десятичном виде осуществляется командой DAS (Decimal Adjust for Substraction).
В приложении описан алгоритм, по которому команда DAS преобразует содержимое регистра AL в две упакованные десятичные цифры.
Итоги
趣 Процессор имеет довольно мощные средства для реализации вычислительных операций. Для этого у него есть блок целочисленных операций и блок операций с плавающей точкой. Для большинства задач, использующих язык ассемблера, достаточно целочисленной арифметики.
趣 Команды целочисленных операций работают с данными двух типов: двоичными и двоично-десятичными числами (BCD-числами).
趣 Двоичные данные могут либо иметь знак, либо не иметь такового. Процессор, на самом деле, не различает числа со знаком и без. Он лишь помогаетотслежи- вать изменение состояния некоторых битов операндов и состояние отдельных флагов. Операции сложения и вычитания чисел со знаком и без знака проводятся одним устройством и по единым правилам.
Ш Контроль за правильностью результатов и их надлежащей интерпретацией полностью лежит на программисте. Он должен контролировать состояние флагов CF и OF регистра EFLAGS во время вычислительного процесса.
綫 Для операций с числами без знака нужно контролировать флаг CF. Установка его в 1 сигнализирует о том, что число вышло за разрядную сетку операндов.
ж Для чисел со знаком установка флага OF в 1 говорит о том, что в результате сложения чисел одного знака результат выходит за границу допустимых значений чисел со знаком в данном формате, и сам результат меняет знак (пропадает порядок).
綴 По результатам выполнения арифметических операций устанавливаются также флаги PF, ZF и SF.
• В отличие от команд сложения и вычитания, команды умножения и деления позволяют учитывать знаки операндов.
蕊Арифметические команды очень «капризны» к размерности операндов, поэтому в систему команд процессора включены специальные команды, позволяющие отслеживать эту характеристику.
* Хотя диапазон значений двоичных данных довольно велик, для коммерческих приложений его явно недостаточно, поэтому в архитектуру процессора введены средства для работы с так называемыми двоично-десятичными (BCD) данными.
縫 Двоично-десятичные данные представляются в двух форматах, упакованном и неупакованном. Наиболее универсальным является неупакованный формат.
Глава 9
Логические команды и команды сдвига
> Краткое описание группы логических команд
> Команды для выполнения логических операций
Команды сдвига
Организация работы с отдельными битами
Наряду со средствами арифметических вычислений система команд процессора имеет также средства логического преобразования данных. Под логическим понимается такое преобразование данных, в основе которого лежат правила формальной логики. Формальная логика работает на уровне утверждений истинно или ложно. Для процессора это, как правило, означает 1 или 0 соответственно. Для компьютера язык нулей и единиц является родным, но минимальной единицей данных, с которой работают машинные команды, является байт. Однако на системном уровне часто необходимо иметь возможность работать на предельно низком уровне — на уровне битов.
К средствам логического преобразования данных в языке ассемблера относятся логические команды и логические операции (рис. 9.1). Команды рассматриваются в этой главе, операции были изучены нами в главе 5. Напомню, что операнд команды ассемблера в общем случае может представлять собой выражение, которое, в свою очередь, является комбинацией операторов и операндов. Среди этих операторов могут быть и операторы, реализующие логические операции над объектами выражения.
Перед знакомством с этими средствами давайте посмотрим, что же представляют собой сами логические данные и какие действия над ними могут производиться.
7 Зак. 256
Средствалогическойобработки данных
Операции IОперации I[[IIIIIIсдвига Iсдвига Iобычногосдвигаобычногосдвига-sar-sal—shl-shr—shld—shrd-sar-sal—shl-shr—shld—shrdциклического сдвига—rcl—rcr—rolI— rorциклического сдвига—rcl—rcr—rolI— rorлогическиелогические-and-or-xor-not-and-or-xor-notсдвигасдвигасравнения-eq-ne-It* Ie-gtLgeсравнения-eq-ne-It* Ie-gtLge
Операции I
Операции I
[
[
I
I
I
I
I
I
сдвига I
сдвига I
обычногосдвига
обычногосдвига
-sar
-sal
—shl
-shr
—shld
—shrd
-sar
-sal
—shl
-shr
—shld
—shrd
циклического сдвига
—rcl
—rcr
—rol
I— ror
циклического сдвига
—rcl
—rcr
—rol
I— ror
логические
логические
-and
-or
-xor
-not
-and
-or
-xor
-not
сдвига
сдвига
сравнения
-eq
-ne
-It
* Ie
-gt
Lge
сравнения
-eq
-ne
-It
* Ie
-gt
Lge
Рис.Средства процессора для работы с логическими данными
Логические данные
Теоретической базой для логической обработки данных является формальная логика. Существует несколько систем логики. Одна из наиболее известных — это исчисление высказываний. Высказывание — это любое утверждение, о котором можно что оно либо истинно, либо ложно. Исчисление высказываний представляет собой совокупность правил определения истинности или ложности некоторой комбинации высказываний.
Исчисление высказываний очень гармонично сочетается с принципами работы компьютера и основными методами его программирования. Все аппаратные компоненты компьютера построены на логических микросхемах. Система представления информации в компьютере на самом нижнем уровне основана на понятии бита. Бит, имея всего два состояния — 0 (ложно) и 1 (истинно), естественным образом вписывается в систему исчисления высказываний.
Согласно теории, над высказываниями (над битами) могут выполняться логические операции.
Отрицание (логическое НЕ) — логическая операция над одним операндом, результатом которой является величина, обратная значению исходного операнда. Эта операция однозначно характеризуется следующей таблицей истинно- emu1'.
Таблица истинности — таблица результатов логических операций в зависимости от значений исходных операндов.
Значение операнда:О1
Результат операции:1О
綴 Логическое сложение (логическое включающее ИЛИ) — логическая операция над двумя операндами, результатом которой является истина (1), если один или оба операнда истинны (1), или ложь (0), если оба операнда ложны (0). Эта операция описывается с помощью следующей таблицы истинности:
Значение операнда 1:Значение операнда 2: Результат операции:Значение операнда 1:Значение операнда 2: Результат операции:0011О 1 О 101110011О 1 О 10111
Значение операнда 1:
Значение операнда 2: Результат операции:
Значение операнда 1:
Значение операнда 2: Результат операции:
0011
О 1 О 1
0111
0011
О 1 О 1
0111
• Логическое умножение (логическое И) — логическая операция над двумя операндами, результатом которой является истина (1) только в том случае, если оба операнда истинны (1). Во всех остальных случаях значение операции — ложь (0). Эта операция описывается с помощью следующей таблицы истинности:
Значение операнда 1 :Значение операнда 2:Результат операции:Значение операнда 1 :Значение операнда 2:Результат операции:0011О1О10001.0011О1О10001.
Значение операнда 1 :
Значение операнда 2:
Результат операции:
Значение операнда 1 :
Значение операнда 2:
Результат операции:
0011
О1О1
0001.
0011
О1О1
0001.
isisЛогическое исключающее сложениеЛогическое исключающее сложение(логическое исключающее ИЛИ) — логиче-
is
is
Логическое исключающее сложение
Логическое исключающее сложение
ская операция над двумя операндами, результатом которой является истина (1), если только один из двух операндов истинен (1), и ложь (0), если оба операнда либо ложны (0), либо истинны (1). Эта операция описывается с помощью следующей таблицы истинности:
Значение операндаЗначение операнда 2:Результат операции:Значение операндаЗначение операнда 2:Результат операции:1:0011О 1 О 101101:0011О 1 О 10110
Значение операнда
Значение операнда 2:
Результат операции:
Значение операнда
Значение операнда 2:
Результат операции:
1:0011
О 1 О 1
0110
1:0011
О 1 О 1
0110
Логические команды
Система команд процессора содержит пять команд, поддерживающих описанные ранее операции. Эти команды выполняют логические операции над битами операндов. Размерность операндов, естественно, должна быть одинакова. Например, если размерность операндов равна слову (16 битов), то логическая операция выполняется сначала над нулевыми битами операндов, и ее результат записывается на место бита 0 результата. Далее команда последовательно повторяет эти действия над всеми битами с первого до пятнадцатого. Возможные варианты размерности операндов для каждой команды можно найти в приложении.
Далее перечислены базовые команды процессора, поддерживающие работу с логическими данными:
Я and операнд_1,операнд_2 — операция логического умножения. Команда выполняет поразрядно логическую операцию И (конъюнкцию) над битами операндов операнд_1 и операнд_2. Результат записывается на место операнд_1.
綴 or операнд_1,операнд_2 — операция логического сложения. Команда выполняет поразрядно логическую операцию ИЛИ (дизъюнкцию) над битами операндов операнд„1 и операнд_2. Результат записывается на место операнд.,1.
盛 хогоперанд_1,операнд_2 — операция логического исключающего сложения. Команда выполняет поразрядно логическую операцию исключающего ИЛИ над битами операндов операнд_1 и операнд_2. Результат записывается на место опе- ранд_1.
и test операнд_1,операнд„2 — операция проверки (способом логического умножения). Команда выполняет поразрядно логическую операцию И над битами операндов операнд_1 и операнд_2. Состояние операндов остается прежним, изменяются только флаги ZF, SF, и PF, что дает возможность анализировать состояние отдельных битов операнда без изменения их состояния в исходных операндах.
糸not операнд — операция логического отрицания. Команда выполняет поразрядное инвертирование (замену значения на обратное) каждого бита операнда. Результат записывается на место операнда.
Для представления роли логических команд в системе команд процессора очень важно понять области их применения и типовые приемы их использования при программировании. Далее мы будем рассматривать логические команды в контексте обработки последовательности битов.
Очень часто некоторая ячейка памяти должна играть роль индикатора, показывая, например, занятость некоторого программного или аппаратного ресурса. Так как эта ячейка может принимать только два значения — занято (1) или свободно (0), то отводить под нее целый байт очень расточительно, логичнее для этой цели использовать бит. А если таких индикаторов много? Объединив их в пределах одного байта или слова, можно получить довольно существенную экономию памяти. Посмотрим, что могут сделать для этого логические команды.
С помощью логических команд возможно выделение отдельных битов в операнде с целью их установки, сброса, инвертирования или просто проверки на определенное значение. Для организации подобной работы с битами второй операнд обычно играет роль маски. Путем установки в 1 битов этой маски и определяются нужные для конкретной операции биты первого операнда. Покажем, какие логические команды могут применяться для этой цели.
Для установки определенных разрядов (битов) в 1 применяется команда or операнд_1,операнд_2
В этой команде второй операнд, играющий роль маски, должен содержать единичные биты на месте тех разрядов, которые должны быть установлены в 1 в первом операнде:
or еах,10b ;установить 1-й бит в регистре еах
Для сброса определенных разрядов (битов) в 0 применяется команда
and операнд_1,операнд_2
В этой команде второй операнд, играющий роль маски, должен содержать нулевые биты на месте тех разрядов, которые должны быть установлены в 0 в первом операнде:
and еах,0fffffffdh ;сбросить в 0 1-й бит в регистре еах
Для выяснения того, какие биты в обоих операндах различаются, или для инвертирования заданных битов в первом операнде применяется команда хог операнд_1,операнд_2
Интересующие нас биты маски (второго операнда) при выполнении команды XOR должны быть единичными, остальные — нулевыми:
хог eax,10b ;инвертировать 1-й бит в регистре еах
jz mes ;переход, если 1-й бит в al был единичным
Для проверки состояния заданных битов в первом операнде применяется команда
test операнд_1,операнд_2
Проверяемые биты первого операнда в маске (втором операнде) должны иметь единичное значение. Алгоритм работы команды TEST подобен алгоритму работы команды AND, но он не меняет значения первого операнда. Результатом команды является установка значения флага нуля ZF:
綫 если ZF = 0, то в результате логического умножения получился ненулевой результат, то есть хотя бы один единичный бит маски совпал с соответствующим единичным битом первого операнда;
蕊 если ZF 1, то в результате логического умножения получился нулевой результат, то есть ни один единичный бит маски не совпал с соответствующим единичным битом первого операнда.
Таким образом, если любые соответствующие биты в обоих операндах установлены, то ZF =3 0. Для реакции на результат команды TEST целесообразно использовать команду перехода на метку JNZ (Jump if Not Zero) — переход, если флаг нуля ZF ненулевой, или команду с обратным действием JZ (Jump if Zero) — переход, если флаг нуля ZF нулевой. Например,
testeax,00000010h
jnz ml;переход если 4-й бит равен 1
Начиная с системы команд процессора i386, набор команд для поразрядной обработки данных расширился. При использовании этих команд необходимо указывать одну из директив: .386, .486 и т. д. Следующие две команды позволяют осуществить поиск первого установленного в 1 бита операнда. Поиск можно произвести как с начала, так и от конца операнда:
• bsfоперанд_1,операнд_2 — сканирование битов вперед (Bit Scaning Forward). Команда просматривает (сканирует) биты второго операнда от младшего к старшему (от бита 0 до старшего бита) в поисках первого бита, установленного в 1. Если таковой обнаруживается, в первый операнд заносится номер этого бита в виде целочисленного значения. Если все биты второго операнда равны 0, то флаг нуля ZF устанавливается в 1, в противном случае флаг ZF сбрасывается в 0.
mov al,@2h
bsf bx, al ;bx=l
j z ml ;переход, если al=00h
綫 bsr операнд_1,операнд_2 — сканирование битов в обратном порядке (Bit Scaning Reset). Команда просматривает (сканирует) биты второго операнда от старшего к младшему (от старшего бита к биту 0) в поисках первого бита, установленного в 1. Если таковой обнаруживается, в первый операнд заносится номер этого бита в виде целочисленного значения. При этом важно, что позиция первого единичного бита слева все равно отсчитывается относительно бита 0. Если все биты второго операнда равны 0, то флаг нуля ZF устанавливается в 1, в противном случае флаг ZF сбрасывается в 0.
Листинг9,1 демонстрирует пример применения команд BSR и BSF. Введите код и исследуйте работу программы в отладчике (в частности, обратите внимание на то, как меняется содержимое регистра ВХ после выполнения команд BSF и BSR).
Листинг9.1,Сканирование битов
;prg_9_l•asm'
masm modelsmall
stack 256
.data; сегмент данных
.code;сегмент кода
main:;точка входа в программу
mov ax,@data mov ds(ax
.486;это обязательно
xor ax, ax
mov al,02h
bsf bx.ax ;bx=l
jz ml;переход, если al=00h
bsr bx.ax
ml:
mov ax ,4c00h стандартный выход
int 21h end main
Интерес представляют еще несколько из группы логических команд, позволяющих реализовать доступ к конкретному биту операнда. Они, как и предыдущие, появились в моделях процессоров Intel, начиная с i386. Поэтому при их использовании не забывайте указывать одну из директив: .386, .486 и т. д. Операнд может находиться как в памяти, так и в регистре общего назначения. Положение бита задается смещением его относительно младшего бита операнда. Смещение может как задаваться в виде непосредственного значения, так и содержаться в регистре общего назначения. Вкачествезначения смещения вы можете использовать результаты работы команд BSR и BSF. Все команды присваивают значение выбранного бита флагу CF.
Команда проверки бита ВТ (Bit Test) переносит значение бита в флаг CF: bt операнд,смещение_бита
Например,,
bt ах, 5;проверить значение бита 5
jnc ml;переход, если бит = 0
Команда проверки и установки бита BTS (Bit Test and Set) переносит значение бита в флаг CF и затем устанавливает проверяемый бит в 1: bts операнд,смещение_бита
Например,
mov ах, 10
bts pole,ах ;проверить и установить 10-й бит в pole
jc ml ;переход, если проверяемый бит был равен 1
Команда проверки и сброса бита BTR (Bit Test and Reset) переносит значение бита во флаг CF и затем устанавливает этот бит в 0: btr операнд,смещение_бита
Команда проверки и инвертирования бита ВТС (Bit Test and Convert) переносит значение бита в флаг CF и затем инвертирует значение этого бита: btc операнд,смещение_бита
Командысдвига
Команды сдвига также обеспечивают манипуляции над отдельными битами операндов, но иным способом, чем рассмотренные ранее логические команды. Все команды сдвига перемещают биты в поле операнда влево или вправо, в зависимости от кода операции. Все команды сдвига имеют одинаковую структуру: коп операнд,счетчик_сдвигов
Количество сдвигаемых разрядов (значение счетчик_сдвигов) может задаваться двумя способами:
II статически — непосредственно во втором операнде;
發динамически — в регистре CL перед выполнением команды сдвига.
Исходя из размерности регистра CL, понятно, что значение счетчик_сдвигов может лежать в диапазоне от 0 до 255. Но на самом деле это не совсем так. В целях оптимизации процессор воспринимает только значения пяти младших битов счетчика, то есть значение лежит в диапазоне от 0 до 31. В последних моделях процессора есть дополнительные команды, позволяющие делать 64-разрядные сдвиги. Мы их рассмотрим чуть позже.
Все команды сдвига устанавливают флаг переноса CF. По мере сдвига битов за пределы операнда они сначала попадают во флаг переноса CF, устанавливая его равным значению очередного бита, оказавшегося за пределами операнда. Куда этот бит попадет дальше, зависит от типа команды сдвига и алгоритма программы.
По принципу действия команды сдвига можно разделить на два типа:
經 команды линейного сдвига;
к команды циклического сдвига.
Линейный сдвиг
К командам линейного сдвига относятся команды, осуществляющие сдвиг по следующему алгоритму.
Очередной «выдвигаемый» бит устанавливает флаг CF.
Бит, появляющийся с другого конца операнда, имеет значение 0.
При сдвиге очередного бита он переходит во флаг CF, при этом значение предыдущего сдвинутого бита теряется]
Команды линейного сдвига делятся на два подтипа:
ti команды логического линейного сдвига;
緻 команды арифметического линейного сдвига.
Далее перечислены команды логического линейного сдвига:
緩 shl операнд,счетчик_сдвигов — логический сдвиг влево (Shift Logical Left). Содержимое операнда сдвигается влево на количество битов, определяемое значением счетмик_сдви「ов. Справа в позицию младшего бита вписываются нули.
shr— логический сдвиг вправо (Shift Logical Right).
Содержимое операнда сдвигается вправо на количество битов, определяемое значениемСлева в позицию старшего (знакового) бита вписы
ваются нули.
9.2 иллюстрирует принцип работы этих команд.
Сдвиг влево логическийСдвиг влево логическийФлаг cfФлаг cfСдвиг вправо логический shrСдвиг вправо логический shrооФлаг cfРис. 9.2. Схема работы команд линейного логического сдвигаФлаг cfРис. 9.2. Схема работы команд линейного логического сдвига
Сдвиг влево логический
Сдвиг влево логический
Флаг cf
Флаг cf
Сдвиг вправо логический shr
Сдвиг вправо логический shr
о
о
Флаг cf
Рис. 9.2. Схема работы команд линейного логического сдвига
Флаг cf
Рис. 9.2. Схема работы команд линейного логического сдвига
Ниже показан фрагмент программы, выполняющей преобразование двух неупакованных BCD-чисел в слове памяти bcd_dig в упакованное BCD-число в ре- гистреAL.
0905h0905hmov ax,bcd_dig shl ah,4addmov ax,bcd_dig shl ah,4addописание неупакованного BCD-числа 95
0905h
0905h
mov ax,bcd_dig shl ah,4
add
mov ax,bcd_dig shl ah,4
add
пересылка
сдвиг влево
сложение для получения результата:
Команды арифметического линейного сдвига отличаются от команд логического сдвига тем, что они особым образом работают со знаковым разрядом операнда: ii— арифметическийвлево (Shift Arithmetic Left).
Содержимое операнда сдвигается влево на количество битов, определяемое значениемСправа (в позицию младшего бита) вписываются
нули. КомандаSAL не сохраняет знака, но устанавливаетOF в случае смены
знака очередным выдвигаемым битом. В остальном командаSAL полностью аналогична командеSHL;
sar операнд,счетчик_сдвигов — арифметический сдвиг вправо (Shift Arithmetic Right). Содержимое операнда сдвигается вправо на количество битов, опреде- ляемоезначениемсчетчик_сдвигов. Слевавоперандвписываютсянули. Команда SAR сохраняет знак, восстанавливая его после сдвига каждого очередного бита.
Рисунок 9.3 иллюстрирует принцип работы команд линейного арифметичес-
кого сдвига.
Сдвиг влево арифметический sal
JУVVУ2V VVU
76543210
Флаг cf
Флаг cf
Рис. 9.3. Схема работы команд линейного арифметического сдвига
Команды арифметического сдвига позволяют выполнить «быстрое» умножение и деление операнда на степени двойки. Посмотрите на двоичное представление чисел 75 и 150:
75 01001011
150 10010110
Второе число является сдвинутым влево на один разряд первым числом. Если у вас еще есть сомнения, проделайте несколько умножений на 2, 4, 8 и т. д.
Аналогичная ситуация — с операцией деления. Сдвигая вправо операнд, мы, фактически, осуществляем операцию деления на степени двойки 2, 4, 8 и т. д.
Преимущество этих команд по сравнению с командами умножения и деления — в скорости их исполнения процессором, что может пригодиться при оптимизации программы.
Циклический сдвиг
К командам циклического сдвига относятся команды, сохраняющие значения сдвигаемых битов. Есть два типа команд циклического сдвига:
' - команды простого циклического сдвига (рис. 9.4);
遂 команды циклического сдвига через флаг переноса CF (рис. 9.5).
Далее перечислены команды простого циклического сдвига:
喲 rot операнд.счетчик_сдвигов — циклический сдвиг влево (Rotate Left). Содержимое операнда сдвигается влево на количество битов, определяемое операндом счетчик„сдвигов. Сдвигаемые влево биты записываются в тот же операнд справа.
緻 гогоперанд^счетчик_сдвигов — циклический сдвиг вправо (Rotate Right). Содержимое операнда сдвигается вправо на количество битов, определяемое операндом счетчик_сдвигов. Сдвигаемые вправо биты записываются в тот же операнд слева.
Сдвиг влево циклический го!
V VV VУVУVУVУV
V V
V V
УVУV
УVУV
76543210
Флаг cf
Сдвиг вправо циклический ror
У H V工У工У工УV6543210У H V工У工У工УV6543210Флаг cfФлаг cfРис. 9.4. Схема работы команд простого циклического сдвигаРис. 9.4. Схема работы команд простого циклического сдвигаСдвиг влево циклический rdСдвиг влево циклический rd7654321 О7654321 О
У H V工У工У工УV
6543210
У H V工У工У工УV
6543210
Флаг cf
Флаг cf
Рис. 9.4. Схема работы команд простого циклического сдвига
Рис. 9.4. Схема работы команд простого циклического сдвига
Сдвиг влево циклический rd
Сдвиг влево циклический rd
7654321 О
7654321 О
Флаг cf
Сдвиг вправо циклический rcr
тvVVVv I V ^^1
76 б 4321 О
rf
Флаг cf
Рис. 9.5. Схема работы команд циклического сдвига через флаг переноса CF
Как видно из рис. 9.4, команды простого циклического сдвига в процессе своей работы осуществляют одно полезное действие: циклически сдвигаемый бит не только вдвигается в операнд с другого конца, но и одновременно его значение становится значением флага CF. К примеру, для того чтобы обменять содержимое двух половинок регистра ЕАХ, достаточно выполнить следующую последовательность команд:
mov eax,ffff0000h
mov cI,16 rol eax,cl
Команды циклического сдвига через флаг переноса CF отличаются от команд простого циклического сдвига тем, что сдвигаемый бит не сразу попадает в операнд с другого его конца, а сначала записывается во флаг переноса CF. Лишь следу-
ющее исполнение данной команды сдвига (при условии, что она выполняется в цикле) приводит к помещению выдвинутого ранее бита в другой конец операнда (рис. 9.5). Команды циклического сдвига через флаг переноса CF перечислены далее:
rcl операнд,счетчик_сдвигов — циклический сдвиг влево через перенос (Rotate through Carry Left). Содержимое операнда сдвигается влево на количество битов, определяемое операндом счетчик_сдвигов. Сдвигаемые биты поочередно становятся значением флага переноса CF;
齡 rcr операнд^счетчик_сдвигов — циклический сдвиг вправо через перенос (Rotate through Carry Right). Содержимое операнда сдвигается вправо на количество битов, определяемое операндом счетчик_сдвигов. Сдвигаемые биты поочередно становятся значением флага переноса CF.
Из рис. 9.5 видно, что при сдвиге через флаг переноса появляется промежуточный элемент, с помощью которого можно производить подмену циклически сдвигаемых битов, в частности, выполнять рассогласование битовых последовательностей. Под рассогласованием битовой последовательности здесь и далее подразумевается действие, которое позволяет некоторым образом локализовать и извлечь нужные участки этой последовательности и записать их в другое место. Например, рассмотрим, как переписать в регистр ВХ старшую половину регистра ЕАХ с одновременным ее обнулением в регистре ЕАХ:
movml:clc rcl rdmovml:clc rcl rdex ,16ex ,16eax, 1bx, 1eax, 1bx, 1loopml rol eax, 16loopml rol eax, 16;кол-во сдвигов для еах;кол-во сдвигов для еах;сброс флага cf в 0;сдвиг крайнего левого бита из еах в cf;перемещение бита из cf справа в Ьх;цикл 16 раз;восстановить правую часть еах;сброс флага cf в 0;сдвиг крайнего левого бита из еах в cf;перемещение бита из cf справа в Ьх;цикл 16 раз;восстановить правую часть еах
mov
ml:
clc rcl rd
mov
ml:
clc rcl rd
ex ,16
ex ,16
eax, 1
bx, 1
eax, 1
bx, 1
loopml rol eax, 16
loopml rol eax, 16
;кол-во сдвигов для еах
;кол-во сдвигов для еах
;сброс флага cf в 0
;сдвиг крайнего левого бита из еах в cf
;перемещение бита из cf справа в Ьх
;цикл 16 раз
;восстановить правую часть еах
;сброс флага cf в 0
;сдвиг крайнего левого бита из еах в cf
;перемещение бита из cf справа в Ьх
;цикл 16 раз
;восстановить правую часть еах
хог dx ,dx
;очистка dx для подсчета единичных
mov ex, 32
;число циклов подсчета ;метка цикла
гог eax,1
;циклический сдвиг вправо на 1 бит
jnc not_one
;переход, если очередной бит в cf ;не равен единице
inc dx
;увеличение счетчика цикла
cyclcyclnot_one:loopcyclnot_one:loopcyclКоманды простого циклического сдвига можно использовать для операций другого рода. К примеру, подсчитаем количество единичных битов в регистре ЕАХ: битов
cycl
cycl
not_one:
loopcycl
not_one:
loopcycl
;переход на метку cycl, если;значение в сх не равно 0
Этот фрагмент не требует особых пояснений, единственное, что нужно помнить, — особенности работы команды цикла ШОР (глава 10). Команда ШОР сравнивает значение регистра ЕСХ/СХ с нулем и, если оно не равно нулю, выполняет уменьшение ЕСХ/СХ на единицу и передает управление на метку в программе, указанную в этой команде в качестве операнда.
Дополнительныекомандысдвига
Система команд моделей микропроцессоров Intel, начиная с 80386, содержит дополнительные команды сдвига, расширяющие рассмотренные нами ранее возможности. Это — команды сдвига двойной точности:
緻 shld операнд_1лОперанд__2,счетчик_сдвигов — сдвиг влево двойной точности. Команда сдвигает биты первого операнда влево и заполняет его справа значения - ми битов, вытесняемых из второго операнда, согласно схеме на рис. 9.6. Количество сдвигаемых битов определяется значением счетчика сдвигов, которое может лежать в диапазоне 0...31. Это значение может задаваться непосредственно в третьем операнде или содержаться в регистре CL. Значение второго операнда не меняется;
shld операнд_1, операнд_2г рнет”ик сдви『0В|
Флаг cf 210210
операнд_1операнд_2 х
Рис. 9.6. Схема работы команды SHLD
毅 &Ьгс1операнд„1,операнд_2,счетчик_сдвигов — сдвиг вправо двойной точности. Команда сдвигает биты первого операнда вправо и заполняет его слева значениями битов, вытесняемых из второго операнда, согласно схеме на рис. 9.7. Количество сдвигаемых битов определяется значением счетчика сдвигов, которое может лежать в диапазоне 0...31. Это значение может задаваться непосредственно в третьем операнде или содержаться в регистре CL. Значение второго операнда не меняется.
210-2 1 О
операнд_2операнд_1Флаг cf
Рис. 9.7. Схема работы команды SHRD
Как мы отметили, команды SHLD и SHRD осуществляют сдвиги на величину до 32 разрядов, но за счет особенностей задания операндов и алгоритма работы эти команды можно использовать для работы с полями длиной до 64 битов. Например, рассмотрим, как можно осуществить сдвиг влево на 16 битов поля из 64 битов.
.data
pole」 dd 0b21187f5h
poljh dd 45ff6711h
.code
mov cl,16;загрузка счетчика сдвигов в cl
mov eax,pole_h
shldpole_l,eax,cl
shl pole二h,cl;pole_l=8 7f50000h;
pole_h=6711blllh一
Рассмотрим еще некоторые наиболее типичные примеры применения этих команд. Отметим следующий момент. Рассмотренные далее действия, конечно,.можно
Примеры работы с битовыми строками 205
выполнить и множеством других способов, но эти являются самыми быстрыми. Если ваши программы должны работать максимально быстро, то есть смысл потратить время на разбор этих примеров.
Примеры работы с битовыми строками
Рассогласование битовых строк
Наглядный пример рассогласования последовательностей битов — преобразование неупакованного BCD-числа в упакованное BCD-число. Один из вариантов такого преобразования был рассмотрен нами ранее при обсуждении команды линейного сдвига SHL Попробуем выполнить подобное преобразование с использованием команд сдвига двойной точности (листинг 9.2). В общем случае длина числа может быть произвольной, но при этом нужно учитывать ограничения, которые накладываются используемыми ресурсами процессора. Ограничения связаны в основном с тем, что центральное место в преобразовании занимает регистр ЕАХ, поэтому, если преобразуемое число имеет размер более четырех байтов, то его придется делить на части. Но это уже чисто алгоритмическая задача, поэтому в нашем случае предполагается, что неупакованное BCD-число имеет длину 4 байта.
Листинг9.2. Преобразование BCD-числа(вариант2)
;prg_9_2.a5m
masm
model
small
stack
256
.data
len=4
;длина неупакованного BCD-числа
unpck BCD label dword
dig_BCD
db 2,4,3,6
;неупакованное BCD-число 6342
pck_BCD
dd 0
;pck_BCD=O0006342
.code main:
;точка входа в программу
mov
ax,@data
mov
ds, ax
xor
ax, ax
mov
ex,len
.386
;3To обязательно
mov
eax,unpck_BCD
ml:
shl
eax ,4
;убираем нулевую тетраду
shld
pck_BCD,eax,4
;тетраду с цифрой заносим в поле pck_BCD
shl
eax:4
;убираем тетраду с цифрой из еах
loop
ml
;цикл
exit
;pck_BCD=00006342
mov
ax,4c00h
int
21h
end
main
Команды сдвига двойной точности SHLD и SHRD позволяют осуществлять с максимально возможной скоростью вставку битовой строки из регистра в произвольное место другой (большей) строки битов в памяти и извлечение в регистр битовой подстроки из некоторой строки битов в памяти. В результате этих операций смежные с подстрокой биты по ее обеим сторонам остаются неизменными.
Вставка битовых строк
Рассмотрим пример вставки битовой строки длиной 16 битов, находящейся в регистре ЕАХ, в строку памяти str, начиная с ее бита 8 (листинг 9.3). Вставляемая битовая строка выровнена к левому краю регистра ЕАХ.
Листинг 9.3. Вставка битовой строки
<1>
<2>
<3>
<4>
<5>
<6>
<7>
<8>
<9>
<10>
<11>
<12>
<13>
<14>
<15>
<16>
<17>
<18>
;prg_9_3.asm
raasm
model
stack
.data
bit_str ddдля вставки
P_strddподстрока
.code
main:;точка входа в программу
mov ax,@data
mov
xor
.386обязательно
mov
край места вставки циклически переместить к краю ;строки bit_str (сохранение правого
ror
shr;сдвинуть строку вправо
;на длину подстрокибитов)
<19>
<20>
<21>
<22>
<23>
<24>
<25>
;сдвинуть 16 бит
младшие 8 бит
exit:
mov
int
end main
Листинг 9.3 удобно исследовать в отладчике. При этом важно понять зависимость между непосредственными значениями, используемыми в командах строк 17-20, и исходными значениями. Общая методика вставки битовых строк выглядит следующим образом.
Подогнать к правому краю строки младший бит места вставки в этой строке.
Делать это нужно командой циклического сдвига, чтобы сохранить правую часть исходной строки. Величина сдвига определяется очень просто — это номер начальной позиции места вставки (строка 17).
Сдвинуть исходную строку вправо на количество битов, равное длине вставляемой подстроки (строкаЭти биты нам больше не нужны, поэтому для сдвига
используется команда простого сдвига
Командой SH LD вставить вставляемую подстроку в исходнуюПеред
этим, естественно, левый край вставляемой подстроки находится у левого края регистраЕАХ (строка 19).
Восстановить командой циклического сдвига правую часть исходной строки (строка 20).
Наибольшей эффективности при использовании этой программы можно достичь, если оформить представленную в ней последовательность команд в виде макрокоманды. Понятие макрокоманды будет рассматриваться нами в главе 14, но сейчас важно отметить, что в данном случае она позволит нам не задумываться
о настройке строк 17-20на конкретную вставку. При изучении материала главы 14 вы можете поэкспериментировать с данной программой, разработав на ее основе макрокоманду.
Извлечение битовых строк
Рассмотрим пример извлечения 16 битов из строки в памяти bit_5tr, начиная с бита 8, в регистр ЕАХ (листинг 9.4). Результат следует выровнять по правому краю регистра ЕАХ; строка bit_str не изменяется. Этот пример можно рассматривать как обратный тому, который мы только что привели в листинге 9.3. Методика извлечения битовой подстроки, если вы разобрались с программой вставки битовой строки, не должна вызвать у вас затруднений.
Листинг 9.4. Извлечение битовой строки
;prg_9_4.asm
masm
modelsmall
stack 256
.data
bit_str dd llffffllh ;строка для извлечения
.code
main:;точка входа в программу
mov ax,@data
mov ds,ax xor ax, ax .386;это обязательно
;левый край места извлечения циклически переместить к левому краю ;строки bit_str (сохранение левого контекста)
rol bit_str,8
mov ebx,bit_str;подготовленную строку в ebx
;в регистр еахror bit_str,8 : восстановить старшие 8 бит;в регистр еахror bit_str,8 : восстановить старшие 8 битshld eax,ebx,16 ;вставить извлекаемые 16 бит
;в регистр еах
ror bit_str,8 : восстановить старшие 8 бит
;в регистр еах
ror bit_str,8 : восстановить старшие 8 бит
;eax=0000ffff;eax=0000ffffexit:
;eax=0000ffff
;eax=0000ffff
mov ax,4c00h
int 21h
end main
Пересылка битов
По сути, программа пересылки битов является комбинацией двух предыдущих. Поэтому попробуйте самостоятельно разработать программу пересылки блока битов из одной битовой строки в другую, взяв за основу только что рассмотренные программы (см. листинги 9.3 и 9.4). К примеру, пусть имеется две битовые строки: bit_strldd Qabcdefabh
bit_str2dd 012345678h
Из этих строк получите строку
bi t_str2 dd 0abcd34abh
Итоги
Минимально адресуемая единица данных в процессоре — байт. Логические команды позволяют манипулировать отдельными битами. Только эти команды
« в системе команд процессора позволяют работать на битовом уровне. Этим, в частности, объясняется их важность.
* Работа на битовом уровне позволяет в отдельных случаях существенно сэкономить память, особенно при моделировании различных массивов, содержащих одноразрядные флаги или переключатели.
滋 Команды сдвига позволяют выполнять быстрое умножение и деление операндов на степени двойки, а также эффективное преобразование данных.
變 Применение команд циклического сдвига и сдвига двойной точности позволяет реализовать максимально быстрые операции по рассогласованию, перемещению, вставке и извлечению битовых подстрок.
Глава 10
Команды передачи управления
Программирование нелинейных алгоритмов
Классификация команд передачи управления
Команды безусловной передачи управления
Понятие процедуры в языке ассемблера
Команды условной передачи управления
Средства организации циклов в языке ассемблера
В предыдущей главе мы познакомились с некоторыми командами, из которых формируются линейные участки программы. Каждая из них в общем случае выполняет некоторые действия по преобразованию или пересылке данных, после чего процессор передает управление следующей команде. Однако последовательно работают очень мало программ. Обычно в программе есть точки, в которых нужно принять решение о том, какая команда будет выполняться следующей. Это решение может быть:
綫 безусловным — в данной точке необходимо передать управление не следующей команде, а другой, которая находится на некотором удалении от текущей;
ж условным — решение о том, какая команда будет выполняться следующей, принимается на основе анализа некоторых условий или данных.
Как вы помните, программа представляет собой последовательность команд и данных, занимающих определенное пространство оперативной памяти. Это пространство памяти может либо быть непрерывным, либо состоять из нескольких фрагментов. В главе 5 нами были рассмотрены средства сегментации кода программы
и ее данных. То, какая команда программы должна выполняться следующей, процессор узнает по содержимому пары регистров CS:(E)IP* в которой:
Si CS — регистр сегмента кода, в котором находится физический (базовый) адрес текущего сегмента кода;
ж EIP/IP — регистр указателя команды, в котором находится значение, представляющее собой смещение в памяти следующей выполняемой команды относительно начала текущего сегмента кода.
ПРИМЕЧАНИЕНапомню, почему мы записываем регистры EIP/IP через косую черту.
Какой конкретно регистр будет использоваться, зависит от установленных режимов адресации usel6 или use32. Если указано use 16, то используется IP, если use32 — то EIP.
Таким образом, команды передачи управления изменяют содержимое регистров CS и EIP, в результате чего процессор выбирает для выполнения не следующую по порядку команду программы, а команду в некотором другом участке программы. Конвейер внутри процессора при этом сбрасывается.
По принципу действия команды процессора, обеспечивающие организацию переходов в программе, можно разделить на три группы.
纓 Команды безусловной передачи управления:
безусловного перехода;
□ вызова процедуры и возврата из процедуры;
D вызова программных прерываний и возврата из программных прерываний. 緩 Команды условной передачи управления:
Р перехода по результату команды сравнения;
П перехода по состоянию определенного флага;
П перехода по содержимому регистра ЕСХ/СХ.
趣 Команды управления циклом:
П организации цикла со счетчиком ЕСХ/СХ;
D организации цикла со счетчиком ЕСХ/СХ с возможностью досрочного выхода из цикла по дополнительному условию.
Возникает вопрос о том, каким образом обозначается то место, куда необходимо передать управление. В языке ассемблера это делается с помощью меток. Метка — это символическое имя, обозначающее определенную ячейку памяти и предназначенное для использования в качестве операнда в командах передачи управления.
Подобно переменной, транслятор ассемблера присваивает любой метке три атрибута:
При обсуждении архитектуры микропроцессора мы говорили, что команды извлекаются из памяти заранее в так называемый конвейер, поэтому адрес подлежащей выборке команды из памяти и содержимое пары CS:E(IP) — не одно и то же. Эта пара регистров содержит адрес команды в программе, которая будет выполняться следующей, а не той команды, которая пойдет в конвейер.
弱 имя сегмента кода, где эта метка описана;
смещение — расстояние в байтах от начала сегмента кода, в котором описана метка;
顧 тип, или атрибут расстояния, метки.
Последний атрибут может принимать два значения:
踩 near — переход на метку возможен только в пределах сегмента кода, где эта метка описана, то есть для перехода на метку физически достаточно изменить только содержимое регистра EIP/IP;
邀 far — переход на метку возможен только в результате межсегментной передачи управления, для осуществления которой требуется изменение содержимого как регистра EIP/IP, так и регистра CS.
Метку можно определить двумя способами:
оператором : (двоеточие);
绥 директивой LABEL
Синтаксис первого способа показан на рис. 10.1.
Символическое ИМЯ^一C^|г一
Ц Команда ассемблера
Рис. 10.1.Синтаксис описания метки с помощью оператора
С помощью оператора: можно определить метку только ближнего типа — near. Определенную таким образом метку можно использовать в качестве операнда в командах условных переходов JCC и безусловного перехода JMP, CALL Эти команды, естественно, должны быть в том же сегменте кода, в котором определена метка. Команда ассемблера может находиться как на одной строке с меткой, так и на следующей.
Во втором способе определения меток в программе используется директива LABEL (рис. 10.2).
Т Символическое имя Н label I —I Тип метки]—
Рис. 10.2. Синтаксис директивы LABEL
Тип метки может иметь значение near или far. Обычно директиву LABEL используют для определения идентификатора заданного типа. Например, следующие описания меток ближнего типа эквивалентны: ml:
mov ax,pole_l
ml label near
mov ax,pole_l
Понятно, что метка может быть только одного типа — либо near, либо far. Если возникает необходимость использовать для одной и той же команды метки и дальнего, и ближнего типов, то в этом случае необходимо определить две метки, причем метку дальнего типа нужно описать, используя директиву LABEL, как показано в следующем фрагменте:
publicm_f ar ;сделать метку m_far видимой для внешних программ
m_farlabel far■ определение метки дальнего типа m_far
mjhear::определение метки ближнего типа n_jfar
movax,pole_l一
Определив для команды mov ax,pole_l две метки, можно организовывать переход на эту команду как из данного сегмента команд, так и из других сегментов команд, в том числе принадлежащих другим модулям. Для того чтобы сделать видимым извне имя метки m_far, применяется директива PUBLIC. К более подробному описанию этой директивы мы еще вернемся в главе 15,
Другой часто встречающийся случай применения директивы LABEL — это организация доступа к области памяти, содержащей данные разных типов, например:
mas_b labelbyte
mas_w Ow 15 dup (?)
в этом фрагменте оба идентификатора относятся к одной области памяти и дают возможность работать с ней, используя разные имена, либо как с байтовым массивом, либо как с массивом слов
mov mas_b+10,al ;запись из al в массив байтов (в 11-й байт)
mov mas_w,ax;запись из ах в первое слово области mas_w
Вспомним еще одно очень важное понятие ассемблера, имеющее прямое отношение к меткам, — счетчик адреса команд. Мы уже упоминали о нем в первых главах и говорили, что транслятор ассемблера обрабатывает исходную программу последовательно — команду за командой. При этом ведется счетчик адреса команд, который для первой исполняемой команды равен нулю, а далее, по ходу обработки очередной команды транслятором, увеличивается на длину этой команды. По сути, счетчик адреса команд — это смещение конкретной команды относительно начала сегмента кода. Таким образом, каждая команда во время трансляции имеет адрес, равный значению счетчика адреса команд. Обратитесь к главе 6 и еще раз посмотрите на приведенный в нем листинг 6.2. Первая колонка в листинге — номер строки файла листинга. Вторая колонка (или третья, если присутствует колонка с уровнем вложенности) — смещение команды относительно начала сегмента кода или, как мы сейчас определили, счетчик адреса. Значение, на которое он увеличивается по мере обработки ассемблером очередной строки исходной программы, равно значению длины машинной команды в этой строке. Исходя из этого, ясно, почему счетчик адреса увеличивается только после тех строк исходной программы, которые генерируют некоторое машинное представление (в том числе после директив резервирования и инициализации данных в сегменте данных).
Транслятор ассемблера обеспечивает нам две возможности работы с этим счетчиком:
В использование меток, атрибуту смещения которых транслятор присваивает значение счетчика адреса следующей команды;
綫 применение для обозначения счетчика адреса команд при задании операндов команд специального символа $, во время трансляции заменяемого текущим численным значением счетчика адреса.
Классический пример:
.data
;вычисление длины строки в сегменте данных
Str_Mes db "Займись делом - учи ассемблер ..."
Len_Msg=$-Str_Mes
После ассемблирования значение Len_Msg будет равно длине строки, так как значение символа $ в месте его появления отличается от Str_Mes ровно на длину строки.
Другое применение, избавляющее программиста от необходимости «плодить» лишние метки в программе, — реализация близкого, буквально через следующую или предыдущую команды, перехода. Для примера рассмотрим фрагмент программы из листинга 6.1 (см. главу 6):
cmp dl,9h ;сравнить (dl) с 9h
jle $+5;перейти на команду mov cl,4h, если dl<9h или dl=9h
sub dl,7h;вычитание: (dl)=(dl)47h
;M1:
mov cl,4h: пересылка 4h в регистр cl
cmpal,9h ;сравнить (al) с 9h 28
jle $+4;перейти на команду add dl.al, если al<9h или al=9h
sub al,7h ;вычитание: (al)=(al)47h
adddl.al
Как узнать правильные значения длины команд? Во-первых, по опыту, то.есть просто догадаться. Во-вторых, из файла листинга, что не всегда дает результат, так как конечные машинные команды (колонка файла листинга с объектным кодом) в нем не всегда до конца сформированы. В-третьих, узнать длины команд можно, загрузив программу в отладчик и активировав окно CPU. Последний способ самый точный. Не будет ничего страшного, если для первого прогона исполняемого модуля программы в нем будут неверные значения для подобных относительных переходов. И еще одно замечание: при переходе вперед необходимо учитывать длину текущей команды перехода, при переходе назад этого делать не нужно.
Кроме возможности получения значения счетчика адреса компилятор позволяет при необходимости установить счетчик адреса в нужное абсолютное значение. Это делается с помощью директивы ORG:
ORG выражение
Здесь выражение должно быть таким, чтобы ассемблер мог преобразовать его к абсолютному числу при первом проходе трансляции.
'K примеру, эту директиву всегда используют при создании исполняемого файла с расширением .com. В контексте нашего обсуждения поясним, в чем здесь суть. В главе 5 мы обсуж/^ли сегментацию и деление программы на сегменты. Программа в формате СОМ состоит из одного сегмента величиной не более 64 Кбайт. Сегментные регистры CS и DS содержат одно и то же значение физического адреса, а регистр SS указывает на конец этого единственного сегмента. Программа-загрузчик операционной системы, считывая с диска исполняемые файлы с расширениями .ехе и .com, производит определенные действия. В частности, настраивает переме-
щаемые адреса программ на их конкретные физические значения. Кроме того, к началу каждой исполняемой программы в памяти добавляется специальная область величиной 256 байт (100h) — префикс программного сегмента (PSP). Он предназначен для хранения различной информации о загруженном исполняемом модуле. Для программ формата СОМ блок PSP находится в начале сегмента размером в 64 Кбайт. В исходной программе, для исполняемого файла которой планируется формат СОМ, мы должны предусмотреть место для блока PSP; что и делается директивой org lOOh.
Чтобы закончить разговор о файлах этого типа, разберемся с тем, как получить исполняемый модуль формата СОМ. Трансляция программы выполняется как обычно. Далее возможны два варианта действий.
犠 Во-первых, возможно использование утилитуtlink с ключом /t:
tlink /t имя_объектного_файла
Этот вариант подходит только в том случае, если вы правильно оформили исходный текст программы для формата СОМ. Кроме того, наличие директивы org lOOh предполагает:
отсутствие разделения сегментов данных и стека, то есть данные необходимо описать в сегменте кода, а для их обхода использовать команду безусловного перехода JMP;
П применение директивы ASSUME для указания транслятору на необходимость связать содержимое регистров DS и SS с сегментом кода:
codeseg segment para "code"
assume cs: codeseg.ds:codeseg,ss:codeseg org 100h jmp ml
;здесь описываем данные ml:
;далее идут команды программы
經 Во-вторых, можно использовать специальную утилиту ехе2Ып. Эта утилита позволяет преобразовать уже полученный ранее исполняемый модуль в формате ЕХЕ в формат СОМ:
exe2bin имя_файла_ехе имя_файла_сом.com
Этот вариант не требует специального оформления исходного текста программы. Единственным требованием является то, чтобы исходный текст был довольно мал по объему.
Интересный вариант использования директивы ORG рассмотрен в [8], где с ее помощью выполняется динамическая модификация машинного кода команды.
Безусловные переходы
Предыдущее обсуждение выявило некоторые детали механизма перехода. Команды перехода модифицируют регистр указателя команды EIP/IP и, возможно, сегментный регистр кода CS. Что именно должно подвергнуться модификации, зависит:
:寃* от типа операнда в команде безусловного перехода (ближний или дальний);
от модификатора, который указывается перед адресом перехода в команде перехода и может принимать следующие значения (сам адрес при прямом переходе находится непосредственно в команде, а при косвенном — в регистре или ячейке памяти):
NEAR PTR — прямой переход на метку внутри текущего сегмента кода, при этом модифицируется только регистр EIP/IP (в зависимости от заданного типа сегмента кода usel6 или use32) на основе указанного в команде адреса (метки) или выражения, использующего символ извлечения значения счетчика адреса команд ($);
П FAR PTR — прямой переход на метку в другом сегменте кода, при этом адрес перехода задается в виде непосредственного операнда или адреса (метки) и состоит из 16-разрядного селектора и 16/32-разрядного смещения, которые загружаются, соответственно, в регистры CS и EIP/IP;
D WORD PTR — косвенный переход на метку внутри текущего сегмента кода, при этом модифицируется (значением смещения размером 16 или 32 бита из памяти по указанному в команде адресу или из регистра) только регистр EIP/IP;
D DWORD PTR — косвенный переход на метку в другом сегменте кода, при этом модифицируются (значением из памяти — и только из памяти, из регистра нельзя) оба регистра, CS и EIP/IP (первое слово/двойное слово адреса перехода, представляющее собой смещение, загружается в EIP/IP; второе/третье слово — в CS).
Команда безусловного перехода
Синтаксис команды безусловного перехода без сохранения информации о точке возврата:
jmp [модификатор] адрес_перехода
Здесь адрес_перехода представляет метку или адрес области памяти, в которой находится указатель перехода.
Всего в системе команд процессора есть несколько кодов машинных команд безусловного перехода JMP. Их различия определяются дальностью перехода и способом задания целевого адреса. Дальность перехода определяется местоположением операнда адрес_перехода. Этот адрес может находиться в текущем сегменте кода или в некотором другом сегменте. В первом случае переход называется внутрисегментным, или близким, во втором — межсегментным, или дальним.
Внутрисегментный переход предполагает, что изменяется только содержимое регистра EIP/IP. Можно выделить три вариантавнутрисегментного использования команды JMP:
к прямой короткий переход;
К прямой переход;
綫 косвенный переход.
Прямой короткий внутрисегментный переход применяется, когда расстояние от команды JMP до адреса перехода не превышает -128 или +127 байт. В этом случае транслятор ассемблера формирует машинную команду безусловного перехода длиной всего два байта (размер обычной команды внутрисегментного безусловного перехода составляет три байта). Первый байт в этой команде — код операции, значение которого говорит о том, что процессор должен особым образом трактовать второй байт команды. Значение второго байта вычисляется транслятором как разность между значением смещения команды, следующей за JMP, и значением адреса перехода. При осуществлении прямого короткого перехода нужно иметь в виду следующий важный момент, связанный с местоположением адреса перехода и самой команды JMP. Если адрес перехода расположен до команды JMP, то ассемблер формирует короткую команду безусловного перехода без дополнительных указаний. Если адрес перехода расположен после команды JMP, транслятор не может сам определить, что переход короткий, так как у него еще нет информации об адресе перехода. Для оказания помощи компилятору в формировании команды короткого безусловного перехода в дополнение к ранее упомянутым модификаторам используют модификатор SHORT PTR:
jmp short ptr ml
;не более 35-40 команд (127 байт) ml:
Еще вариант:
ml:
...;не более 35-40 команд (-128 байт) jmp ml
Прямой внутрисегментный переход отличается от прямого короткого внутрисегментного перехода тем, что длина машинной команды JMP в этом случае составляет три байта. Увеличение длины связано с тем, что поле адреса перехода в машинной команде JMP расширяется до двух байтов, а это, в свою очередь, позволяет производить переходы в пределах 64 Кбайт относительно следующей за JMP команды: ml:
...;расстояние более 128 байт и менее 64 Кбайт jmp ml
Косвенный внутрисегментный переход подразумевает «косвенность» задания адреса перехода. Это означает, что в команде указывается не сам адрес перехода, а место, где он «лежит». Приведем несколько примеров, в которых двухбайтовый адрес перехода выбирается либо из регистра, либо из области памяти:
lea bx .ml
jmp bx ;адрес перехода в регистре bx
ml:
.data
addr_ml dw ml
.code
jmp addr_ml;адрес перехода в ячейке памяти addr_ml
ml:
Еще несколько вариантов косвенного внутрисегментного перехода:
<2>.data
<3>addr dw ml
4> dw m2
<6>.code
<8>cycl:
9>mov si ,0
10jmp addr(si]:адрес перехода в слове памяти addr+(si)
11>...
12>mov si ,2
«13>jmp cycl'
<14>ml:
<16>m2:
17>...
В этом примере одна команда JMP (строка 10) может выполнять переходы на разные метки. Выбор конкретной метки перехода определяется содержимым регистра SI. Операнд команды JMP определяет адрес перехода косвенно после вычисления выражения addr+(SI).
<1>...
<2>.data
<3>addr dw ml
<4>...
<5>.code
6>...
7>lea si ,addr
8>jmp near ptr [si] ;адрес перехода в ячейке памяти addr
<10>ml:
В данном случае указание модификатора NEAR PTR обязательно, так как, в отличие от предыдущего способа, адрес ячейки памяти addr с адресом перехода транслятору передается неявно (строки 3, 7 и 8), и, не имея информации о метке, он не может определить, какой именно переход осуществляется — внутрисегментный или межсегментный. Межсегментный переход предполагает другой формат машинной командыJMР. При осуществлении межсегментного перехода кроме регистра EIP/IP модифицируется также регистр CS. Аналогично внутрисегментному переходу, межсегментный переход поддерживают два варианта команд безусловного перехода: прямой и косвенный.
Команда прямого межсегментного перехода имеет длину пять байтов, из которых два байта составляют значение смещения и два байта — значение сегментной составляющей адреса: seg_l segment
jmp far ptr m2 ;здесь far обязательно
ml label far
seg_2segment
m2 labelfar
jmp ml;здесь far необязательно
Рассматривая этот пример, обратите внимание на модификатор FAR PTR в команде JMP. Его необходимость объясняется все той же логикой работы однопроходного транслятора. Если описание метки (метка ml) встречается в исходном тексте программы раньше, чем соответствующая ей команда перехода, то задание модификатора необязательно, так как транслятор все знает о данной метке и сам формирует нужную пятибайтовую форму команды безусловного перехода. В случае, когда команда перехода встречается до описания соответствующей метки, транслятор не имеет еще никакой информации о метке, и модификатор FAR PTR в команде JMP опускать нельзя, так как транслятор не знает, какую форму команды формировать — трехбайтную или пятибайтную. Без специального указания модификатора транслятор будет формировать трехбайтную команду внутрисегментного перехода.
Команда косвенного межсегментного перехода в качестве операнда имеет адрес области памяти, в которой содержатся смещение и сегментная часть целевого адреса перехода: datasegment
addr_ml dd ml ;в поле addr_ml значения смещения ;и адреса сегмента метки ml dataends
code_l segment
jmp ml
code_l ends code_2 segment
ml labelfar
mov ax,bx
code_2 ends
Как вариант косвенного межсегментного перехода необходимо отметить косвенный регистровый межсегментный переход. В этом виде перехода адрес перехода указывается косвенно — в регистре. Это очень удобно для программирования динамических переходов, в которых адрес перехода может изменяться на стадии выполнения программы: datasegment
addr_ml dd ml ;в поле addr_ml значения смещения
;и адреса сегмента метки ml dataends
code_l segment
lea bx,addr_ml
jmp dword ptr[bx]
code_l ends code_2 segment
ml labelfar
mov ax, bx
Таким образом, модификаторы SHORT PTR, NEAR PTR и WORD PTR применяются для организации внутрисегментных переходов, a FAR PTR и DWORD PTR — межсегментных.
Для полной ясности нужно еще раз подчеркнуть, что если тип сегмента — use32, то в тех местах, где речь шла о регистре IP, можно использовать регистр EIP и, соответственно, размеры полей смещения увеличить до 32 битов.
Процедуры
До сих пор мы рассматривали примеры программ, предназначенные для однократного выполнения. Но, приступив к программированию достаточно серьезной задачи, вы наверняка столкнетесь с тем, что у вас появятся повторяющиеся фрагменты кода. Одни из них могут состоять всего из нескольких команд, другие занимать и достаточно много места в исходном коде. В последнем случае эти фрагменты существенно затруднят чтение текста программы, снизят ее наглядность, усложнят отладку и послужат неисчерпаемым источником ошибок. В языке ассемблера есть несколько средств, решающих проблему дублирования фрагментов программного кода. К ним относятся:
腌 процедуры;
競 макроподстановки (макроассемблер);
購 генерация и обработка программных прерываний.
В данной главе рассматриваются только основные понятия, относящиеся к вызову процедур. Ввиду важности этого вопроса мы продолжим его изучение в главе 15 в контексте темы модульного программирования на ассемблере. Актуальная для программирования под Windows проблема разработки библиотек DLL на ассемблере описана в [8]. Макроассемблеру посвящена глава 14.
Процедура, или подпрограмма, — это основная функциональная единица декомпозиции (разделения на части) некоторой задачи. Процедура представляет собой группу команд для решения конкретной подзадачи и обладает средствами получения управления из точки вызова задачи более высокого уровня и возврата управления в эту точку. В простейшем случае программа может состоять из одной процедуры. Другими словами, процедуру можно определить как правильным образом оформленную совокупность команд, которая, будучи однократно описана, при необходимости может быть вызвана в любом месте программы.
Для описания последовательности команд в виде процедуры в языке ассемблера используются две директивы: PROC и ENDP.
Синтаксис описания процедуры таков (рис. 10.3).
Из рисунка видно, что в заголовке процедуры (директиве PROC) обязательным является только задание имени процедуры. Среди большого количества операндов директивы PROC следует особо выделить [расстояние]. Этот атрибут может принимать значения NEAR или FAR и характеризует возможность обращения к процедуре из другого сегмента кода. По умолчанию атрибут [расстояние] принимает значение NEAR.
Процедура может размещаться в любом месте программы, но так, чтобы на нее случайным образом не попало управление. Если процедуру просто вставить в общий поток команд, то процессор воспримет команды процедуры как часть этого
имя_процедуры PROC [[модификатор_языка ] язык] [расстояние ] -1 [ARG СПИСОК япгл/клаытлп 1
Заголовок процедуры
[RETURN список_элементов]
—
команды, директивы ассемблера
Тело процедуры
[имя_процедуры ]ENDP
Конец процедуры
Рис. 10,3. Синтаксис описания процедуры в программе
потока и, соответственно, начнет выполнять эти команды. Учитывая это обстоятельство, есть следующие варианты размещения процедуры в программе:
в начале программы (до первой исполняемой команды);
в конце программы (после команды, возвращающей управление операционной системе);
промежуточный вариант — внутри другой процедуры или основной программы (в этом случае необходимо предусмотреть обход процедуры с помощью команды безусловного перехода JM Р);
雄 в другом модуле (библиотеке DLL).
Размещение процедуры в начале сегмента кода предполагает, что последовательность команд, ограниченная парой директив PROC и ENDP, будет размещена до метки, обозначающей первую команду, с которой начинается выполнение программы. Эта метка должна быть указана как параметр директивы END, обозначающей конец программы: model small .stack 100h .data .code my_proc procnear ret my^proc endp start: end start
Объявление имени процедуры в программе равнозначно объявлению метки, поэтому директиву PROC в частном случае можно рассматривать как завуалированную форму определения программной метки. Поэтому сама исполняемая программа также может быть оформлена в виде процедуры, что довольно часто и делается с целью пометить первую команду программы, с которой должно начаться выполнение. При этом не забывайте, что имя этой процедуры нужно обязательно указывать в заключительной директиве END. Такой синтаксис мы уже неоднократно использовали в своих программах. Так, последний рассмотренный фрагмент эквивалентен следующему: modelsmall
.stack 100h
.data .code my_proc procnear
ret
my^proc endp start proc
startendp
end start
В этом фрагменте после загрузки программы в память управление будет передано первой команде процедуры с именем start.
Размещение процедуры в конце программы предполагает, что последовательность команд, ограниченная директивами PROC и ENDP, находится следом за командой, возвращающей управление операционной системе: modelsmall
.stack 100h .data .code start:
mov ax*4c@®h
int 21h ;возврат управления операционной системе my_proc procnear
ret
my_proc endp end start
Промежуточный вариант расположения тела процедуры предполагает ее размещение внутри другой процедуры или основной программы. В этом случае необходимо предусмотреть обход тела процедуры, ограниченного директивами PROC и ENDP, с помощью команды безусловного перехода ЗМР: model small .stack 100h .data .code start:
jmp ml
my_proc procnear ret
my_proc endp ml:
mov ax,4c00h
int 21h ;возврат управления операционной системе endstart
Последний вариант расположения описаний процедур — в отдельном сегменте кода — предполагает, что часто используемые процедуры выносятся в отдельный файл, который должен быть оформлен как обычный исходный файл и подвергнут трансляции для получения объектного кода. Впоследствии этот объектный файл с помощью утилиты tlink можно объединить с файлом, в котором данные процедуры используются. С утилитой tlink мы познакомились в главе 6. Этот способ предполагает наличие в исходном тексте программы еще некоторых элементов, связанных с особенностями реализации концепции модульного программирования
в языке ассемблера. Поэтому в полном объеме этот способ будет рассмотрен в главе 15.
Как обратиться к процедуре? Так как имя процедуры обладает теми же атрибутами, что и обычная метка в команде перехода, то обратиться к процедуре, в принципе, можно с помощью любой команды перехода. Но есть одно важное свойство, которое можно использовать благодаря специальному механизму вызова процедур. Суть состоит в возможности сохранения информации о контексте программы в точке вызова процедуры. Под контекстом понимается информация о состоянии программы в точке вызова процедуры. В системе команд процессора есть две команды для работы с контекстом — CALL и RET.
Команда CALL осуществляет вызов процедуры (подпрограммы). Синтаксис команды:
call [модификатор] имя_процедуры
Подобно команде JMP команда CALL передает управление по адресу с символическим именем имя_процедуры, но при этом в стеке сохраняется адрес возврата (то есть адрес команды, следующей после команды CALL).
Команда RET считывает адрес возврата из стека и загружает его в регистры CS и EIP/IP, тем самым возвращая управление на команду, следующую в программе за командой CALL Синтаксис команды:
ret [число]
Необязательный параметр [число] обозначает количество элементов, удаляемых из стека при возврате из процедуры. Размер элемента определяется хорошо знакомыми нам параметрами директивы SEGMENT — US816 и use32 (или соответствующим параметром упрощенных директив сегментации). Если указан параметр usel6, то [число] — это значение в байтах; если use32 — в словах.
Для команды CALL, как и для JMP, актуальна проблема организации ближних и дальних переходов. Это видно из формата команды, где присутствует параметр [модификатор]. Как и в случае команды JMP, вызов процедуры командой CALL может быть внутрисегментным и межсегментным.
犠 При внутрисегментном вызове процедура находится в текущем сегменте кода (имеет тип near), и в качестве адреса возврата команда CALL сохраняет только содержимое регистра IP/EIP, что вполне достаточно (рис. 10.4).
№ При межсегментном вызове процедура находится в другом сегменте кода (имеет тип far), и для осуществления возврата команда CALL должна запомнить содержимое обоих регистров (CS и IP/EIP), при этом в стеке сначала запоминается содержимое регистра CS, затем — регистра IP/EIP (рис. 10.5).
Важно отметить, что одна и та же процедура не может быть одновременно процедурой ближнего и дальнего типов. Таким образом, если процедура используется в текущем сегменте кода, но может вызываться и из другого сегмента программы, то она должна быть объявлена процедурой типа far. Подобно команде JMP, существуют четыре разновидности команды CALL. Какая именно команда будет сформирована, зависит от значения модификатора в команде вызова процедуры CALL и атрибута дальности в описании процедуры. Если процедура описана в начале сегмента данных с указанием дальности в ее заголовке, то при ее вызове параметр
Оперативная памятьОперативная памятьОперативная памятьОперативная памятьСтаршие адреса ОП Стек после команды call my_procСтаршие адреса ОП Стек после команды call my_procПроцедура ближнего типа my_proc:
Оперативная память
Оперативная память
Оперативная память
Оперативная память
Старшие адреса ОП Стек после команды call my_proc
Старшие адреса ОП Стек после команды call my_proc
Старшие адреса
Стек до командыcall
Рис.Содержимое стека до и после выполнения команды вызова процедуры
ближнего типа
Оперативная памятьОперативная памятьСтаршие адреса ОП Стек до команды call my_procСтаршие адреса ОП Стек до команды call my_procОперативная памятьОперативная памятьСтаршие адреса ОПСтаршие адреса ОППроцедура дальнего типа
Оперативная память
Оперативная память
Старшие адреса ОП Стек до команды call my_proc
Старшие адреса ОП Стек до команды call my_proc
Оперативная память
Оперативная память
Старшие адреса ОП
Старшие адреса ОП
Стек после командыcall my_proc
Рис.Содержимоедо и после выполнения команды вызова процедуры
дальнего типа
[модификатор] можно не указывать: транслятор сам разберется, какую команду CALL ему нужно сформировать. Если же процедура описана после ее вызова, например, в конце текущего сегмента или в другом сегменте, то при ее вызове нужно указать ассемблеру тип вызова, чтобы он мог за один проход правильно сформировать команду CALL. Значения модификатора такие же, как и у команды ЗМР, за исключением значения SHORT PTR.
С директивой PROC используются еще несколько директив: ARG, RETURNS, LOCAL, USES. Их назначение — помочь программисту выполнить некоторые рутинные действия при вызове и возврате из процедуры (заодно и повысив надежность кода). Директивы ARG и RETURNS назначают входным и выходным параметрам процедуры, передаваемым через стек, символические имена. Директива USES в качестве параметров содержит имена используемых в процедуре регистров. При обработке
этой директивы ассемблер формирует входной и выходной коды процедуры (из команд PUSH и POP), обеспечивающие сохранение и восстановление регистров. Директива LOCAL предназначена для выделения кадра стека для локальных переменных, что позволяет экономить память, занимаемую программой в целом. Подробно эти директивы обсуждаются в главе 15.
Необходимо заметить, что в данном разделе приведена информация о порядке описания процедур, принятом в TASM. Описание и использование процедур в MASM имеет особенности, о которых можно узнать из материала главы 15.
Последний и, наверное, самый важный вопрос, возникающий при работе с процедурами, — как правильно передать параметры процедуре и вернуть результат? Этот вопрос тесно связан с концепцией модульного программирования и подробно будет рассматриваться в главе 15. С примерами использования процедур вы можете познакомиться в листингах подпрограмм, предназначенных для вычисления четырех основных арифметических действий с двоичными и десятичными (BCD) числами и находящихся среди прилагаемых к книге файлов в каталоге главы 81. Кроме того, вопросы организации рекурсивных и вложенных процедур рассмотрены в [8].
Условные переходы
До сих пор мы рассматривали команды перехода с «безусловным» принципом действия, но в системе команд процессора есть большая группа команд, призванных самостоятельно принимать решение о том, какая команда должна выполняться следующей. Решение принимается в зависимости от определенных условий, определяемых конкретной командой перехода. Процессор поддерживает 18 команд условного перехода, позволяющих проверить:
落 отношение между операндами со знаком (больше или меньше);
отношение между операндами без знака (выше или ниже)2;
состояниями арифметических флагов ZF, SF, CF, OF, PF (но не AF).
Команды условного перехода имеют одинаковый синтаксис:
jcc метка_перехода
Как видно, мнемокод всех команд начинается с символа «j» — от слова jump (прыжок). Вместо символов «сс» указывается конкретное условие, анализируемое командой. Что касается операнда метка_перехода, то он определяет метку перехода, которая может находиться только в пределах текущего сегмента кода; межсегментной передачи управления в условных переходах не допускается. В связи с этим отпадает вопрос о модификаторе, который присутствовал в синтаксисе команд безусловного перехода. В ранних моделях процессора (8086, 80186 и 80286)
Все прилагаемые к книге файлы можно найти по адресу http://www.piter.com/download. — Примеч. ред.
? Термины «больше или меньше» и «выше или ниже» происходят от соответствующих английских терминов «greater-less» и «above-below». Первые буквы этих терминов входят в состав мнемонических обозначений соответствующих команд условного перехода. Несмотря на кажущуюся синонимичность этих терминов, на самом деле они отражают тот факт, что соответствующие им команды условного перехода анализируют разные флаги (см. также далее пояснения в тексте).
команды условного перехода могли осуществлять только короткие переходы — на расстояние от -128 до +127 байт от команды, следующей за командой условного перехода. Начиная с процессора 80386 это ограничение снято, но, как видите, только в пределах текущего сегмента кода.
Для того чтобы принять решение о том, куда будет передано управление командой условного перехода, предварительно должно быть сформировано условие, на основании которого должно приниматься решение. Источниками такого условия могут быть:
любая команда, изменяющая состояние арифметических флагов;
魏 команда CMP, сравнивающая значения двух операндов;
состояние регистра ЕСХ/СХ.
Обсудим эти варианты, чтобы разобраться с тем, как работают команды условного перехода.
Команда сравнения
Команда сравнения СМР (CoMPare)имеет интересный принцип работы. Он абсолютно такой же, как у команды вычитания SUB (см. главу 8). Команда СМР так же, как и команда SUB, выполняет вычитание операндов и по результатам сравнения устанавливает флаги. Единственное, чего она не делает, — не записывает результат вычитания на место первого операнда.
Синтаксис команды CMP:
cmp операнд_1,операнд^
Флаги, устанавливаемые командой СМР, можно анализировать специальными командами условного перехода. Прежде чем мы их рассмотрим, уделим немного внимания мнемонике этих команд (табл. 10.1). Понимание обозначений (элементов в названии командыJCC, обозначенных нами символами «сс») при формировании названия команд условного перехода облегчит их запоминание и дальнейшее практическое использование.
Таблица 10.1. Значение аббревиатур в названии команды jcc
Мнемоническое обозначение
Оригинальный термин
Перевод
Типоперандов
е
Equal
Равно
Любые
n
Not
Нет
Любые
g
Greater
Больше
Числа со знаком
1
Less
Меньше
Числа со знаком
а
Above
Выше (в смысле больше)
Числа без знака
Ь
Below
Ниже (в смысле меньше)
Числа без знака I
В табл. 10.2 представлен перечень команд условного перехода для команды СМР. Не удивляйтесь тому обстоятельству, что одинаковым значениям флагов соответствуют несколько разных мнемокодов команд условного перехода (они отделены друг от друга косой чертой). Разница в названии обусловлена желанием разработ-
8 Зак. 256
чиков процессора упростить использование команд условного перехода с разными группами команд. Поэтому разные названия отражают скорее разную функциональную направленность. Тем не менее, то, что эти команды реагируют на одни и те же флаги, делает их абсолютно эквивалентными и равноправными в программе. Именно поэтому они сгруппированы не по названиям, а по значениям флагов (условиям), на которые они реагируют.
Таблица10.2.Перечень командусловного переходадля команды cmp
Типы
операндов
Мнемокод команды условного перехода
Критерий условного перехода
Значения флагов для перехода
Любые
JE
операнд_1= операнд_2
ZF= 1
Любые
JNE
операнд_1 <> операнд_2
ZF У0
Со знаком
JL/JNGE
операнд_1 < операнд_2
SF <> OF
Со знаком
JLE/JNG
операнд_1 <= операнд_2
SF <> OF или ZF = 1
Со знаком
JG/JNLE
операнд_1 > операнд_2
SF - OF и ZF = 0
Со знаком
JGE/JNL
операнд_1 => операнд_2 |
SF = OF
Без знака
JB/JNAE
операнд_1 < операнд_2
CF = 1
Без знака
JBE/JNA
операнд_1 <= операнд_2
CF == 1 или ZF=1
Без знака
JA/JNBE
операнд_1 > оиеранд_2
CF - 0 и ZF - 0
Без знака
JAE/JNB
опсранд_1 => операнд_2
CF = 0
В качестве примера применения команды CMP рассмотрим фрагмент программы, который обнуляет поле pole_m длиной п байт:
.data
n equ 50
pole_m db n dup (?)
.code,
xor bx, bx;bx=0
ml: mov mem[bx](0
inc bx
cmp bx,n
jne ml
exit:
Так как команды условного перехода не изменяют флагов, то после одной команды CMP вполне могут следовать несколько команд условного перехода. Это может быть сделано для того, например, чтобы исследовать каждую из альтернативных ветвей: больше, меньше или равно:
.data
mas db dup (?)
.code
eql:eql:cmpmas[si],5;сравнитьочередной элементмассива с 5jeeqlпереход,если элементmasравен 5j l low;переход,если элементmasменьше 5jggrt;переход.если элементmasбольше 5cmpmas[si],5;сравнитьочередной элементмассива с 5jeeqlпереход,если элементmasравен 5j l low;переход,если элементmasменьше 5jggrt;переход.если элементmasбольше 5
eql:
eql:
cmp
mas[si],5
;сравнить
очередной элемент
массива с 5
je
eql
переход,
если элемент
mas
равен 5
j l low
;переход,
если элемент
mas
меньше 5
jg
grt
;переход.
если элемент
mas
больше 5
cmp
mas[si],5
;сравнить
очередной элемент
массива с 5
je
eql
переход,
если элемент
mas
равен 5
j l low
;переход,
если элемент
mas
меньше 5
jg
grt
;переход.
если элемент
mas
больше 5
low:
grt:
Команды условного перехода и флаги
Мнемоническое обозначение некоторых команд условного перехода отражает название флага, с которым они работают, и имеет следующую структуру: первым идет символ «j» (jump ’♦— переход), вторым — либо обозначение флага, либо символ отрицания «и», после которого стоит название флага. Такая структура команды отражает ее назначение. Если символа «п» нет, то проверяется состояние флага и, если он равен 1, производится переход на метку перехода. Если символ «п» присутствует, то проверяется состояние флага на равенство 0 и в случае успеха производится переход на метку перехода. Мнемокоды команд, названия флагов и условия переходов приведены в табл. 10.3. Эти команды можно использовать после любых команд, изменяющих указанные флаги.
Таблица 10.3. Команды условного перехода и флаги
Название флага
Номер бита в регистре eflags/flags
Команда условного перехода
Значение флага для осуществления перехода
Переноса CF1
1
JC
CF=1
Четности PF
2
JP
PF = 1
Нуля ZF
6
JZ
ZF = 1
Знака SF
7
JS
SF = 1
Переполнения OF
11
JO
OF= 1
Переноса CF
1
JNC
CF = 0
Четности PF
2
JNP
PF 0
Нуля ZF
6
JNZ
ZF 0
Знака SF
7
jns
sf=O
Переполнения OF
11
JNO
OF 0
Если внимательно посмотреть на табл. 10.2 и 10.3, видно, что многие команды условного перехода в них эквивалентны, так как в них анализируются одинаковые флаги.
В листинге 10.1 приведен пример программы, производящей в строке символов длиной п байт замену строчных букв английского алфавита прописными. Для осмысленного рассмотрения этого примера вспомним ASCII-коды, соответствующие этим буквам (см. главу 6). Строчные и прописные буквы в таблице ASCII упорядочены по алфавиту. Строчным буквам соответствует диапазон кодов 61h-7ah, прописным — 41h-5ah. Для того чтобы понять идею, лежащую в основе алгоритма преобразования, достаточно сравнить представления соответствующих прописных и строчных букв в двоичном виде:
a-01100001...z-01111010
А - 0100 0001...Z - 0101 1010
Как видно из приведенного двоичного представления, для выполнения преобразования между строчными и прописными буквами достаточно всего лишь инвертировать 5-й бит.
Листинг10.1.Смена регистра символов
<1>
;prg_10^1 .asm
<2> modelsmall
<3>
.stack 100h
<4>
.data
<5> n equ 10;количество символов в stroka
<6>
stroka db "acvfgrndup"
<7>
.code
<8> ;
start:
<9>
mov ax,@data
mov ds, ax
<11>
xor ax. ax
<12>
mov ex, n
<13>
lea bx, stroka ;адрес stroka в bx
ml: mov al,[bx];очередной символ из stroka в al
<15>
cmp al,61h;убедиться, что код символа не меньше 61h
<16>
jb next;если меньше, то не обрабатывать
<17>
;;и перейти к следующему символу
<18>
cmp al,7ah;;убедиться, что код символа не больше 7ah
<19>
ja next;если больше, то не обрабатывать
<20>
и перейти к следующему символу
<21>
and al,11011111b инвертировать 5-й бит
<22>
mov [bx],al;;символ - на его место в stroka
<23>
next:
<24>
inc bx;адресовать следующий символ
<25>
dec exуменьшить значение счетчика в сх
<26>
jnz ml;если сх не 0, то переход на ml
<27>
exit:
<28>
mov ax ,4c00h
<29>
int 21h;возврат управления операционной системе
<30>
end start
Обратите внимание на строку 25 листинга. Команда DEC уменьшает значение регистра СХ на 1. Когда это значение станет равным 0, процессор по результату операции декремента установит флаг ZF. Команда в строке 26 анализирует состояние этого флага и, пока он не равен 1 (см. табл. 10.3), передает управление на метку ml. Заметьте, что на место этой команды можно было бы поставить командуJNE (см. табл. 10.2). Но для анализа регистра СХ в системе команд процессора есть специальная команда, которую мы сейчас и рассмотрим.
Команды условного перехода и регистр ЕСХ/СХ
Архитектура процессора предполагает специфическое использование многих регистров. К примеру, регистр EAX/AX/AL используется как аккумулятор, а регистры ЕВР/ВР, ESP/SP — для работы со стеком. Регистр ЕСХ/СХ тоже имеет определенное функциональное назначение — он выполняет функции счетчика в командах управления циклами и при работе с цепочками символов. Возможно, что функционально команды условного перехода, связанные с регистром ЕСХ/СХ, правильнее было бы отнести к этой группе команд.
Синтаксис команд JCXZ (Jump if is— переход, если СХ ноль) и J ECXZ (Jump
Equal ecx Zero — переход, если ЕСХ ноль) таков:
jcxz/jecxz метка_перехода
Эти команды очень удобно использовать при организации цикла и при работе с цепочками символов. В этой главе мы разберемся со средствами организации циклов в программах на языке ассемблера и покажем работу команд JCXZ/JECXZ. Глава 12 будет посвящена цепочечным командам, где мы еще раз вернемся к командам JCXZ/JECXZ. Нужно отметить свойственное им ограничение. В отличие от других команд условной передачи управления, командыECXZ могут адресовать толь
ко короткие переходы — на -128 байт или на +127 байт от следующей за ней команды.
Установка байта по условию
При рассмотрении команд условного перехода логичноеще об од
ной команде — SETcc. Данная команда впервые появилась в процессоре Ее формат:
SETcc
Команда устанавливает байтпосле проверки модификатора задаю
щего условие, аналогично тому, как это делается в командах условного перехода. Фактически, модификатор обозначает флаг, который нужно проверить (см. приложение). В качестве операнда используется либо один из 8-разрядных регистров (AL, АН, BL, BH, CL, CH, DL, DH), либо адрес ячейки памяти размером в байт. Алгоритм работы команды заключается в том, что значение операнда устанавливается по результатам проверки условия, заданного модификатором в= 0 — если условие ложно;
я= 1 — если условие истинно.
К примеру, в следующем фрагменте байт по адресу rez будет установлен в 0: mov ax,0 cmp ax,1 sete byte ptr
Организация циклов
Цикл, как известно, представляет собой важную алгоритмическую структуру, без которой не обходится, наверное, ни одна программа. Организовать циклическое выполнение некоторого фрагмента программы можно, к примеру, используя команды условной передачи управления или команду безусловного перехода JMP. Например, подсчитаем количество нулевых байтов в области mas (листинг 10.2).
ЛистингПодсчет числа нулевых элементов
1> ;prg_10_2.asm
2> model
3> .stack 100h
<4>
5> Ien equ 10:количество элементов в mas
6> mas db
7>
Листинг 10.2
<8> start:
<9>
raov
ax @data
<10>
<11>
mov
mov
ds ax len
;длину поля mas в сх
<12>
<13>
<14>
<15>
xor xor cycl:
jcxz
ax ax si
exit
;проверка сх на 0, если 0, то выход
<16>
cmp
mas[si],0
;сравнить очередной элемент mas с
0
<17>
jne
ml
;если не равно 0, то на ml
<18>
inc
al
;в- счетчик нулевых элементов
<19>
<20>
ml:
inc
si
;перейти к следующему элементу
<21>
dec
сх
сх на 1
<22>
<23>
<24>
<25>
jmp exit:
mov
int
cycl
ax ,4c00h 21h
;возврат управления операционной
системе
<26>end start
Цикл в листинге 10.2 организован тремя командами, JCXZ, DEC и JMP (строки 15, 21 и 22). Команда JCXZ выполняет здесь две функции: предотвращает выполнение «пустого» цикла (когда счетчик цикла в СХ равен нулю) и отслеживает окончание цикла после обработки всех элементов поля mas. Команда после каждой итерации цикла уменьшает значение счетчика в регистре СХ на Заметьте, что при такой организации цикла все операции по его организации выполняются «вручную». Но, учитывая важность такого алгоритмического элемента, как цикл, разработчики процессора ввели в систему команд группу из трех команд, облегчающую программирование циклов. Эти команды также используют регистр ЕСХ/СХ как счетчик цикла. Дадим краткую характеристику этим командам.
Команда LOOP позволяет организовать циклы (loops), подобные циклам for в языках высокого уровня с автоматическим уменьшением счетчика цикла. Синтаксис команды:
loop метка_перехода
Команда реализует описанные далее действия.
Декремент регистра ЕСХ/СХ.
Сравнение регистра ЕСХ/СХ с нулем:
П если (ЕСХ/СХ) > 0, то управление передается на метку перехода;
П если (ЕСХ/СХ) = 0, то управление передается на следующую после LOOP команду.
Команды LOOPE и LOOPZ (Loop still сх <> 0 or Zero flag = 0 — повторить цикл пока СХ <> 0 или ZF = 0) — абсолютные синонимы, поэтому используйте ту команду, которая вам больше нравится. Синтаксис команд:
метка_перехода
Команды реализуют описанные далее действия.
Декремент регистра ЕСХ/СХ.
Сравнение регистра ЕСХ/СХ с нулем и анализ состояния флага нуля ZF:
П если> 0 и ZF = 1, управление передается на метку перехода;
D если (ECX/CX) = 0 илиZF=O,управление передается на следующую после ШОР команду.
Команды LOOPNE и LOOPNZ (Loop still ex <> 0 or NonZero flag = 0 — повторить цикл, пока СХ <> 0 или ZF 1) также абсолютные синонимы. Синтаксис команд: loopne/loopnz метка_перехода
Команды реализуют описанные далее действия.
Декремент регистра ЕСХ/СХ.
Сравнение регистра ЕСХ/СХ с нулем и анализ состояния флага нуля ZF:
□ если (ЕСХ/СХ) > 0 и ZF = 0, управление передается на метку перехода;
О если (ЕСХ/СХ) = 0 или ZF = 1, управление передается на следующую после LOOP команду.
Команды LOOPE/LOOPZ и LOOPNE/LOOPNZ по принципу своей работы являются взаимнообратными. Они расширяют действие команды LOOP тем, что дополнительно анализируют флаг ZF. Это дает возможность организовать досрочный выход из цикла, используя этот флаг в качестве индикатора. Типичное применение этих команд связано с операцией поиска определенного значения в последовательности или со сравнением двух чисел.
Недостаток команд организации цикла LOOP, LOOPE/LOOPZ и LOOPNE/LOOPNZ заключается в том, что они реализуют только короткие переходы (от -128 до +127 байт). Для работы с длинными циклами придется использовать команды условного перехода и команду JMP (см. листинг 10.2), поэтому постарайтесь освоить оба способа организации циклов. Рассмотрим несколько примеров организации циклов с помощью команд LOOP, LOOPE/LOOPZ и LOOPNE/LOOPNZ.
Программа из листинга 10.2 с использованием команды организации цикла будет выглядеть так, как показано в листинге 10.3.
Листинг 10.3. Подсчет нулевых байтов с использованием команд управления циклом
1> ;prg_10_3.asm
2> model small
3> .stack 100h
4> .data
5> len equ 10;количество элементов в mas
6> mas db 1,0,9,8,0.7,8.0,2.0
7> .code
8> start:
9>mov ax,@data
10>mov ds, ax
11>movex,len
12>xorax, ax
13>xorsi, si
14>jcxzexit
15> cycl:
1б>cmpmas [si] ,0
17>jneml
18>incal
19> ml:
20>incsi
21>loop cycl
22> exit:
;длину поля mas в сх
[проверка сх на 0, если 0, то выход
;сравнить очередной элемент mas с 0
;если не равно 0, то на ml
;в э I - счетчик нулевых элементов
;перейти к следующему элементу
продолжение步
Листинг 10.3( продолжение)
23>mov ах,4c00h
24> int 21h;возврат управления операционной системе
25>end start
Заметьте, что у команды JCXZ в строке 14 осталась только одна функция — не допустить выполнения «пустого» цикла, поэтому несколько изменилось ее место в тексте программы: теперь она стоит перед меткой начала цикла cycl. Изменение и контроль содержимого регистра СХ в процессе выполнения каждой итерации выполняет команда ШОР (строка 21).
Рассмотрим пример, в котором продемонстрируем типичный подход к использованию команды LOOPNZ. В программе из листинга 10.4 ищется первый нулевой элемент в поле mas. Интерес представляют строки 20 и 21. Команда LOOPNZ на ос- повании содержимого регистра СХ и флага ZF принимает решение о продолжении цикла. Выход из цикла происходит в одном из двух случаев: СХ = 0 (просмотрены все элементы поля mas) или ZF 1 (командой СМР обнаружен нулевой элемент). Назначение следующей команды JZ (строка 21) в том, чтобы распознать конкретную причину выхода из цикла. Если выход из цикла произошел после просмотра строки, в которой нет нулевых элементов, то флаг JZ не сработает и будет выдано сообщение об отсутствии нулевых элементов в строке (строки 7, 23-25). Если выход из цикла произошел в результате обнаружения нулевого элемента, то в регистре SI окажется номер позиции этого элемента в поле mas и при необходимости можно продолжить обработку. В нашем случае мы просто завершаем программу — переходим на метку exit.
Листинг 10.4. Пример использования команды loopnz
<1>
;prg 10 4.asm
<2> model small
<3>
.stack 100h
<4>
.data
<5>
Ten equ 10;количество элементов в mas
<6>
mas db 1,@,9,8,®,7,8,®,2,®
<7> message db "В поле mas нет элементов, равных нулю,$"
<8>
.code
<9> start:
<10>
mov ax,@data
<11>
mov ds, ax
<12>
mov ex,len;длину поля mas в сх
<13>
xor ax, ax
<14>
xor si, si
<15>
jexz exit;проверка сх на 0, если 0, то выход
<1б>
mov si,-1;готовим si к адресации элементов поля mas
<17>
cycl:
<18>inc si
<19>
cmp mas[si] ,0;сравнить очередной элемент mas с 0
<20>
loopnz cycl
<21>
jz exit;выяснение причины выхода из цикла
<22>
;вывод сообщения, если нет нулевых элементов в mas
<23>
mov ah, 9
<24>
mov dx,offset message
<25>
int 21h
<26>
exit:
<27>
mov ax ,4c00h
<28>
int 21h;возврат управления операционной системе
<29>
end start
Читатели, имеющие даже небольшой опыт программирования на языках высокого уровня, знают, что очень часто возникает необходимость во вложенных циклах. Самый простой пример — обработка двухмерного массива. Работу с массивами, в том числе двухмерными, мы рассмотрим в главе 13, пока же разберемся с основными принципами организации вложенных циклов. Основная проблема, которая при этом возникает, — как сохранить значения счетчиков в регистре ЕСХ/ СХ для каждого из циклов. Для временного сохранения счетчика внешнего цикла на время выполнения внутреннего доступно несколько способов: задействовать регистры, ячейки памяти или стек. В следующем фрагменте программы имеется три цикла, вложенные один в другой. Этот фрагмент можно рассматривать как шаблон для построения других программ с вложенными циклами.
<1>
<2>mov ex,100
3> cycl_l:
4>push сх
5>команды
6>mov ex, 50
7> cycl_2:
8>push сх
9>;;команды
<10>mov ex, 21
11>cycl_3:
12>;...;команды
13>loop cycl
количество
повторений
цикла
cycl_l
;счетчик цикла cycl_l цикла cycl_l
количество повторений
;счетчик цикла cycl_2
цикла cycl_2
5количество повторений
цикла cycl_3
_3
в стек
цикла
в стек
цикла
cycl_2
cycl_3
<14>;команды цикла cycl_2
<15>pop сх ;восстановить счетчик цикла cycl_2
<1б>loop cycl_2
<17>:…;команды цикла cycl_l
<18>pop сх;восстановить счетчик цикла cycl_l
<19>loop cycl_l
<20>
В качестве примера рассмотрим фрагмент программы, которая обрабатывает специальным образом некоторую область памяти (листинг 10.5). Область памяти рассматривается как совокупность пяти полей, содержащих 10 однобайтовых элементов. Требуется заменить все нулевые байты в этой области значением Offh.
Листинг 105Пример использования вложенных циклов
<1> ;prg_10_5.asm
<2> model small
<3> .stack 100h
<4> .data
<5> mas db 1,0,9,8,0,7,8,0,2 ,0
<6>db 1,0,9,8,0,7,8,0,2 ,0
<"db 1,0,9,8,0780?0
<8>db 10,9,8,078,0.?0
9> db 1,0,9,8,0,7,8,0,2 ,0
10>. code
11>start:
12>movax,@data
13>movds, ax
14>xorax, ax
15>leabx,mas
16>movex, 5
17> cycl_l:
18>push ex
19>xor si , si
Листинг 10.5 f продолжение)
<20>movex, 10
<21> cycl一2:
22>стрbyte ptr[bx+si] ,0
23>jneno zero
24>movbyte ptr[bx+si],0ffh
25>no_zero:
26>inc si
27>loop cycl_2
28>pop ex
29>add bx.10
30>loop cycl_l
31>exit:
32>mov ax,4c00h
33>int 21h;возврат управления операционной системе
34>end start
Ограничение области видимости для меток
Применение команд, описанных в данной главе, предполагает довольно интенсивное использование меток. На определенном этапе работы многие программисты типовые фрагменты кода берут из ранее разработанных программ, реализуя своеобразное фрагментарно-модульное программирование. При этом может оказатьСЯ, что фрагменты кода имеют одинаковые метки. Что делать — пересматривать весь текст? Если не проанализировать код с должным вниманием, потом можно провести много часов за работой с отладчиком или, что еще хуже, программа начнет неправильно работать у заказчика. Поэтому полезными могут оказаться некоторые средства работы с метками, описанные в этом разделе.
Пакет TASM поддерживает директиву LOCALS, разрешающую использовать в программелокальные блоковые метки. Формат директивы:
LOCALS [символ_префикса]
Операнд символ_префикса определяет двухсимвольное имя, которое впоследствии потребуется при автоматическом формировании меток в качестве их первых символов. По умолчанию в качестве символа префикса используется двухсимвольная комбинация @@.
Благодаря механизму локальных блоковых меток можно в пределах одной программы, но в разных блоках, использовать одинаковые метки. В качестве блока здесь понимаются две конструкции — процедуры и фрагменты программы между двумя обычными метками. Структурно вариант с процедурами выглядит так: modelsmall
.data'
LOCALS- префикс локальных меток по умолчанию
prod proc
prod endp
proc2proc
@@ml:
proc2endp
.code
Область видимости локальных блоковых меток также можно ограничить обычными метками. Для примера возьмем фрагмент последней программы и поставим в ней бессмысленные с точки зрения логики, но наглядные в контексте нашего обсуждения локальные блоковые метки:
LOCALS
16>mov ex,5
17> суст_1:
18>@6ml?jmp @@ml
21> cycl_2:
@@mff jmp @@ml
25> no_zero:
@@而1:jmp @@ml
Результат трансляции будет положительным, одноименные локальные блоковые метки @@ml будут интерпретированы транслятором как разные.
Пакет MASM также поддерживает механизм определения локальных блоковых меток, позволяя определить три ближние метки с предопределенными именами: @@, @F и @В. Между этими метками существует связь. Определение метки с именем @@ указывает транслятору на необходимость заменить ее уникальной меткой в форме @@хххх, где хххх — уникальное в пределах текущей программы шестнадцатеричное значение. После того как такая метка определена, на нее можно ссылаться с помощью неизменяемых меток @F и @В:
歸;
Jmp@F ;ссылка на предыдущую метку @@ (наверх)
jmp@B;ссылка на следующую метку @@ (вниз)
歸
Итоги
戀 Язык ассемблера (система команд процессора) имеет довольно полный и гибкий набор средств организации всевозможных переходов как в пределах текущего сегмента кода, так и во внешние сегменты.
При организации безусловных переходов возможны переходы как с потерей (JMP), так и с запоминанием (CALL) информации о точке передачи управления.
綏於 Принцип работы команд условного перехода основан на том, что процессор по результатам выполнения некоторых команд устанавливает определенные флаги в регистре E FLAGS/FLAGS. Команды условного перехода анализируют состояние этих флагов и инициируют передачу управления, исходя из результатов анализа.
殿 Система команд процессора допускает программирование циклов со счетчиком. Для этого используется регистр ECX/CX, в который до входа в цикл должно быть загружено значение счетчика цикла.
Удобство работы с метками в программе можно повысить, если использовать локальные блоковые метки. Такую возможность предоставляют оба пакета ассемблера: TASM и MASM.
Глава 11
Программирование типовых управляющих структур
> Программирование операторов условного перехода if-else
> Программирование операторов выбора switch
爹, Программирование операторов цикла while, do-while и for
Язык ассемблера — язык машинных команд. Он поддерживает лишь базовые механизмы организации программ. В нем отсутствуют управляющие конструкции, естественные для языков высокого уровня. Речь идет о поддержке конструкций типа операторов выбора, организации циклов и т. п. В прошлой главе мы положили начало обсуждению этих вопросов, рассмотрев принципы организации циклов в программах на ассемблере. Цель данной главы — разработать шаблоны управляющих конструкций на ассемблере, аналогичные типовым операторам языка высокого уровня.
Поступим просто — откроем любой учебник по языку С или C++, составим список приведенных в нем управляющих операторов и покажем способы их реализации на ассемблере. Типовой список будет следующим:
операторы выбора:
□ условный оператор if-else;
□ переключатель switch;
операторы цикла:
П цикл с предусловием while;
П цикл с постусловием do-while;
П итерационный цикл for;
趣 операторы continue и break.
Условный оператор if-else
Условный оператор if-else используется для принятия решения о дальнейшем пути исполнения программы. Синтаксис условного оператора в нотации языков С и C++: if (выражение)
оператор_1;
else
оператор_2
Алгоритм работы условного оператора — вычисляется логическое значение выражения: если оно истинно, то выполняется оператор_1, в противном случае — оператор_2.
В общем случае условный оператор может состоять из одного блока if (без блока else):
if (выражение)
оператор_1;
В программе на ассемблере данные варианты условного оператора можно реализовать следующим образом:
;короткий вариант оператора if (выражение) оператор_1;
cmp opl,op2 ;вычисление выражения j ne endif
;...;последовательность команд, соответствующая операторах
endi f:; конец короткого условного оператора
Строго говоря, использование команды СМР при реализации условного оператора не является обязательным. В данном случае она скорее обозначает место вычисления некоторого условия в программе, по результатам которого принимается решение о ветвлении. Вместо данной команды можно использовать любую команду, изменяющую флаг, который будет анализироваться последующим оператором условного перехода. Эти же рассуждения касаются и команды JNE, вместо которой может стоять требуемая в данном вычислительном контексте команда условного перехода: ;полный вариант оператора if (выражение) оператор_1; else оператор_2
cmp opl,op2 ;вычисление выражения
jne elsel
;...;последовательность команд, соответствующая оператор_1
jmp endif elsel:
;...;последовательность команд, соответствующая оператор_2
endif:;конец полного условного оператора
Остается лишь добавить, что приведенные ранее рассуждения о командах СМР и JNE соответствуют также полной форме оператора if.
Оператор выбора switch
Оператор switch (переключатель) в программе на ассемблере можно реализовать несколькими способами. В данной главе мы рассмотрим два наиболее общих и распространенных способа — с использованием команд сравнения и с использованием таблицы. Тем не менее на практике можно встретить и другие способы реализации оператора switch, наиболее полно отражающие условия конкретной решаемой задачи (см., например [8] и некоторые примеры в данной книге).
В нотации языков С и C++ синтаксис оператора switch выглядит так: switch (константное_значение_"выражение") { case константное-выраженйе_1: операнд_1 case константное выражение^З:оп.еранд_2
default:onepaH4_def ault -
>
Простейший способ реализации оператора switch в программе на ассемблере заключается в организации последовательных сравнений с условными переходами. К примеру, необходимо обработать три возможные альтернативы в программе, которые идентифицируются целочисленными значениями 1,2,3("выражение"): ;...формирование кoнcтaнтнoe^знaчeниe_,,выражение"
mov al,константное_значение_"выражение" - стр al,l;проверка первой альтернативы (константное выражение_1=1?)
]е handle_condi tionl ;равно, идем на обработку cmp al,2;проверка второй альтернативы (константное выражение_2=2?)
je handle_condition2 ;равно, идем на обработку cmp al,3;проверка третьей альтернативы (константное выражение_3=3?)
je handle_condition2;равно, идем на обработку
;если condi tionol | 2 | 3 , то производим обработку по умолчанию ;…;последовательность команд для обработки по умолчанию
jmp end_switch;уходим на конец switch
handle__condrt ionl: ;последовательность команд для обработки handle_conditionl
jmp end_switch ;уходим на конец switch handle_condrtion2:
;последовательность команд для обработки handle_condi ti on2
jmp end_switch;уходим на конец switch
handle_condftion3:
;последовательность команд для обработки handle_condition3 end_switch:
;...;продолжение программы
Второй, более элегантный способ реализации конструкции, соответствующей оператору switch, — табличный. Сразу отмечу, что данный материал логичнее рассматривать после изучения главы 13, посвященной сложным структурам данных. Поэтому, если у вас возникнут трудности с пониманием представленного здесь материала, пропустите его и вернитесь к нему позже.
Для реализации рассматриваемого способа в памяти моделируется некоторое подобие таблицы. Каждая строка таблицы состоит из двух ячеек. Строки таблицы располагаются в памяти последовательно — друг за другом. В первой ячейке таблицы располагается значение константное_выражение_п, во второй ячейке — адрес перехода, если выполняется условие константное_значение_" выражение" = конс- тантное_выражение_п. Целью адреса перехода может быть процедура или метка, соответствующая фрагменту кода, обрабатывающему выполнение условия, заданного в заголовке оператора выбора. Реализация табличным способом приведенного ранее примера будет выглядеть так:
.data table_swi tchdb 1
dw'handle_condi tionl db 2
dw handle_condi tion2 db 3
dw handle_condi tion3
.code
; ... формирование константное_значение_"выражение"
mov al,константное_значение_"выражение"
mov bx,offset table_swi tch
mov ex,3;счетчик циклов=количество альтернатив
next condi tion:
emp al,[bx]jne next_jmp word ptr [bx+1]add bx,3next_: loopnext_cond1tionjdefault:-если condi tionol I 2 I 3 , то:…;последовательностьjmphandle_condi tionl:последовательность командjmphandle_condi tion2:последовательность командjmphandle_condi tion3 : последовательность командjmp ...emp al,[bx]jne next_jmp word ptr [bx+1]add bx,3next_: loopnext_cond1tionjdefault:-если condi tionol I 2 I 3 , то:…;последовательностьjmphandle_condi tionl:последовательность командjmphandle_condi tion2:последовательность командjmphandle_condi tion3 : последовательность командjmp ...;проверка очередной альтернативы
emp al,[bx]
jne next_
jmp word ptr [bx+1]
add bx,3
next_: loopnext_cond1tion
jdefault:-
если condi tionol I 2 I 3 , то
:…;последовательность
jmp
handle_condi tionl:
последовательность команд
jmp
handle_condi tion2:
последовательность команд
jmp
handle_condi tion3 : последовательность команд
jmp ...
emp al,[bx]
jne next_
jmp word ptr [bx+1]
add bx,3
next_: loopnext_cond1tion
jdefault:-
если condi tionol I 2 I 3 , то
:…;последовательность
jmp
handle_condi tionl:
последовательность команд
jmp
handle_condi tion2:
последовательность команд
jmp
handle_condi tion3 : последовательность команд
jmp ...
;к следующему элементу
;на обработку совпадения
;адрес следующей строки таблицы -> bx
производим обработку по умолчанию команд для обработки по умолчанию ;уходим, куда нужно
для обработки handle_conditionl
:уходим, куда нужно
для обработки handle_condi tion2
;уходим, куда нужно
для обработки handle__condi tion3
;уходим, куда нужно ;продолжение программы
.datatable_switchdb 1dw handle_condi tionldb 2dw handle_condi tion)db 3dw handle_condition3.datatable_switchdb 1dw handle_condi tionldb 2dw handle_condi tion)db 3dw handle_condition3:адрес процедуры;адрес процедуры;адрес процедуры:адрес процедуры;адрес процедуры;адрес процедурыhandle_conditionlhandle_condi tion2handle_condi tion3handle_conditionlhandle_condi tion2handle_condi tion3Для лучшего структурирования программы последовательность команд ДЛЯ обработки условия handle^conditionN можно оформить в виде процедур:
.data
table_switchdb 1
dw handle_condi tionl
db 2
dw handle_condi tion)
db 3
dw handle_condition3
.data
table_switchdb 1
dw handle_condi tionl
db 2
dw handle_condi tion)
db 3
dw handle_condition3
:адрес процедуры
;адрес процедуры
;адрес процедуры
:адрес процедуры
;адрес процедуры
;адрес процедуры
handle_conditionl
handle_condi tion2
handle_condi tion3
handle_conditionl
handle_condi tion2
handle_condi tion3
. формирование константное_значение_ выражение mov al, кoнcтaнтнoe_знaчeниe_,,выражение"mov bx,offsettable_switch. формирование константное_значение_ выражение mov al, кoнcтaнтнoe_знaчeниe_,,выражение"mov bx,offsettable_switchmov ex, 3 next_condition:cmp al,[bx]jne next_call word ptr [bx+1]jmp end_switchadd bx,3next_: loopnext_condition;default:;если condi ti оп<>11213, тоmov ex, 3 next_condition:cmp al,[bx]jne next_call word ptr [bx+1]jmp end_switchadd bx,3next_: loopnext_condition;default:;если condi ti оп<>11213, то
. формирование константное_значение_ выражение mov al, кoнcтaнтнoe_знaчeниe_,,выражение"
mov bx,offsettable_switch
. формирование константное_значение_ выражение mov al, кoнcтaнтнoe_знaчeниe_,,выражение"
mov bx,offsettable_switch
mov ex, 3 next_condition:
cmp al,[bx]
jne next_
call word ptr [bx+1]
jmp end_switch
add bx,3
next_: loopnext_condition
;default:
;если condi ti оп<>11213, то
mov ex, 3 next_condition:
cmp al,[bx]
jne next_
call word ptr [bx+1]
jmp end_switch
add bx,3
next_: loopnext_condition
;default:
;если condi ti оп<>11213, то
handle_conditionlproc
;...;последовательность команд для обработки handle_conditionl
endp
handle_condi tion2proc
;... ~ ;последовательность команд для обработки handle_condi tion2 endp
handle_condi tion3proc
;... j последовательность команд для обработки handle_condi tion3 endp
.code
;счетчик циклов=количество альтернатив
проверка очередной альтернативы
:k следующему элементу
;на обработку совпадения
;адрес следующей строки таблицы -> bx
производим обработку по умолчанию
;...;последовательность команд для обработки по умолчанию
end_swi tch:
:.~;продолжение программы
Операторы цикла
В предыдущей главе мы рассматривали общую концепцию реализации циклов в программах на языке ассемблера. В данном разделе обсудим способы организации циклов, традиционно поддерживаемых большинством языков высокого уровня. Для полноты изложения нам, возможно, придется повторить некоторые тезисы предыдущей главы.
Оператор цикла с предусловием while
Оператор цикла с предусловием while имеет следующий формат: while (выражение-условие) тело цикла
Для облегчения реализации этого оператора приведем его псевдокод, близкий к нотации ассемблера:
while_begiп:
;вычисление логического значения "выражение-условие"
ЕСЛИ "выражение-условие"=FALSE ТО ПЕРЕЙТИ НА end^while операторы цикла
ПЕРЕЙТИ на while_begi n ; переход на начало очередной итерации цикла
end_wh1le:—; конец цикла
;.二продолжение программы
На ассемблере это может выглядеть так:
while_begin:
qiHp a 1,1; вычисление логического значения "выражение-условие "
jne end_while :ЕСЛИ не равно ТО ПЕРЕЙТИ НА end_while
;что-то делаем - тело цикла
jmp while_begin;переход на начало очередной итерации цикла
end_while:~;конец цикла
;.二;продолжение программы
Операторы continue и break
Прекращение выполнения цикла возможно в двух случаях:
и значение выражение-условие (см. выше) равно FALSE;
имеет место безусловная передача управления за пределы цикла (в программе на языках С и C++ эти действия выполняют операторы break и goto).
В языках С и C++ изменить последовательность выполнения тела цикла можно с помощьюоператораcontinue.
Алгоритм выполнения операторов break, goto и continue прост, они передают управление в определенную точку программы:
卞禽break — сразу за конец цикла;
窩 goto — в произвольное место программы;
流g continue — на начало последнего открытого оператора while, do или for.
В программе на ассемблере естественнее всего реализовать эти операторы с помощью команды безусловного перехода JMP, которая передаст управление в нужную точку программы.
Оператор цикла с постусловием do-while
Продолжим обсуждение операторов цикла различного типа. Очередной — оператор цикла с постусловием do-while. Его формат в нотации языков С и C++: do
тело цикла
while(выражение-условие)
Основная отличительная особенность данного оператора — в том, что операторы, составляющие тело цикла, выполняются хотя бы один раз. Близкий к нотации ассемблера псевдокод реализации этого оператора выглядит так: do_while_begi n: операторы цикла
;вычисление логического значения "выражение-условие "
ЕСЛИ "выражение-условие"=FALSE ТО ПЕРЕЙТИ НА end_while
ПЕРЕЙТИ на do_while_begin:;переход на начало очередной итерации цикла
end_while:; конец цикла
;...;продолжение программы
Оператор итерационного цикла for
Оператор итерационного цикла for — это единственный оператор цикла, хотя бы частично поддерживаемый ассемблером на уровне команд. Его формат в нотации языков С и C++:
for (инициализация цикла; выражение-условие; выражение) тело цикла
С точки зрения грамматики все три выражения в операторе for являются произвольными. Но обычно первое из них (инициализация цикла) предназначено для присвоения начального значения переменной цикла, второе (выражение-условие) вычисляет логическое значение для принятия решения о выходе из цикла, третье (выражение) вычисляет значение переменной цикла на очередной его итерации.
Ассемблер с помощью команды ШОР поддерживает только один вариант организации такого цикла, который был подробно рассмотрен в предыдущей главе. Для реализации произвольного варианта цикла for может оказаться полезным следующий псевдокод:
инициализация переменной цикла
for_begi п:
;вычисление логического значения "выражение-условие "
ЕСЛИ пвыражение-условие"=FAL5E ТО ПЕРЕЙТИ НА end_for операторы цикла
вычисляем выражение - очередное значение переменной цикла
ПЕРЕЙТИ на for_begi п:;переход на начало очередной итерации цикла
end_for:;конец цикла
;.~продолжение программы
Какой цикл эффективнее? Тот, в котором меньше проверок и переходов.
Особенности пакета MASM
Пакет MASM имеет встроенные средства, визуально приближающие написанную программу к программам на языке высокого уровня. Это директивы для формирования конструкций, аналогичных условным и циклическим операторам языков высокого уровня: •IF, .ELSE, .ELSEIF, .ENDIF, .REPEAT, .UNTIL, .WHILE, .ENDW, .BREAK, .CONTINUE. Всем директивам предшествует символ . (точка). Использование данных конструкций можно рекомендовать для повышения надежности кода. Программист, оставаясь на уровне ассемблера, повышая логический уровень контролируемых им синтаксических конструкций, снижает вероятность ошибок логики, ускоряя тем самым весь процесс разработки.
Условная конструкция .IF
Конструкция .IF предназначена для генерации кода, функционально аналогичного условному оператору языков высокого уровня. Ее синтаксис:
.IF условие
предложения блока IF
[.ELSEIF] условие
предложения блока ELSEIF
…[・EL5E]
предложения блока ELSE
.ENDIF
Здесь слова •IF, .ELSEIF, .ELSE, .ENDIF — служебные, аусловие представляет один из следующих операторов:
т onl == оп2 — операнды равны;
滋onl != оп2 — операнды не равны;
皴onl > оп2 — больше;
幾onl >= оп2 — больше или равно;
懸onl < оп2 — меньше;
驟onl <= оп2 — меньше или равно;
勰onl & номер_бита — проверка бита;
撼 ! onl — инверсия (NOT);
娜onl&& оп2 — логическое умножение (AND);
齧 onl IIon2 — логическое сложение (OR);
CARRY?-EFLAGS.CF=1?;
ш OVERFLOW? - EFLAGS.OF=1?;
纖 PARITY? - EFLAGS.PF=1?;
SIGN? -讦 LAGS.SFE?;
^ ZERO? - EFLAGS.ZF=1?.
Эти же условия используются в конструкциях .REPEAT, .WHILE, .BREAK, .CONTINUE (см. далее). Сочетание операндов onl и оп2 должно быть допустимым для команд ассемблера, то есть выражение типа obl_l<obl_2 (память-память) ассемблер не пропустит.
Допускается вложение конструкций .IF. Назначение и использование директив .ELSE и .ELSEIF аналогичны соответствующим конструкциям в языках высокого уровня. Завершающая директива условного блока .ENDIFобязательна.
Интерес для изучения представляет то, как ассемблер преобразует директивы рассматриваемых нами конструкций высокого уровня в машинный код. Для этого необходимо вставить в любую программу, не имеющую синтаксических ошибок, фрагмент, подобный следующему: .if al==0 mov ax,1 .elseif ax==0 movax, 2
.else movax, 3
.endi f
После этого необходимо выполнить компиляцию примера:
ML.EXE /Fl /Zi Prg_6_l.asm
Ключ /Fl предназначен для генерации файла листинга (по умолчанию не создается). Он намеренно вставлен в командную строку для того, чтобы не увидеть замену высокоуровневых директив командами ассемблера в тексте листинга. Для визуализации замены встроенных макросов необходимо загрузить исполняемый модуль в отладчик CodeView, выбрать в окне Source команду Options ► Source Window и в открывшемся окне Source Window Options выбрать режим отображения (Display Mode) Mixed Source and Assembly. Взору предстанет следующий смешанный код: 25: .if al==0 1792:000С 36803Е000100 CMP BYTE PTR SS:[0100].00 1792:0012 7505 JNZ 0019 26: mov ax, 1 1792:0014 B80100MOV AX,0001
.elseif ax==0 1792:0017 EB0C JMP 0025 1792:0019 0BC0 OR AX, AX 1792:001В 7505 JNZ 0022 28: mov ax, 2 1792:®@1D B80200MOV AX,0002
.else 1792:0020 EB03 JMP 0025 30: mov ax , 3 1792:0022 B80300MOV AX,0003
.endi f
На примере команд сравнения видно, как ассемблер выполняет оптимизацию кода. Для приведенного фрагмента программы ассемблер реализовал сравнение в двух вариантах:
ж для переменной al==0 - CMP BYTE PTR ss:[oiooloo;
II для регистра ах==0 — OR AX,AX.
Циклическая конструкция .REPEAT
Конструкция .REPEAT соответствует циклу с постусловием в языках высокого уровня.
Синтаксис конструкции:
.REPEAT
команды ассемблера
.UNTIL условие
Существует и второй тип конструкции .REPEAT:
.REPEAT
команды ассемблера
.UNTILCXZ [условие]
Конструкция .REPEAT заставляет процессор повторно исполнять команды ассемблера пока условие истинно (не равно нулю). Особенностью такого цикла является то, что блок команд ассемблера выполняется хотя бы один раз. Исходя из конкретного условия, ассемблер при формировании его машинной реализации выполняет необходимую оптимизацию. Что именно при этом происходит, можно увидеть, загрузив код в CodeView указанным ранее способом.
Директива .UNTILCXZ может использоваться с проверкой дополнительного условия в виде выражение! ==или выражение! != выражение2, чтобы
генерировать команды ШОРЕ и LOOPNE. Директива .UNTILCXZ выполняет декремент регистра ЕСХ и прекращаетвыполнениециклапри ЕСХ = 0. При отсутствииусловия директива •UNTILCXZ просто генерирует команду LOOP.
Циклическая конструкция .WHILE
Конструкция .WHILE соответствует циклу с предусловием в языках высокого уровня.
Синтаксис конструкции:
.WHILE условие команды ассемблера
.ENDW
Директива .WHILE генерирует последовательность команд выполняющих блок команд ассемблера, если условие истинно. Директива • ENDW завершает блок команд ассемблера. Он заставляет вернуть управление конструкции .WHILE, которая после проверки истинности условия либо заново инициирует выполнение блока команд ассемблера, либо передает управление команде, следующей за .ENDW. Конструкция .WHILE допускает вложенность, причем ассемблер выполняет необходимую оптимизацию для формирования наиболее эффективного кода.
Конструкции .BREAK и .CONTINUE
Конструкции .BREAKи .CONTINUE соответствуют аналогичным конструкциям языков высокого уровня.
Директива .BREAK генерирует код выхода на первую команду, следующую за телом текущего цикла .WHILE или .REPEAT. Ее синтаксис:
.BREAK [.IF условие]
Директива .BREAK позволяет задать дополнительное условие, при выполнении которого будет выполнен выход из цикла независимо от условия основного цикла.
Директива .CONTINUE передает управление на начало текущего цикла .WHILE или .REPEAT. Ее синтаксис:
.CONTINUE [.IF условие]
При необходимости директива .CONTINUE может определять дополнительное условие, при соблюдении которого будет выполнен переход на начало текущего цикла.
Директивы .CONTINUE и .BREAK могут использоваться только внутри циклов .WHILE или .REPEAT.
Комплексный пример
Чтобы проиллюстрировать использование директив MASM для имитации конструкций высокого уровня, рассмотрим пример. Этот пример не является примером «вещи в себе», он призван показать класс задач, которые актуальны при программировании на любом языке. Это задачи контроля информации, поступающей в программу в ходе ее функционирования. Как методически правильно подойти к этому вопросу, не «изобретая каждый раз заново велосипед»? Если вы знакомы с теорией построения трансляторов, то суть проблемы разбора символьных последовательностей и подходы к ее решению для вас не новы. Если же у вас таких знаний нет, то материал данного раздела будет полезен для формирования у вас теоретически грамотного подхода к решению проблемы контроля входных данных ваших программ.
Частью любого компилятора является сканер, задача которого — чтение входного потока символов и выделение в нем синтаксически значимых единиц ипфор- мации. Примером таких единиц могут быть ключевые слова языка, имена переменных, константы и другие синтаксически автономные единицы исходной программы. Сканер распознает их и в зависимости от их типа формирует внутреннее представление этих единиц и информацию о них для дальнейшей синтаксической и семантической обработки. Теоретической основой работы сканера является теория автоматов.
Немного теории. Конечный автомат представляет собой кортеж М = {K, А, Р, S, F}, где:
簪 К — конечное множество состояний;
А — конечный входной алфавит;
幾 Р — множество переходов;
駿 S — начальное состояние;
懇 F — множество конечных (последних) состояний.
Принципиально сканер функционирует так. Он имеет конечное множество состояний К, одно из которых является начальным S, и несколько конечных F. Перед началом обработки символов очередной лексемы сканер находится в состоянии S. По мере считывания очередной литеры лексемы состояние сканера меняется. Эти переходы также заранее определены множеством правил перехода Р. После окончания чтения лексемы автомат должен оказаться в одном из конечных состояний, некоторые из которых могут соответствовать состоянию ошибки. Исходя из того, в каком из состояний оказался сканер, он делает вывод о принадлежности лексемы входному языку. Если лексема ошибочная, то компилятором формируется соответствующее диагностическое сообщение, если же лексема корректная, то она в зависимости от своего типа подвергается дальнейшей обработке.
Рассмотрим пример конечного автомата для распознавания целых десятичных чисел со знаком, считываемых программой с консоли. Хорошую помощь в построении конечных автоматов оказывают формы Бэкуса-Наура и синтаксические
диаграммы. Их мы обсуждали в главе 5. Формы Бэкуса-Наура для построения десятичных чисел со знаком выглядят так:
<десятичное_знаковое_целое>=^<число_без_знака> | +<число_без__знака> | <число_без_знака>
<число_без7знака>=><дес__цифра> | <числ61без_знака><дёс_цйфра>~~
<дес_цйфра>=>011|2|3|4|5|6|7|8|9-
Графически конечный автомат удобно представлять в виде направленного графа. Конечный автомат для десятичного числа со знаком показан на рис. 11.1.
Рис. 11.1. Конечный автомат для десятичного числа со знакомРис. 11.1. Конечный автомат для десятичного числа со знаком
Рис. 11.1. Конечный автомат для десятичного числа со знаком
Рис. 11.1. Конечный автомат для десятичного числа со знаком
По рисунку легко закончить формальное определение конечного автомата для десятичного числа со знаком:
送К= {1,2,3,4};
拠A -{+,-, 0, 1,2,3,4,5,6,7,8,9};
Р = {1->2, 1->3, 1->4, 2->4, 2->3, 3->4, 3>3};
經 S = {!};
粽 F = {4}
Далее приведена программа (листинг 11.1), моделирующая конечный автомат для распознавания десятичных чисел со знаком.
Листинг 11.1. Программа конечного автомата для десятичных чисел со знаком
.model smallмодель памяти
.data: сегмент данных
message db Qdh.Qah/ I am ready t'o receive your variant of signed decimal number (to finish press Enter): $'
message_Invalid__Number db'input is bed, please input your
number again or press Enter. $'
good_luck_messagedb 0dh,0ah,'Your number is: '
input_stringdb 10 dup (2@h) ;строка для ввода 9 цифр и знака
db 0dh,0ah,'Thank you for use this program. I wish you good luck. . .$*
.stack 256h; сегмент стека
.code; сегмент кода
GetMextChar proc
mov ah,1;чтение символа из стандартного ввода
int 21h;символ в al
ret
GetNextChar endp
main mov mov mov lea int xor
procначало процедуры main
ax,@dataзаносим адрес сегмента данных в регистр ах
ds, axах в ds
ah, 9
dx.message
21hвывод сообщения на экран
si ,siподготовка индексного регистра si
call GetNextChar : ввод первого символа в al
.if (al=="+" ||I I al>=30h && al<=39h)
mov input_string[si],al
inc si
.else
jmp bed_end .endif
.while si<9
callGetNextChar ;ввод очередного символа в al
.if (al>=30h && al<=39h)
mov input_string[si] ,al inc si
.continue
.else!f al==0dh
. break
.else
jmp bed_end
.endi f . endw
mov ah ,9
lea dx,good_luck_message
int 21h; вывод сообщения good luck_message на экран
jmp exit_prog
bed_end:
mov ah, 9
lea dx,message_Invalid_Number
int 21h-: вывод сообщения message_Invalid_Number на экран
exi t_prog:一 —
call GetNextChar задержка до ввода любого символа
mov ax ,4c00h; пересыпка 4c00h.в регистр ах
int 21hвызов прерывания с номером 21h
main endpконец процедуры main
end mainконец программы с точкой входа main
Напомню, что для получения пригодного для отладки исполняемого модуля данной программы необходимо сформировать командную строку вида ML.EXE /Fl /Zi P「g_ll_L asm >p
Для отладки используйте отладчик CodeView:
cv. exe Prg_ll_l.asm
В качестве упражнения вы можете потренироваться в программировании конечных автоматов для разбора произвольных символьных строк. После освоения материала главы 17практически полезной может быть программа конечного автомата для разбора чисел с плавающей точкой. Дополнительный материал по программированию конечных автоматов вы найдете в [8].
В заключение данной главы хотелось бы заметить, что приведенные в ней рассуждения о моделировании вариантов типовых циклических конструкций не являются догмой. Их основное назначение — показать возможность и практическую пользу моделирования циклических конструкций в ассемблерной программе. Важность материала данной главы состоит в том, что использование этих конструкций в программах способно, наряду с облегчением написания ассемблерного кода, существенно повысить его надежность.
Итоги
Язык ассемблер не содержит управляющих конструкций, естественных для языков высокого уровня. Это существенно снижает наглядность ассемблерных программ и надежность программирования в целом.
• На практике существует настоятельная необходимость поддержки любым языком операторов выбора, различного типа операторов цикла и некоторых других.
ж В разных пакетах ассемблера реализованы свои «фирменные» подходы к решению данной проблемы:
□ TASM имеет два режима работы: MASM и IDEAL. В режиме MASM все управляющие конструкции нужно моделировать. Этот процесс подробно описан в первой части данной главы. В режиме IDEAL есть встроенные средства для организации типовых управляющих конструкций, но из-за прекращения поддержки TASM фирмой Borland работа в режиме IDEAL теряет актуальность;
D пакет MASM имеет встроенные средства, моделирующие управляющие конструкции языков высокого уровня. Их использование существенно повышает скорость и надежность разработки программ.
纖 Важно понимать, что встроенные средства, моделирующие управляющие конструкции языков высокого уровня — это лишь надстройка компилятора над некоторым базовым ассемблерным уровнем. В конечном итоге, все высокоуровневые конструкции будут переведены в последовательность команд ассемблера и исполнены процессором.
Глава 12
Цепочечные команды
Средства процессора для обработки цепочек элементов в памяти
Операции пересылки и сравнения цепочек
Операции для работы с отдельными элементами цепочек
勢. Операции для работы с портами ввода-вывода
В данной главе будет рассмотрена чрезвычайно интересная группа команд, понимание принципов работы которых и их грамотное использование способны значительно облегчить жизнь программисту, пишущему программы на языке ассемблера. Речь идет о так называемых цепочечных командах, которые иногда называют командами обработки строк символов. Однако эти названия не идентичны. Под строкой символов здесь понимается последовательность байтов, а цепочка — это более общее название для случаев, когда размер элемента последовательности превышает байт и составляет слово или двойное слово. Таким образом, цепочечные команды позволяют проводить действия над блоками памяти, представляющими собой последовательности элементов следующего размера:
歎 8 битов, то есть байт;
纖 16 битов, то есть слово;
' 32 бита, то есть двойное слово.
Содержимое этих блоков для процессора не имеет никакого значения. Это могут быть символы, числа и все что угодно. Главное, чтобы размеры элементов соответствовали одному из перечисленных ранее вариантов и эти элементы находились в соседних ячейках памяти.
Всего в системе команд процессора поддерживаются семь операций-примитивов обработки цепочек. Каждая из них реализуется в процессоре тремя командами,
в свою очередь, каждая из этих команд работает с соответствующим размером элемента — байтом, словом или двойным словом. Особенность всех цепочечных команд в том, что они, кроме обработки текущего элемента цепочки, корректируют содержимое определенных регистров с тем, чтобы автоматически продвинуться к следующему элементу цепочки.
Перечислим операции-примитивы обработки цепочек и реализующие их команды ассемблера.
Пересылка цепочки:
D MOVS адрес_приемника,адрес_источника;
口 MOVSB;
D MOVSW;
D MOVSD.
ж Сравнение цепочек:
П CM PS адрес_приемникаладрес_источника;
CMPSB;
D CMPSW;
а CMPSD.
ж Сканирование цепочки:
D SCAS адрес_приемника;
D SCASB;
D SCASW;
D SCASD.
ж Загрузка элемента из цепочки:
П LODS адрес_источника;
LODSB;
П LODSW;
П LODSD.
綴 Сохранение элемента в цепочке:
П STOS адрес„приемника;
П STOSB;
П STOSW;
П STOSD.
緻i Получение элементов цепочки из порта ввода-вывода:
П INS адрес_приемника,номер_порта;
П INSB;
П INSW;
П INSD.
趣 Вывод элементов цепочки в порт ввода-вывода:
П OUTS номер_порта,адрес_источника;
OUTBS;
OUTWS;
OUTDS.
Логически к этим командам нужно отнести и так называемые префиксы повторения. Вспомните формат машинной команды и его первые необязательные байты префиксов. Один из возможных типов префиксов — это префиксы повторения. Они предназначены для использования цепочечными командами. Префиксы повторения имеют свои мнемонические обозначения:
REP;
- REPE, или REPZ;
漆 REPNE, или REPNZ.
Эти префиксы повторения указываются перед нужной цепочечной командой в поле метки. Цепочечная команда без префикса выполняется один раз. Размещение префикса перед цепочечной командой заставляет ее выполняться в цикле. Различия приведенных префиксов — в основании, по которому принимается решение о циклическом выполнении цепочечной команды: по состоянию регистра ЕСХ/СХ или по флагу нуля ZF.
後 Префикс повторения REP (REPeat) используется с командами, реализующими операции-примитивы пересылки и сохранения элементов цепочек, — соответственно, MOVS и STOS. Префикс REP заставляет данные команды выполняться, пока содержимое в ЕСХ/СХ не станет равным 0. При этом цепочечная команда, перед которой стоит префикс, автоматически уменьшает содержимое ЕСХ/СХ на единицу. Та же команда, но без префикса, этого не делает.
緩 Префиксы повторения REPE (REPeat while Equal) и REPZ (REPeat while Zero) являются абсолютными синонимами. Они заставляют цепочечную команду выполняться до тех пор, пока содержимое ЕСХ/сх не равно 0 или флаг ZF равен 1. Как только одно из этих условий нарушается, управление передается следующей команде программы. Благодаря возможности анализа флага ZF наиболее эффективно эти префиксы можно использовать с командами CM PS и SCAS для поиска различающихся элементов цепочек.
総 Префиксы повторения REPNE (REPeat while Not Equal) и REPNZ (REPeat while Not Zero) также являются абсолютными синонимами. Их действие на цепочечную команду несколько отличается от действий префиксов REPE/REPZ. Префиксы REPNE/REPNZ заставляют цепочечную команду циклически выполняться до тех пор, пока содержимое ЕСХ/СХ не равно нулю или флаг ZF равен нулю. При нарушении одного из этих условий работа команды прекращается. Данные префиксы также можно использовать с командами CM PS и SCAS, но для поиска совпадающих элементов цепочек.
Следующий важный момент, связанный с цепочечными командами, заключается в особенностях формирования физического адреса операндов адрес_источни- ка и адрес_приемника. Цепочка-источник, адресуемая операндом адрес_источника, может находиться в текущем сегменте данных, определяемом регистром DS. Цепочка-приемник, адресуемаяоперандомадрес_приемника,должнабыть вдополни- тельном сегменте данных, адресуемом сегментным регистром ES. Важно отметить,
что допускается замена (с помощью префикса замены сегмента) только регистра DS, регистр ES подменять нельзя. Вторые части адресов (смещения цепочек) также находятся в строго определенных местах. Для цепочки-источника это регистр ESI/SI (Source Index register — индексный регистр источника). Для цепочки-получателя это регистр EDI/DI (Destination Index register — индексный регистр приемника). Таким образом, полные физические адреса для операндов цепочечных команд следующие:
缄 адрес_источника — пара ds:esi/si;
• адрес_приемника — параes:edi/di.
Кстати, вспомните команды LDS и LES, которые мы рассматривали в главе 7. Эти команды позволяют получить полный указатель (сегмент плюс смещение) на ячейку памяти. Применение их в данном случае очень удобно в силу жесткой регламентации использования регистров для адресации операндов источника и приемника в цепочечных командах.
Вы, наверное, обратили внимание на то, что все семь групп команд, реализующих цепочечные операции-примитивы, имеют похожий по структуре набор команд. В каждом из этих наборов присутствуют одна команда с явным указанием операндов и три команды, не имеющие операндов. На самом деле набор команд процессора имеет соответствующие машинные команды только для цепочечных команд ассемблера без операндов. Команды с операндами транслятор ассемблера задействует только для определения типов операндов. После того как выяснен тип элементов цепочек по их описанию в памяти, генерируется одна из трех машинных команд для каждой из цепочечных операций. По этой причине все регистры, содержащие адреса цепочек, должны быть инициализированы заранее, в том числе и для команд, допускающих явное указание операндов. В силу того, что цепочки адресуются однозначно, нет особого смысла применять команды с операндами. Главное, что вы должны запомнить, — правильная загрузка регистров указателями обязательно требуется до выдачи любой цепочечной команды. Также на практике могут встретиться случаи, когда транслятор потребует явно переопределить сегмент ES: для соответствующего операнда в команде с явным указанием операндов.
Последний важный момент, касающийся всех цепочечных команд, — это направление обработки цепочки. Есть две возможности:
掇 от начала цепочки к ее концу, то есть в направлении возрастания адресов;
от конца цепочки к началу, то есть в направлении убывания адресов.
Как мы увидим позже, цепочечные команды сами выполняют модификацию регистров, адресующих операнды, обеспечивая тем самым автоматическое продвижение по цепочке. Количество байтов, на которые эта модификация осуществляется, определяется кодом команды. А вот знак этой модификации определяется значением флага направления DF (Direction Flag) в регистре EFLAGS/FLAGS:
т если DF = 0, то значения индексных регистров ESI/SI и EDI/DI будут автоматически увеличиваться (операция инкремента) цепочечными командами, то есть обработка будет осуществляться в направлении возрастания адресов;
в если DF = 1, то значения индексных регистров ESI/SI и EDI/DI будут автоматически уменьшаться (операция декремента) цепочечными.командами, то есть обработка будет идти в направлении убывания адресов.
Состоянием флага DF можно управлять с помощью двух команд, не имеющих операндов:
CLD (Clear Direction Flag) — очистить флаг направления (команда сбрасывает флаг направления DF в 0);
緩STD (Set Direction Flag) — установить флаг направления (команда устанавливает флаг направления DF в 1).
Это вся информация, касающаяся общих свойств цепочечных команд. Далее мы более подробно рассмотрим каждую операцию-примитив и команды, которые ее реализуют. При этом одну из команд в каждой группе цепочечных команд (команду с операндами) мы будем рассматривать подробнее, поскольку это более общая команда в смысле ограничений, накладываемых на типы операндов.
Пересылка цепочек
Команды, реализующие операцию-примитив пересылки цепочек, производят копирование элементов из одной области памяти (цепочки) в другую. Размер элемента определяется применяемой командой. Ассемблер предоставляет в распоряжение программиста четыре команды, работающие с разными размерами элементов цепочки: 綏 MOVS адрес_приемника,адрес_источника — переслать цепочку (MOVe String);
翔 MOVSB — переслать цепочку байтов (MOVe String Byte);
縱 MOVSW — переслать цепочку слов (MOVe String Word);
嚮 MOVSD — переслать цепочку двойных слов (MOVe String Double word).
Команда MOVS
Синтаксис команды MOVS:
movs адрес_приемника,адрес_источника
Команда копирует байт, слово или двойное слово из цепочки, адресуемой операндом адрес_источника, в цепочку, адресуемую операндом адрес_приемника. Размер пересылаемых элементов ассемблер определяет, исходя из атрибутов идентификаторов, указывающих на области памяти приемника и источника. К примеру, если эти идентификаторы были определены директивой DB, то пересылаться будут байты, если идентификаторы были определены с помощью директивы DD, то пересылке подлежат 32-разрядные элементы, то есть двойные слова. Ранее уже было отмечено, что для цепочечных команд с операндами, к которым относится и команда пересылки movs адрес_приемника,адрес„источника, не существует машинного аналога. При трансляции в зависимости от типа операндов транслятор преобразует ее в одну из трех машинных команд: MOVSB, MOVSW или MOVSD.
Сама по себе команда MOVS пересылает только один элемент, исходя из его типа, и модифицирует значения регистров ESI/SI и EDI/DI. Если перед командой написать префикс REP, то одной командой можно переслать до 64 Кбайт данных (если
размер адреса в сегменте 16 бит — usel6) или до 4 Гбайт данных (если размер адреса в сегменте 32 бита — use32). Число пересылаемых элементов должно быть загружено в счетчик — регистр СХ (usel6) или ЕСХ (use32). Перечислим последовательность действий, которые нужно выполнить в программе для того, чтобы переслать цепочку элементов из одной области памяти в другую с помощью команды MOVS. В общем случае эту последовательность можно рассматривать как типовую для выполнения любой цепочечной команды.
Установить значение флага DF в зависимости от того, в каком направлении будут обрабатываться элементы цепочки — в направлении возрастания или убывания адресов.
Загрузить указатели на адреса цепочек в памяти в пары регистров DS:(E)SI и ES: (E)DL-
Загрузить в регистр ЕСХ/СХ количество обрабатываемых элементов.
Выдать команду MOVS с префиксом REP.
На примере листинга 12.1 рассмотрим, как эти действия реализуются программно. В этой программе производится пересылка символов из одной строки в другую. Строки находятся в одном сегменте памяти. Для пересылки используется команда-примитив MOVS с префиксом повторения REP.
Листинг12.1- Пересылка строк командой MOVS
;prg_12-l・asm masm-"modelsmallstack 256.datasource db "Тестируемая строка",* $ destdb 19 dup ("").codeassume ds:@data,es:@datamain:mov ax,©data mov ds, axmov es. ax cldlea si ,sourcelea di ,destmov ex, 20repmovs dest,sourcelea dx,dest mov ah,09h int 21hexit:mov ax,4c00hint 21hendmain;prg_12-l・asm masm-"modelsmallstack 256.datasource db "Тестируемая строка",* $ destdb 19 dup ("").codeassume ds:@data,es:@datamain:mov ax,©data mov ds, axmov es. ax cldlea si ,sourcelea di ,destmov ex, 20repmovs dest,sourcelea dx,dest mov ah,09h int 21hexit:mov ax,4c00hint 21hendmain;строка-источник ;строка-приемник;строка-источник ;строка-приемник;точка входа в программу;загрузка сегментных регистров ;настройка регистров DS и ES ;на адрес сегмента данных;точка входа в программу;загрузка сегментных регистров ;настройка регистров DS и ES ;на адрес сегмента данных;сброс флага DF - обработкастроки от начала к концу;за「рузка в si смещения;строки-источника;загрузка в DS смещения строки-приемника;для префикса rep - счетчик;повторений (длина строки);пересылка строки;вывод на экран строки-приемника;сброс флага DF - обработкастроки от начала к концу;за「рузка в si смещения;строки-источника;загрузка в DS смещения строки-приемника;для префикса rep - счетчик;повторений (длина строки);пересылка строки;вывод на экран строки-приемника
;prg_12-l・asm masm-"
modelsmall
stack 256
.data
source db "Тестируемая строка",* $ destdb 19 dup ("")
.code
assume ds:@data,es:@data
main:
mov ax,©data mov ds, ax
mov es. ax cld
lea si ,source
lea di ,dest
mov ex, 20
repmovs dest,source
lea dx,dest mov ah,09h int 21h
exit:
mov ax,4c00h
int 21h
endmain
;prg_12-l・asm masm-"
modelsmall
stack 256
.data
source db "Тестируемая строка",* $ destdb 19 dup ("")
.code
assume ds:@data,es:@data
main:
mov ax,©data mov ds, ax
mov es. ax cld
lea si ,source
lea di ,dest
mov ex, 20
repmovs dest,source
lea dx,dest mov ah,09h int 21h
exit:
mov ax,4c00h
int 21h
endmain
;строка-источник ;строка-приемник
;строка-источник ;строка-приемник
;точка входа в программу
;загрузка сегментных регистров ;настройка регистров DS и ES ;на адрес сегмента данных
;точка входа в программу
;загрузка сегментных регистров ;настройка регистров DS и ES ;на адрес сегмента данных
;сброс флага DF - обработка
строки от начала к концу
;за「рузка в si смещения
;строки-источника
;загрузка в DS смещения строки-приемника
;для префикса rep - счетчик
;повторений (длина строки)
;пересылка строки
;вывод на экран строки-приемника
;сброс флага DF - обработка
строки от начала к концу
;за「рузка в si смещения
;строки-источника
;загрузка в DS смещения строки-приемника
;для префикса rep - счетчик
;повторений (длина строки)
;пересылка строки
;вывод на экран строки-приемника
Пересылка байтов, слов и двойных слов
Пересылка байтов, слов и двойных слов производится командами MOVSB, MOVSW и MOVSD. Единственной отличительной особенностью этих команд от команды movs
является то, что последняя может работать с элементами цепочек любого размера — 8,16 или 32 бита. При трансляции команда MOVS преобразуется в одну из трех команд: MOVSB, MOVSW или MOVSD. Ранее было показано, что решение о том, в какую конкретно команду будет произведено преобразование, принимается транслятором, исходя из размеров элементов цепочек, адреса которых указаны в качестве операндов команды MOVS. Что касается адресов цепочек, то для любой из четырех команд они должны формироваться программой явно и заранее в регистрах ESI/SI и EDI/DI.
К примеру, посмотрим, как изменится программа из листинга 12.1 при использовании команды MOVSB:
.data
source
db "Пересыпаемая строка$" ;строка-источник
dest
.code
db 20 DUP (?)
;строка-приемник
ASSUME main:
ds
:@data,es:@data
…cld
;сброс флага DF — просмотр строки от начала к концу
lea
si,source
;загрузка в ES строки-источника
lea
di,dest
;загрузка в DS строки-приемника
mov
ex, 20
;для префикса гер - длина строки
rep movsb
;пересылка строки
Как видим, изменилась только строка с командой пересылки. Отличие в том, что программа из листинга 12.1 может работать с цепочками элементов любой из трех размерностей: 8,16 или 32 бита, а последний фрагмент — только с цепочками байтов. Далее, как мы и договорились раньше, чтобы не загромождать описания, мы будем рассматривать группы команд для операций-примитивов только на примере более общей команды, а вы будете понимать, что на самом деле можно использовать любую из трех команд в соответствующем контексте.
Сравнение цепочек
Команды, реализующие операцию-примитив сравнения цепочек, производят сравнение элементов цепочки-источника с элементами цепочки-приемника. Здесь ситуация с набором команд и методами работы с ними аналогична операции-примитиву пересылки цепочек. TASM предоставляет программисту четыре команды сравнения цепочек, работающие с разными размерами элементов цепочки:
CMPS адрес_приемника,адрес_источника — сравнить строки (CoMPare String);
CMPSB — сравнить строку байтов (CoMPare String Byte);
CMPSW — сравнить строку слов (CoMPare String Word);
CMPSD — сравнить строку двойных слов (CoMPare String Double word).
Команда CMPS
Синтаксис команды CM PS:
cmps адрес_приемника,адрес_источника
Здесь:
風 адрес_источника определяет адрес цепочки-источника в сегменте данных, заранее загружаемый в пару регистров DS: ESI/SI;
级 адрес_приемника определяет адрес цепочки-приемника, которая должна находиться в дополнительном сегменте, заранее загружаемый в пару регистров ES:EDI/DI.
Алгоритм работы команды CMPS заключается в последовательном выполнении вычитания (элемент цепочки-источника минус элемент цепочки-получателя) над очередными элементами обеих цепочек. Принцип выполнения вычитания командой CMPS такой же, как у команды сравнения CMP. Она так же, как и CMP, производит вычитание элементов, не записывая при этом результата, и устанавливает флаги ZF, SF и OF. После вычитания очередных элементов цепочек командой CMPS индексные регистры ESI/SI и EDI/DI автоматически изменяются в соответствии со значением флага DF на значение, равное размеру элемента сравниваемых цепочек. Чтобы заставить команду CMPS выполняться несколько раз, то есть произвести последовательное сравнение элементов цепочек, необходимо перед командой CMPS определить префикс повторения. С командой CMPS можно использовать префиксы no: вторения REP, REPE/REPZ или REPNE/REPNZ:
錢笠 REP — сравнивать элементы цепочек, пока ЕСХ/СХ>0;
II REPE или REPZ — сравнивать элементы цепочек до выполнения одного из двух условий:
□ содержимое ЕСХ/СХ равно нулю;
о в цепочках встретились разные элементы (флаг ZF стал равен нулю);
滋 REPNE или REPNZ — сравнивать элементы цепочек до выполнения одного из двух условий:
П содержимое ЕСХ/СХ равно нулю;
п в цепочках встретились одинаковые элементы (флаг ZF стал равен единице).
Таким образом, выбор подходящего префикса позволяет организовать более гибкий поиск одинаковых или различающихся элементов цепочек командой CMPS. Критерий для выбора префикса — условие выхода из цикла. Для определения конкретного условия наиболее подходящим является способ, использующий команду условного перехода JCXZ. Ее работа заключается в анализе содержимого регистра ЕСХ/СХ, и если оно равно нулю, то управление передается на метку, указанную в качестве операнда JCXZ. Так как в регистре ЕСХ/СХ содержится счетчик повторений для цепочечной команды, имеющей любой из префиксов повторения, то, анализируя ЕСХ/СХ, можно определить условия выхода из цикла цепочечной команды. Если значение в ЕСХ/СХ не равно нулю, то это означает, что выход произошел по причине совпадения либо несовпадения очередных элементов цепочек. Существует возможность еще больше конкретизировать информацию об условии завершения операции сравнения. Сделать это можно с помощью команд условной передачи управления (табл. 12.1).
Таблица 12.1. Соответствие команд условной передачи управления условиям завершения команды CMPS
Условие завершения сравнения
Соответствующая команда условного перехода
операнд^источник > операнд_приемник
JA или JG (операнд со знаком)
операнд_источник =операнд_приемник|
JE (в том числе для операнда со знаком)
операнд_источник <>операнд_приемник
JNE или JNZ (в том числе для операнда со знаком)
операнд^источник < операнд_приемник |
JB или JL (операнд со знаком)
операнд_источник <= операнд_приемник
JBE или JLE (операнд со знаком)
операнд источник >= операнд приемник
JAE или JGE (операнд со знаком)
Как определить местоположение очередных совпавших или несовпавших элементов в цепочках? Вспомните, что после каждой итерации цепочечная команда автоматически осуществляет инкремент-декремент значения адреса в соответствующих индексных регистрах. Поэтому после выхода из цикла в этих регистрах будут находиться адреса элементов, находящихся в цепочке после (!) элементов, послуживших причиной выхода из цикла. Для получения истинного адреса этих элементов необходимо скорректировать содержимое индексных регистров, увеличив либо уменьшив значение в них на длину элемента цепочки.
В качестве примера рассмотрим программу из листинга 12.2, в которой сравнивается две строки, находящиеся в одном сегменте. Используется команда CMPS. Префикс повторения — REPE.
Листинг 12.2. Сравнение двух строк командой CMPS
<l>:prg_12_2.asm
<2>MODEL small
<3>STACK256
<4>.data
<5>matchdb 0ah,0dh, 'Сравнение закончено '/S'
<6>faileddb 0ah(0dh,'Строки не совпадают * ,'$'
<7>stringl db •*0123456789' ,0ah,0dh, '$';исследуемые строки
<8>string2 db ”912340678®' J$‘
<10>A5SUME<ll>main:<12>mov<13>mov<14>mov<10>A5SUME<ll>main:<12>mov<13>mov<14>mov;привязка DS и ES к сегменту данных;загрузка сегментных регистров;настройка ES на DS stringl и string2;привязка DS и ES к сегменту данных;загрузка сегментных регистров;настройка ES на DS stringl и string2<9>.code
<10>A5SUME
<ll>main:
<12>mov
<13>mov
<14>mov
<10>A5SUME
<ll>main:
<12>mov
<13>mov
<14>mov
;привязка DS и ES к сегменту данных
;загрузка сегментных регистров
;настройка ES на DS stringl и string2
;привязка DS и ES к сегменту данных
;загрузка сегментных регистров
;настройка ES на DS stringl и string2
ds:@data,es:@data
ax,@data
d£,ax es, ax
<15>;вывод на экран исходных строк
<16>movah,09h
<17>leadx.stringl
<18>int21h
<19>lea dx,string2
<20>int21h
<21>;сброс флага DF - сравнение в направлении возрастания адресов
<22>cld
<23>leasi .stringl;загрузка в si смещения stringl
<24>lea di,string?;загрузка в di смещения string2
<25>mov ex,10:длина строки для префикса repe
<26>;сравнение строк (пока сравниваемые элементы строк равны)
продолжение £к
9 Зак. 256
Листинг 12.2 (продолжение)
2 7 >;выход при обнаружении несовпавшего элемента
<28>сус1:
29> j cxz end_hand ;для последнего элемента
<30>гере cnips stri ngl, stri ng2
31> je end^hand ;для последнего элемента
32>;не совпали
33>mov ah,09h
34>lea dx,failed
35>int. 21h; вывод сообщения
36>;теперь, чтобы обработать несовпавший элемент в строке, ;необходимо уменьшить значения регистров si и di
37>dec si
38>dec di
39>;сейчас в ds:si и es:di адреса несовпавших элементов
4®>;здесь вставить код по обработке несовпавшего элемента
<41>;после этого продолжить поиск в строке:
<42>incsi
<43>incdi
<44>jmpcycl
<45>enci hand:
<46> mov
ah,®9h
;вывод сообщения
<47>lea
dx.match
<48>int.
21h
<49>exi t:
;выход
<5Q>mov
ax,4cO0h
< 51 >i 门 t
2Ih
<52>end main
;конец
программы
В программе есть несколько требующих пояснений моментов. Это, во-первых,
строки 37 и 38, в которых мы скорректгцюиали адреса очередных элементов для получения адресов иесовпавших элементов. Необходимо понимать, что если сравниваются цепочки с элементами слов или двойных слов, то нужно корректировать содержимое регистров ESI/SI и EDI/DI на 2 п. 4 байта соответственно. Во~вто))ых, это строки 42 (I 43. Смысл их в том, что для просмотра оставшейся часта строк необходимо установить указатели на следующие элементы строк за последними нссовпавшпмм элементами. После этого можно повторить весь процесс просмотра и обработки нссовпавпшх элементов в осташппхся частях строк. В-третьих, это строки28-31,с помощью которых учитываются особенности самих строк. Особое
вшшание следует уделить обработке последних элементов строк в предположе-
нии, что они могут не совпасть. Предлагаю вам проследить за выполнением программы в отладчике на следующих наборах входных данных:
stringl = '0123456789' п string2 = '0123406789':
stringl = '0123456789' и string2 = '9123406780\
Сравнение байтов, слов и двойных слов
Гак же как п в группе команд пе])сс.ылки цепочки, в группе команд сравнения есть отдельные команды для сравнения цепочек байтов, слов, двойных слов —CMPSB, CMPSW и CMPSD соответственно. Для этих команд все рассуждения аналогичны тем, что были приведены при описании команд пересылки. Ассемблер преобразует команду CMPS в O/Uiy из машинных команд, CMPSB, CMPSW или CMPSD, в зависимости от размера элементов сравниваемых цепочек.
Сканированиецепочек
Команды, реализующие операцию-примитив сцсаы ирования цепочек, производят поиск некоторого значения в области памяти. Логически эта область памяти рассматривается как последовательность (цепочка) :)лсмент(^в фиксированной длины размером 8, 16 или 32 бита. Искомое значение предварительно должно быть помещено в один из регистров AL/AX/EAX. Выбор конкретного регистра из этлх трех должен быть согласован с размером ЗЛСМ6НТ0В цепочки, в которой осуществляется поиск. Система команд процессора предоставляет программистучетыре команды сканирования цепочки. Выбор конкретной команды определяется размером элемента:
SCAS адрес_приемника — сканировать цепочку (SCAning String);
SCASB — сканировать цепочку байтов (SCAning String Byte);
ж SCASW — сканировать цепочку слов (SCAning String Word);
Я SCASD — сканировать цепочку двойных слов (SCAning String Double Word).
Команда SCAS
Синтаксис команды SCAS:
seas адрес_приемника
Команда имеет один операнд, обозначающий местонахождение цепочки в Дополнительном сегменте (адрес цепочки должен быть заранее сформирован в рсги- страх ES:EDI/DI). Транслятор анализирует тип идентификатора адрес_приемника. который обозначает цепочку в сегменте данных, и формирует одну из трех машинных команд, SCASB, SCASW или SCASD. Условие поиска для каждой из этих трех команд находится в строго определенном месте. Так, если цепочка описана с помощью директивы DB, то искомый элемент должен быть байтом, находиться в регистре AL, исканированиецепочки осуществляетсякомандой SCASB. Еслицепочкаописана с помощью директивы DW, то это — слово в регистре АХ, и поиск ведется командой SCASW. Если цепочка описана с помощью директивы DD, то это — двойное слово в ЕАХ, и поиск ведется командой SCASD. Принцип поиска тот же, что и в команде сравнения CMPS, то есть последовательное выполнение вычитания (содержимое регистра аккумулятора минус содержимое очередного элемента цепочки). В зависимости от результатов вычитания производится установка флагов, при этом сами операнды не изменяются. Так же как и в случае команды CMPS, с командой SCAS удобно использовать префиксы REPE/REPZ или REPNE/REPNZ.
S REPE или REPZ — если нужно организовать поиск до тех пор, пока не будет выполнено одно из двух условий:
будет достигнут конец цепочки (содержимое ЕСХ/СХ равно 0);
в цепочке встретится элемент, отличный от элемента в регистре AL/AX/EAX.
til REPNE или REPNZ — если нужно организовать поиск до тех пор, пока не будет выполнено одно из двух условий:
П будет достигнут конец цепочки (содержимое ЕСХ/СХ равно 0);
П в цепочке встретится элемент, совпадающий с элементом в регистре AL/AX/EAX.
Таким образом, команда SCAS с префиксом REPE/REPZ позволяет найти элемент цепочки, отличающийся по значению от заданного в аккумуляторе. Команда SCAS с префиксом REPNE/REPNZ позволяет найти элемент цепочки, совпадающий по значению с элементом в аккумуляторе. В качестве примера рассмотрим листинг 12.3, в котором реализован поиск символа в строке. В программе используется команда-примитив SCAS. Символ задается явно (строка 20). Префикс повторения — REPNE.
Листинг 12.3.Поиск символа в строке командой SCAS
;p「g_12_3.3$m
MASM~ 一
MODELsmall
STACK256
.data.
тексты сообщений
fnd db Oah.Qdh.'Символ найден!
nochar db 0ah,0dh,'Символ не найден
;строка для поиска
string db "Поиск символа в этой строке.",0ah,0dh,* $ .code
ASSUME ds:@data,es:@data
main:
mov ax,@data
mov ds,ax
mov es, ax
;настройка ES на DS
mov ah,09h lea dx,string int 21h
;вывод сообщения string
mov al,'a'
;символ для поиска - "а" (кириллица)
;сброс флага df
;загрузка в es:di смещения строки
cld lea di.string
mov ex, 28
;АЛЯ префикса repne - длина строки
;поиск в строке (пока искомый символ и символ в строке не совпадут)
;выход при первом совпадении
repne seas string
je found;если равны - переход на обработку,
failed:;иначе - выполняем некоторые действия
;вывод сообщения о том, что символ не найден
mov ah,09h
lea dx,nochar.
int 21h:вывод сообщенияnochar
jmp exit;на выход
found:;совпали
mov ah,09h
lea dx,fnd
int 21h;вывод сообщения fnd
;теперь, чтобы узнать место, где совпал элемент в строке,
;необходимо уменьшить значение в регистре di и вставить нужный обработчик
;dec di
;... вставьте обработчик
exit:;выход
mov ax,4c00h
int 21h
end main
Сканирование строки байтов, слов, двойных слов
Система команд процессора, так же как в случае операций-примитивов пересылки и сравнения, предоставляет команды сканирования, явно указывающие размер элемента цепочки — SCASB, SCASW или SCASD. Помните, что даже если вы этого не делаете, то ассемблер все равно преобразует команду SCAS в одну из этих трех машинных команд.
Загрузка элемента цепочки в аккумулятор
Операция-примитив загрузки элемента цепочки в аккумулятор позволяет извлечь элементцепочки и поместить его врегистр-аккумуляторAL, АХили ЕАХ. Эту операцию удобно использовать вместе с поиском (сканированием) с тем, чтобы, найдя нужный элемент, извлечь его (например, для изменения). Возможный размер извлекаемого элемента определяется применяемой командой. Программист может использовать четыре команды загрузки элемента цепочки в аккумулятор, работающие с элементами разного размера:
т LODS адрес_источника — загрузить элемент из цепочки (LOaD String) в регистр- аккумулятор AL/AX/EAX;
缠 LODSB — загрузить байт из цепочки (LOaD String Byte) в регистр AL;
* LODSW — загрузить слово из цепочки (LOaD String Word) в регистр АХ;
緻 LODSD — загрузить двойное слово (LOaD String Double Word) из цепочки в регистр ЕАХ.
Рассмотрим работу этих команд на примере команды LODS.
КомандаLODS
Синтаксис команды LODS:
lod адрес_источника
Команда имеет один операнд, обозначающий строку в основном сегменте данных. Работа команды заключается в том, чтобы извлечь элемент из цепочки по адресу, соответствующему содержимому пары регистров DS: ESI/SI, и поместить его в регистр EAX/AX/AL. При этом содержимое ESI/SI подвергается инкременту или декременту (в зависимости от состояния флага DF) на величину, равную размеру элемента. Эту команду удобно использовать после команды SCAS, локализующей местоположение искомого элемента в цепочке. Префикс повторения в этой команде может и не понадобиться — все зависит от логики программы.
В качестве примера рассмотрим листинг 12.4, в котором командой CMPS сравниваются две цепочки байтов в памяти (stringl и stringZ) и первый не совпавший байт из stringZ помещается в регистр AL. Для загрузки этого байта в регистр-аккумулятор AL используется команда LODS. Префикса повторения в команде LODS нет, так как он попросту не нужен.
Листинг12.4.Использование команды LODS для загрузки байта в регистр AL
MASMMODEL STACK .data ;строки stringl string2 mes_eq fnd db.codeMASMMODEL STACK .data ;строки stringl string2 mes_eq fnd db.code:prg_ll_4.asm
MASM
MODEL STACK .data ;строки stringl string2 mes_eq fnd db
.code
MASM
MODEL STACK .data ;строки stringl string2 mes_eq fnd db
.code
small
256
для сравнения
db "Поиск символа в этой строке.",0ah,0dh,
db "Поиск символа не в этой строке.",0ah,0dh,' $
db "Строки совпадают.",6ah t0dh,1 $1
"Несовпавший элемент в регистре al'^Oah^dh,
;привязка ds и es к сегменту данных
assume ds:@data,es:@data
262Глава 12. Цепочечные команды
Листинг 12Л(продолжение)
та i门:
mov
ax,@data; загрузка сегментных
регистрсь
mov
ds, ax
mov
us, ax
.настройка rg не
d*
mov
ah,®9h
lea
dx,string!
int
21h
;ВЫВОД S U' 11"!g J.
lea
dz,s t「i ng2
int
21h
вывод str-i ng2
cld
сброс флага df
lea
a i , s t Li [1 % 1
загрузка в e $ : d
i смещения
строки
s t r 1 n g 1
lea
si . str 1ng2
загрузка в Ci 5 : 5
i смещения
строки
stri ng2
mov
「x.29
для префикса repe -- длина
строки
поиск в строке 『пока нужный символ и символ в строке не равны)
;выход 一 при iiepRGM не совпавшем
repe 匸呻5 st.ri ngl, stri ng?
j cxz eqlесли равны - переход на eql
jmp no__eq: если не равны - переход на no_eq
eql. :выводим сообщение о совпадении строк
mov ah,@9匕
lea dx,me$_eq
int 2 th一; вывод сообщения mes_eq
j m p exit;на выход
no 一附:: обработка несовпадения элементов
mov ah , @9h
lea dx,fnd
int 21hвывод сообщения fnd
;т e 门 e p ь, чтобы извлечь несовпавший элеменг из строки
;в регистр-аккумулятор,
;ум(?ньшаем значение регистра si и тем самым перемещаемся
;к действительной позиции элемента в строке
dec si;команда 1 ods использует ds:si-адресацию
;теперь ds:si указывает на позицию в s t г i ng2
lods string2 загрузим элемент из строки в AL
;нетрудно догадаться что в нашем примере эго символ - "н11
exit:;выход
mov ax,4c00h
int 21h end mai n
Загрузка в регистрAL/AX/EAX байтов, слов, двойных слов
Команд i d загрузки байта в регистр AL (LODS8), слова — в регистр АХ (LODSW), двойного слова в регистр ЕАХ (LODSD) аналогичшй другим цепочечным командам. Они являются вариантами команды LODS. Каждая из -)тих команд работает с цепочками из элементов определенного размера. Предварительно вы должны загрузить значение длины цетючки л ее адрес в регистры ЕСХ/СХ и DS:ES1/SI,
Перенос элемента из аккумулятора в цепочку
Oiiepaiii!«-ni'ii.M!! iин переноса злемеита из аккумулятора в пепочк;/'позволяет про- 113Еестг1де!!с^'!И1е, обратное действию команды LODS, то есть co?.pai Iить значение из рсгистр«1-аккуму/1ятора в элементе цепочки. Эту онершхию удобно использовать
вместе с операциями поиска (сканирования) SCANS и загрузки LODS с тем, чтобы, найдя нужный элемент, извлечь его в регистр и записать на его место новое значение. Команды, поддерживающие эту опсраци1()-11римитив, могут работать с: -uie- ментами размером 8, 16 или 32 бита. ТASM предоставляет программисту четыре команды сохранения элемента цепочки из регистра-аккумулятора, работающие с элементами разного размера:
STOS адрес^приемника — сохранить в цепочке элемент (STOre String) из регистра-аккумулятора AL/AX/EAX;
STOSB — сохранить в цепочке байт (STOre String Byte) из регистра AL;
STOSW — сохранить в цепочке слово (STOre String Word) из регистра АХ;
STOSD — сохранить в цепочке двойное слово (STOre Siring Double Word) из регистра ЕАХ.
Команда STOS
Синтаксис команды STOS:
stos адрес_приемника
Команда имеет один операнд адрес_приемника, адресующий цепочку в дополнительном сегменте данных. Команда пересылает элемент из аккумулятора (регистра EAX/AX/AL) в элемент цепочки по адресу, соответствующему содержимому пары регистров ES:EDI/DI. При этом содержимое EDI/DI подвергается инкременту или декременту (в зависимости от состояния флага DF) па величину, равную размеру элемента цепочки.
Префикс повторения в этой команде может и не понадобиться — все зависит от логики программы. Например, если использовать префикс повторенияREP,тз можно применить команду для инициализации области памяти некоторым фиксированным значением.
В качестве примера рассмотрим листинг 12.5, в котором производится замена в строке всех символов «а» символами, вводимыми'с клавиатуры.
Листинг 12.5. Замена командой STOS символа в строке символом, вводимым с клавиатуры
;prg_12_5.asm
MASM"
MODEL small
STACK 256
.data
сообщения
fnd db ©ah,0dh f'Символ найден','$'
nochar db 0ah ,0dh,'Символ не найден.','$'
mesldb Oah,Odh,'Исходная строка
string db "Поиск символа в этой строке.",Oah,0dh J $' ;строка для поиска
mes2db Oah,Odh,' Введите символ, на который следует заменить найденный'
db Oah,Odh,'$'.
mes3db 0ah ,Odh,'Новая строка: '".
.code『
assume ds:@da£a,es:@data :привязка ds и es
к сегменту данных
main:mov ax,@datamain:mov ax,@dataточка входа в программу
main:
mov ax,@data
main:
mov ax,@data
明心:.,明心:.,загрузка сегментных регистров
明心:.,
明心:.,
Листинг 12.5 (продолжение)
mov ds,ax mov es, ax
;настройка es на ds
mov ah,09h
lea dx,mesl
int 21h
:вывод сообщения mesl
lea dx, string
int 21h
:вывод string
cld
;сброс флага df
lea di .string
;загрузка в di смещения string
mov ex,28
;для префикса repne - длина строки
поиск в строке string до тех пор, пока
символ в a I и очередной символ в строке
не равны: выход — при первом совпадении
cycl:
mov al,'a*
;символ для поиска - "а" (кириллица)
repne seas string
]e found
;если элемент найден, то переход на found
failed:
иначе, если не найден, то вывод сообщения nochar
mov ah,09h
lea dx,nochar
int 21h
jmp exit
;переход на выход
found:
mov ah,09h
lea dx, fnd
int 21h
;вывод сообщения об обнаружении символа
корректируем di для
получения значения
действительной позиции совпавшего элемента
;в строке и регистре
al
dec di
new_char:;блок замены символа
mov ah.09h
lea dx,mes2
int 21h; вывод сообщения mes2
;ввод символа с клавиатуры
mov ah,01h
int
21h
;в al - введенный символ
stos
string
;сохраним введенный символ
mov lea int
ah,09h dx,mes3 21h
; (из al) в строке
;string в позиции старого символа
;вывод сообщения mes3
lea int
dx,string 21h
;вывод сообщения string следующего символа "а" в строке
;переход на поиск
inc
di
;указатель в строке string на символ
jmp
cycl
следующий после совпавшего, ;на продолжение просмотра string
exit:
mov ах ,4c00h
int 21h
end main
;выход
;конец программы
Сохранение в цепочке байта, слова, двойного слова из регистра AL/AX/EAX
Команды STOSB, STOSW и STOSD, аналогично другим цепочечным операциям, являются вариантами команды STOS. Каждая из этих команд работает с цепочками из элементов определенного размера. Предварительно необходимо загрузить значение длины цепочки и ее адрес в регистры ЕСХ/СХ и ES:EDI/DI.
Работа с портами ввода-вывода
Описанные далее две команды появились впервые в системе команд процессора i386. Они позволяют организовать эффективную передачу данных между портами ввода-вывода и цепочками в памяти. Следует отметить, что эти две команды позволяют достичь более высокой скорости передачи данных по сравнению с той скоростью, которую может обеспечить контроллер DMA (Direct Memory Access — прямой доступ к памяти).
Ввод элемента цепочки из порта ввода-вывода
Операция ввода элемента цепочки из порта ввода-вывода реализуется командой INS (Input String), имеющей следующий формат:
ins адрес_приемника,номер_порта
Эта команда вводит элемент из порта, номер которого находится в регистре DX, в элемент цепочки, адрес памяти которого определяется операндом адрес_прием- ника. Несмотря на то, что цепочка, в которую вводится элемент, адресуется указанием этого операнда, ее адрес должен быть явно сформирован в паре регистров ES:EDI/DI. Размер элементов цепочки должен быть согласован с размером порта — он определяется директивой резервирования памяти, с помощью которой выделяется память для размещения элементов цепочки. После пересылки команда INS производит коррекцию содержимого регистра EDI/DI на величину, равную размеру элемента, участвовавшего в операции пересылки. Как обычно, при работе цепочечных команд учитывается состояние флага DF.
Подобно командам, реализующим рассмотренные ранее цепочечные операции- примитивы, транслятор преобразует команду INS в одну из трех машинных команд без операндов, работающих с цепочками элементов определенного размера:
鹽 INSB (INput String Byte) — ввести из порта цепочку байтов;
懇 INSW (INput String Word) — ввести из порта цепочку слов;
藜 INSD (INput String Double Word) — ввести из порта цепочку двойных слов.
К примеру, введем из порта 5000h 10 байтов в область памяти pole:
.data
poledb 10 dup
.code
push ds
pop es ;настройка es на ds
mov dx,5000h
lea di fpole
mov ex, 10 rep insb
Вывод элемента цепочки в порт ввода-вывода
Операция вывода элемента цепочки в порт ввода-вывода реализуется командой OUTS (Output String), имеющей следующий формат:
outs номер_порта,адрес_источника
Эта команда выводит элемент цепочки в порт, номер которого находится в регистре DX. Адрес элемента цепочки определяется операндом адрес_источника.
на то, что цепочка, из которой выводится элемент, адресуется указаниемоперанда, значение адреса должно быть явно сформировано в паре регистровРазмер структурныхдолжен быть согласован
с размером порта — он определяется директивой резервирования памяти, с помощью которой выделяется память дляэлементов цепочки. После пересылки команда OUTS производитсодержимого регистра ESI/SI на ве-
равную размеру элемента цепочки, участвовавшего в операции пересылки. этом, как обычно, учитываетсяфлага
Подобно команде INS транслятор преобразуетOUTS в одну из трех ма-
команд безс
го размера:
String Byte)цепочку байтов в порт ввода-вывода;
String Word) — вывести цепочку слов в порт ввода-вывода;
StringWord) —цепочку двойных слов в порт
ввода-вывода.
Впримера рассмотрим фрагмент программы, которая выводит после-
символов в порт ввода-вывода с номером 378соответствую
щий
. data
db "Текст для печати "
.code
'mov dx,378h
lea
muv ex,16
rep outsb■
организации работы с портами недостаточно знать их номера и назначе- менее важно знать и понимать алгоритмы их работы. Эти сведения можно найти вна устройство (но, к сожалению, далеко не всегда).
Реализации более сложных алгоритмовоснованные на работе с цепо
чечными командами, приведены в |8].
Итоги
командимеетинтересную группу команд, позволя-
производитьблоками элементов до 64 Кбайт или 4 Гбайт
в зависимости отразрядности адресаили
элементов логически могут представлять собой последовательности элементов с любымив памяти в виде двоичных кодов.
Единственноесостоит в том, что размеры элементов вблоках
значением 8,пли 32
Команды обработки строквозможность выполнения семи
раций-примитивов,поэлементно.
Каждая операция-примитивтремя разными машинными коман-
и одной псевдокомандой, которая преобразуется транслятором одну из
трехупомяпутыхрансо м,【ниннвх команд. В:屮нпн г преобразования определи- ется типом О1!срапдов в команде.
Процессор всегда предполагает, что строка41р11ем11пк находится в/^ополгиггель- ном сегмеш с (адресуемом пос[)е/(ством ссгменгиогсз |)егист卩工 Е5), а сгрока-ис- точлик в сегАпягге да11ных (адресуемом посредством сегментного pei isct- ра DS).
Процессор адресует строкзг-]!рпемн.лк через решет卩EDI/DI, а строку-источник — через регистр ESI/SI.
Допускается переопределять сегмент для C'rpOiGIHlC'rOVIHHKth для строки-приемника этого делать нельзя.
磅 Особенностьработы цепочечных команд состоит в том, что опи ати'омати ческн Вы11олн5потприраще1п1еил11уме1н)и1еииссодсржимогорегистров EDI/DI и ESI/ SI в зависимости от используемо!! цепочечной команды. Что именно происходит с этими регпетрамп, определяется состоянием флага DF, которым управ;!»- ют команды CLD и 5TD. Значеипе. на которое изменяется содержимое индексных регистров, определяется типом элементов строки или кодом операции цепочечной команды.
Глава 13
Сложные структуры данных
Понятие сложного типа данных в ассемблере
Средства ассемблера для создания и обработки сложных структур данных
Массивы
餌Структуры
> Объединения
> Записи
В предыдущих главах при разработке программ мы использовали данные двух типов.
Ж Непосредственные данные, представляющие собой числовые или символьные значения и являющиеся частью команды. Непосредственные данные формируются программистом в процессе написания программы для конкретной команды ассемблера.
經 Данные, описываемые с помощью ограниченного набора директив резервирования памяти, позволяют выполнять самые элементарные операции по размещению и инициализации числовой и символьной информации. При обработке этих директив ассемблер сохраняет в своей таблице символов информацию о местоположении данных (значения сегментной составляющей адреса и смещения) и типе данных, то есть единицах памяти, выделяемых для размещения данных в соответствии с директивой резервирования и инициализации данных.
Эти два типа данных являются элементарными, или базовыми; работа с ними поддерживается на уровне системы команд процессора. Используя данные этих типов, можно формализовать и запрограммировать практически любую задачу. Но насколько это будет удобно — вот вопрос.
Обработка информации в общем случае — процесс очень сложный. Это косвенно подтверждает популярность языков высокого уровня. Одно из несомненных достоинств языков высокого уровня — поддержка развитых структур данных. При их использовании программист освобождается от решения конкретных проблем, связанных с представлением числовых или символьных данных, и получает возможность оперировать информацией, структура которой в большей степени отражает особенности предметной области решаемой задачи. В то же самое время, чем выше уровень такого абстрагирования от конкретного представления данных в компьютере, тем большая нагрузка должна ложиться на компилятор для создания действительно эффективного кода. Ведь нам уже известно, что в конечном итоге все написанное на языке высокого уровня в компьютере будет представлено на уровне машинных команд, работающих только с базовыми типами данных. Таким образом, самая эффективная программа — программа, написанная в машинных кодах, но писать сегодня большую программу в машинных кодах — занятие довольно бессмысленное.
С целью облегчения разработки программ в язык ассемблера на уровне его директив была введена поддержка нескольких сложных типов данных. Это позволило несколько сгладить различия между языками высокого уровня и ассемблером. У программиста появилась возможность сочетать достоинства языка ассемблера и языков высокого уровня (в направлении абстрагирования от конкретного представления данных), что в итоге повышает эффективность конечной программы.
TASM поддерживает следующие сложные типы данных:
и массивы;
структуры;
объединения;
записи.
Разберемся более подробно с тем, как определить данные этих типов в программе и организовать работу с ними.
Массивы
Дадим формальное определение: массив — структурированный тип данных, состоящий из некоторого числа элементов одного типа.
Для того чтобы разобраться в возможностях и особенностях обработки массивов в программах на ассемблере, нужно ответить на следующие вопросы.
爾 Как описать массив в программе?
Как инициализировать массив, то есть как задать начальные значения его элементов?
後 Как организовать доступ к элементам массива?
о Как организовать выполнение типовых операций с массивами?
Описание и инициализация массива в программе Специальных средств описания массивов в программах ассемблера, конечно, нет. Чтобы использовать массив в программе, его нужно смоделировать одним из перечисленных далее способов.
Можно перечислить элементы массива в поле операндов одной из директив описанияданных. Приперечпсленииэлсментыраздсляютсязапятыми. Иакри- мер,
;массив из 5 элементов. Размер каждого элемента 4 байта: mas dd 1,2,3,4,5
Можно использовать оператор повторения DUP. К примеру,
;массив из 5 нулевых элементов. Размер каждого элемента 2 байта: mas dw 5 dup (0)
Такой способ определения используется для резервирования памяти с целью размещения и инициа^гизации элементов массива.
Можно использовать директивы LABEL и REPT. Пара этих директив позволяет облегчить описание больших массивов в памяти и повысить наглядность такого описания. Директива REPT относится к макросредствам языка ассемблера и шяспяваст повторение указанное число раз строк, заключенных между директивой и строкой ENDM. К примеру, определим массив байтов в области памяти, обозначенной идентификатором mas_b. В данном случае директива LABEL определяет символическое имя mas_b аналогично тому, как это делают директивы резервирования и инициализации памяти. Достоинство директивы LABEL — в том, что она не резервирует память, алишь определяет характеристики объекта. В данном случае объект — это ячейка памяти. Используя несколько директив LABEL, записанных одна за друюй, можно присвоить одной и той же области памяти разные имена и типы, что “ сделано в следующем фрагменте:
mas_b label byte mas__w label word re pt 4
dw OflfSh endm
В результате в памяти будет создана последовательность из четырех слов I'lfO. Эту последовательность можно трактовать как массив байтов или слов в зависимости от того, какое имя области мы будем использовать в программе — mas_b или mas_w.
Чтобы инициализировать значениями область памяти и впоследствии трактовать ее как массив, можно использоватг> цикл. Посмотрим па примере листинга 13.1, каким образом это делается.
Листинг13.1.Инициализация массива в цикле
; prg 13 1. asm
MASH
MODELsmall
STACK256
.data
mes db 0ah,0dh,1 Массив- ",”$'
mas db 10 dup (?);исходный массив
:db 0
.cede
ma i n:
mov ax, @data mov ds ,ax xor ax, ax mov ex , 10 mov si ,e
;обнуление ах
;значение счетчика цикла в сх индекс начального элемента в сх
go:
mov bh , i
mov mas [si],bh inc i
inс i
цикл инициализации
i в bh
запись в массив i
инкремент i
продвижение к следующему элементу массива
;вывод
show:
loop goповторить цикл
на экран получившегося массива
mov ex , 10
mov si ,0
mov ah, ®9h
lea dx , roes
int 21h
mov ah,®2h;функция вывода значения изна экран
exi t :
end
mov dl ,mas ⑶]
add dl,3Qh
int 2 Ih inc si loop show
mov 日x ,4二®®h
int 2 Ih main
преобразование числа в символ
стандартный выход
конец программы
Доступ к элементам массива
При работе с массивами необходимо четко представлять о?6о, что все элементы массива располагаются в памяти компьютера гюсле/ювател ьпо. Само по себе та- кос расположение tni'iei'o не говорит о назпачентш п порядке пс!1()л1)з()ваш1яэ1их элсме1пов. И только лишь Hj)orpaMMiiCT с, помощью составленного им алгоритма обработки определяет, как нужно трактовать последовательность байтов, составляющих массив. Так, одпу и ту же область памяти можно трактовать однов^х'меи- но и как одномерный, и как двухмерный массив. Все зависит только от МП'。卩FfMK обработки этих данных в конкретной программе. Сами по себе данные не несут никакой информации о своем «смысловом», или логическом, типе. Помшгге об этом принципиальном моменте.
Те же соображения можно распространить и на индексы элементов массива. Ассемблер не подозревает пи об их существовании, пи об их чис-лепнык смыело- вых значениях. Для того чтобы локализовать определенный элемент массива, к его имени нужно добавить индекс. Так как мы моделируем массив, то должны нихибо- титься и о моделировании индекса. В языке ассемблера индексы массивов — это обычные адреса, но с ними работают особым образом. Другими словами, когда при программировании на ассемблере мы говорим об индексе, то, скорее, подразумеваем под этим не номер элемента в массиве, а некоторый адрес. ДаваНте еще раз обратимся к описанию массива. К примеру, пусть в программе статически определена последовательность данных: mas dw 0 4.2.3,4,5
Пусть эта последовательность чисел трактуется как одгюмерный массив. Размерность каждого элемента определяется директивой DW, то есть она равна двум байтам. Чтобы НО.'В^Н-П'Ъ доступ к третьему工牙?'1『门\Гnvjrno к адресу M;,CC! i ВЗ upk
бавить 6. Нумерация элементов массива в ассемблере начинается с нуля. То есть в нашем случае речь, фактически, идет о 4-м элементе массива — 3, но об этом знает только программист; процессору в данном случае все равно — ему нужен только адрес. В общем случае для получения адреса элемента в массиве необходимо начальный (базовый) адрес массива сложить с произведением индекса (номер элемента минус единица) этого элемента на размер элемента массива:
база + (индекс • размер элемента).
Архитектура процессора предоставляет довольно удобные программно-аппаратные средства для работы с массивами. К ним относятся базовые и индексные регистры, позволяющие реализовать несколько режимов адресации данных. Используя данные режимы адресации, можно организовать эффективную работу с массивами в памяти. Вспомним эти режимы.
• Индексная адресация со смещением — режим адресации, при котором эффективный адрес формируется из двух компонентов:
постоянный (базовый) компонент формируется указанием прямого адреса массива в виде имени идентификатора, обозначающего начало массива;
П переменный (индексный) компонент формируется указанием имени индексного регистра.
К примеру,
mas dw @,1,2,34 5
mov si ,4
;поместить 3-й элемент массива mas в регистр ах: mov ax,mas[si]
緻 Базовая индексная адресация со смещением — режим адресации, при котором эффективный адрес формируется максимум из трех компонентов:
D в качестве постоянного (необязательного) компонента может выступать прямой адрес массива в виде имени идентификатора, обозначающего начало массива, или непосредственного значения;
переменный (базовый) компонент формируется указанием имени базового регистра;
Р переменный (индексный) компонент формируется указанием имени индексного регистра.
Этот вид адресации удобно использовать при обработке двухмерных массивов. Пример использования этой адресации мы рассмотрим далее при изучении особенностей работы с двухмерными массивами.
Напомним, что в качестве базового регистра может использоваться любой из восьми регистров общего назначения. В качестве индексного регистра также можно использовать любой регистр общего назначения, за исключением ESP/SP.
Процессор позволяет масштабировать индекс. Это означает, что если указать после имени индексного регистра символ звездочки (*) с последующей цифрой 2, 4 или 8, то содержимое индексного регистра будет умножаться на 2,4 или 8, то есть масштабироваться. Применение масштабирования облегчает работу с массивами, которые имеют размер элементов, равный 2, 4 или 8 байт, так как процессор сам
производит коррекцию индекса для получения адреса очередного элемента массива. Нам нужно лишь загрузить в индексный регистр значение требуемого индекса (считая от 0). Кстати сказать, возможность масштабирования появилась в процессорах Intel, начиная с модели i486. По этой причине в рассматриваемом далее примере программы стоит директива Ее назначение, как и ранее использовавшейся директивы .386, — в том, чтобы указать ассемблеру на необходимость учета дополнительных возможностей новых процессоров при формировании машинных команд.
В качестве примера масштабирования рассмотрим листинг 13.2, в котором просматривается массив, состоящий из слов, и производится сравнение этих элементов с нулем. Выводится соответствующее сообщение.
ЛистингПросмотр массива слов с использованием масштабирования
;prg 13
;tlink /v /3 prg 12 2.
MASM
MODEL
STACK
;начало сегмента данных сообщений: db "не равен
raes2 db"равен 0!$
db$'
mas6,0,8массив
.code
.486обязательно
main:
mov
movds с сегментом данных
xor;обнуление ах
prepare:
mov;значение счетчика цикла в сх
movв esi
compare:
movэлементмассивавdx
dx,0dx с0
equal;переход, если равно
;не равно
movсообщения на экран
lea dx,mes3
int 21h
movномера элемента массива
на экран
mov
add dl,30h
int 21h
mov
lea
int 21h
inc esi
dec
jcxz exit
jmp compare
equal:
mov
lea dx,mes3
int 21h
mov
mov add
на следующий элемент
условие для выхода из цикла сх=0? Если да - на выход нет - повторить цикл равно 0
вывод сообщения mes3 на экран
продолжение
Листинг 13.2 (продолжение)
int 21h
mov ah,09h
;вывод сообщения mes2 на экран
lea dx,mes2 int 21h inc esi
;на следующий элемент
dec ex
;все элементы обработаны?
jcxz exit
.imp compare exit:
mov ax,4c00h
стандартный выход
int 21h end main
,;конец программы
Еще несколько
слов о соглашениях.
it Если для описания адреса используется только один регистр, то речь идет о базовой адресации, и этот регистр рассматривается как базовый:
;переслать байт из области данных,
;адрес которой находится в регистре ebx: moval,[ebx]
緩 Если для задания адреса в команде используется прямая адресация (в виде идентификатора) в сочетании с одним регистром, то речь идет об индексной адресации. Регистр считается индексным, и поэтому для получения адреса нужного элемента массива можно выполнить масштабирование:
add eax,mas[ebx*4]
;сложить содержимое еах с двойным словом
;в памяти по адресу mas + (ebx)*4
幾Если для описания адреса используются два регистра, то речь идет о базово
индексной адресации. Левый регистр рассматривается как базовый, правый — как индексный. В общем случае это не принципиально, но если масштабирование применяется к одному из регистров, то он всегда является индексным. Однако лучше придерживаться определенных соглашений. Помните, что применение регистров ЕВР/ВР и ESP/SP по умолчанию подразумевает, что сегментная составляющая адреса находится в регистре SS.
Заметим, что базово-индексную адресацию не возбраняется сочетать с прямой адресацией или указанием непосредственного значения. Адрес тогда будет формироваться как сумма всех компонентов.
К примеру,
mov ax,mas[ebx][ecx*2]
;адрес операнда равен [mas+(ebx)+(ecx)*2]
sub dx. [ebx+8][ecx*4]
;адрес операнда равен [(ebx)+8+(ecx)*4]
Следует заметить, что масштабирование эффективно лишь тогда, когда размерность элементов массива равна 2, 4 или 8 байт. Если же размерность элементов другая, то организовывать обращение к элементам массива нужно обычным способом, как описано ранее.
Рассмотрим пример работы с массивом из пяти трехбайтовых элементов (листинг 13.3). Младший байт в каждом из этих элементов представляет собой некий счетчик, а старшие два байта — что-то еще, для нас не имеющее никакого значе-
ния. Необходимо последовательно обработать элементы данного массива, увеличив значения счетчиков на единицу.
Листинг 133 Обработка массива элементов с нечетной длиной
;prg_13 3.asm
MASM
MODEL
small
;модель памяти
STACK
256
;размер стека
.data
;начало сегмента данных
N = 5
;количество элементов массива
mas db
5 dup (3 dup
(®))
.code
;сегмент кода
main:
;точка входа в программу
mov
ax,@data
mov
ds, ax
xor
ax, ax
;обнуление ах
mov
si ,0
;0 в si
mov
ex , N
;N в сх
go:
mov
dl.mas [si]
;первый байт поля в dl
inc
dl
увеличение dl на 1 (по условию)
mov
mas[si],dl
;заслать обратно в массив
add
si ,3
;сдвиг на следующий элемент массива
loop
go
;повтор цикла
mov
si ,0;
подготовка к выводу на экран
mov
cx,N
show:
;вывод на экран содержимого
;первых байтов полей
mov
dl,mas[si]
add
si , 3
add
dl,30h
mov
ah,02h
1 nt
21h
loop
show
exi t:
mov
ax ,4c00h;стандартный выход
int
21h
endmain
L;конец программы
Двухмерные массивы
С представлением одномерных массивов в программе на ассемблере и организацией их обработки все достаточно просто. А как быть, если программа должна обрабатывать двухмерный массив? Все проблемы возникают по-прежнему из-за того, что специальных средств для описания такого типа данных в ассемблере нет. Двухмерный массив нужно моделировать. На описании самих данных это почти никак не отражается — память под массив выделяется с помощью директив резервирования и инициализации памяти. Непосредственно моделирование обработки массива производится в сегменте кода, где программист, описывая алгоритм обработки на ассемблере, определяет, что некоторую область памяти необходимо трактовать как двухмерный массив. При этом вы вольны в выборе того, как понимать распо- ложе][ие элементов двухмерного массива в памяти: по строкам или по столбцам. Если последовательность однотипных элементов в памяти трактуется как двухмерный массив, расположенный по строкам, то адрес элемента (i,j) вычисляется по формуле
(база + (количество_элементов_в_строке • i +j) • размер_элемента).
Здесь i = 0...И—1 — номер строки, aj=0...722-1 — номер столбца. Например, пусть имеется массив чисел (размером в 1 байт) mas(i, j) с размерностью 4 • 4 (г :ss 0...3, j = 0...3):
23040567
05060799
67080923
87090008
В памяти элементы этого массива будут расположены в следующей последовательности:
23 04 05 67 05 06 07 99 67 08 09 23 87 09 00 08
Если мы хотим трактовать эту последовательность как двухмерный массив, приведенный раньше, и извлечь, например, элемент mas(2, 3) = 23, то, проведя нехитрый подсчет, убедимся в правильности наших рассуждений:
Эффективный адрес mas(2, 3) H mas + (4 • 2 + 3) • 1= mas + 22.
Посмотрите на представление массива в памяти и убедитесь, что по этому смещению действительно находится нужный элемент массива.
Логично организовать адресацию двухмерного массива, используя рассмотренную нами ранее базово-индексную адресацию. При этом возможны два основных варианта выбора компонентов для формирования эффективного адреса:
* сочетание прямого адреса как базового компонента адреса и двух индексных регистров для хранения индексов:
mov ax,mas[ebx][esi]
綫 сочетание двух индексных регистров, один из которых является и базовым, и индексным одновременно, а другой — только индексным:
mov ax,[ebx][esi]
В программе это будет выглядеть примерно так:
фрагмент программы выборки элемента
;массива mas(2,3) и его обнуления
.data
mas db 23,4,5,67,5,6,7,99,67.8,9.23,87,9,0,8 i=2
]=3
el_size=l
.code
:386
movesi,4*el_size*i
movdi , j*el_size
mov al.mas[esi] [edi] ;b al элемент mas(2,3)
В качестве законченного примера рассмотрим программу поиска элемента в двухмерном массиве чисел (листинг 13.4). Элементы массива заданы статически.
Листинг 134 Поиск элемента в двухмерном массиве
;prg 13 4.asm
MASM
MODELsmall
STACK256
.data
размером 2x5 - если ее не
для наглядности она может быть описана так:
;аггау2(5 DUP (?))
мы ее инициализируем:
array
dw
1,2,3,3.5.6.7,3,9,0
это будет выглядеть так
логически
;аггау=
{1
2}
{3
3}
{5
6}
{7
3}
{9
0}
элемента
3для поиска
failed dbтакого элемента в
success dbэлемент в массиве присутствует
dbнаиденных элементов
fnd db '
.code
main:
mov ax,©data
mov ds,ax
xor
mov s 0в матрице
mov bx,0;Ьх=строки в матрице
mov;число для внешнего цикла (по строкам)
;внешний цикл по строкам
pushв стеке счетчика внешнего цикла
movдля внутреннего цикла (по столбцам)
mov s 0
цикл по строкам
movах первый (очередной) элемент матрицы
addна следующий элемент в строке
содержимое текущего элемента в ах с искомым элементом:
cmp ax,elem
;если текущий совпал с искомым, то переход на here для обработки,
foundtime loop iternal
счетчик совпавших
move_next:
pop
add
loop external
в матрице
СХ из стека (5) на следующую строку ;цикл (внешний)
cmpчисла совпавших с 0
больше 0, то переход совпавших с искомым movсообщения на экран
movfailed
21h на выход
eql:есть элементы, совпавшие с искомым
mov; вывод сообщений на экран
movsuccess
int mov mov add int 21h mov movfnd
int 21h
exit:выход
movстандартное завершение программы
int
endmain
конец программы
- цикл продолжения поиска
j ne $+6
Анализируя работу программы, не забывайте, что при написании программ на языке ассемблера нумерацию элементов массива удобнее производить с 0. При поиске определенного элемента массив просматривается от начала и до конца. Программа сохраняет в поле foundtime количество вхождений искомого элемента в массив. В качестве индексных используются регистры SI и ВХ.
Типовые операции с массивами
Для демонстрации основных приемов работы с массивами лучше всего подходят программы поиска или сортировки. Программа поиска была приведена ранее. Теперь рассмотрим одну из программ, выполняющих сортировку массива по возрастанию (листинг 13.5).
Листинг 13.5. Сортировка массива
;prg_13_5.asm
MASM" 一
MODELsmall
STACK256
.data
mesl db 0ah,0dh,'Исходный массив - $' ,0ah,0dh некоторые сообщения
mes2 db 0ah,0dh,'Отсортированный массив - $*,0ah,Odh
nequ 9;количество элементов в массиве, считая с 0
mas dw 2,7,4,0,1.9,3,6,5,8 исходный массив
tmp dw 0;переменные для работы с массивом
idw 0
jdw 0
.code
main:
mov ax,@data
mov xor
ds ax
.ax ,ax
;вывод mov lea int
на экран ah,09h
dx,mesl 21h
исходного массива
;вывод сообщения
mesl
mov ex, 10 mov si ,0 show_primary:
;вывод значения
элементов
исходного массива на экран
mov dx,mas[si]
add dl,30h
mov ah,Q2h
int 21h
add si ,2
loop show_primary
строки 40-85 программы эквивалентны следующему коду на языке С: for (i=0;i<9;i++)
for (j=9:]>i;j-)
if (mas[1]>mas[j])
;(tmp=mas[i];
;mas[i]=mas [ j];
;mas[j]=tmp;}
mov i,0:инициализация i
внутренний цикл по j
internal:
movj
jmp cycl_j ;переход на тело цикла
exchange; -
shl
bx,l
mov
ax ,mas[bx]
;ax=mas[i]
mov
bx.j
: bx=j
shl cmp
bx,l
ax,mas [bx]
;mas[i] ? mas[j] - сравнение элементов
jгe
lesser
;если mas[i ] меньше, то обмен не нужен
mov
bx, i
;и переход на продвижение далее по массиву
;иначе tmp=mas [ i] , mas[1]=mas[j], mas[j]=tmp
;tmp=mas [i]
;bx=i
shl
bx,l
;умножаем на 2, так как элементы - слова
mov
tmp,ax
:
mov
j
;nias [ i ] =mas [j ] ; bx=j
shl
bx,l
;умножаем на 2, так как элементы - слова
mov
ax,mas [bx]
;ax=mas[jl
mov
bx, i
;bx = i
shl
bx,l
;умножаем на 2, так как элементы - слова
mov
mas[bx],ax
;mas[1]=mas[j]
mov
bx, j
;mas[j]=tmp ;bx=j
shl
bx,l
;умножаем на 2, так как элементы - слова
mov
ax,tmp
;ax=tmp
mov
mas[bx],ax
;mas[j]:tmp
lesser:
dec
;продвижение далее по массиву во внутреннем ;цикле
:
“Г」:
mov
ax , j
цело цикла по j
;ax=j
cmp
ax , 1
; сравнить j ? i
exchange
;если j X , то переход на обмен
i nc i
иначе - на внешний цикл по i :i++
ernp
i , n
;сравнить i ? n - прошли до конца массива
j 1
internal
;если 1 <n - продолжение обработки
mov bx, i
;bx = 1
;вывод отсортированного массива
mov ah,®9h
lea dx,mes2
int 21h
prepare:
mov ex, 10
;вывод значения элемента на экран;вывод значения элемента на экран;стандартный выход;конец программы;стандартный выход;конец программыmov si ,® show:.
;вывод значения элемента на экран
;вывод значения элемента на экран
;стандартный выход
;конец программы
;стандартный выход
;конец программы
mov dx,mas [ si] add dl,30h
mov ah,02h
int 21h
add si ,2
loop show
exit:
mov ax,4c0Oh
int 21h
end main
В основе программы лежит алгоритм, похожий на метод пузырьковой сортировки. Эта программа не претендует на безусловную оптимальность, так как существует целая теория, касающаяся подобного типа сортировок. Перед нами стоит другая цель — показать использование средств ассемблера для решения подобно-
го рода задач. В программе два цикла. Внешний цикл определяет позицию в массиве очередного элемента, с которым производится попарное сравнение элементов правой части массива (относительно этого элемента). За каждую итерацию внешнего цикла на месте этого очередного элемента оказывается меньший элемент из правой части массива (если он есть). В остальном программа довольно проста и на языке высокого уровня заняла бы около десятка строк. Более подробно с реализацией различных типов сортировок на языке ассемблера можно познакомиться в [8].
Структуры
Рассмотренные нами ранее массивы представляют собой совокупность однотипных элементов. Но часто в приложениях возникает необходимость рассматривать некоторую совокупность данных разного типа как некоторый единый тип. Это очень актуально, например, для программ баз данных, где с одним объектом может ассоциироваться совокупность данных разного типа. К примеру, ранее мы рассмотрели листинг 13.3, в котором работа производилась с массивом трехбайтовых элементов. Каждый элемент, в свою очередь, представлял собой два элемента разных типов: однобайтовое поле счетчика и двухбайтовое поле, которое могло нести еще какую-то нужную для хранения и обработки информацию. Если читатель знаком с одним из языков высокого уровня, то он знает, что такой объект обычно описывается с помощью специального типа данных — структуры. С целью повысить удобство использования языка ассемблера в него также был введен такой тип данных.
По определению структура — это тип данных, состоящий из фиксированного числа элементов разного типа.
Дляиспользованияструктур в программе необходимовыполнитьтридействия.
Задать шаблон структуры. По смыслу это означает определение нового типа данных, которыйвпоследствииможноиспользоватьдляопределенияперемен- ных этого типа.
Определить экземпляр структуры. Этот этап подразумевает инициализацию конкретной переменной с заранее определенной (с помощью шаблона) структурой.
Организовать обращение к элементам структуры.
Очень важно хорошо понимать разницу между описанием структуры в программе и ее определением. Описание структуры в программе означает лишь указание компилятору ее схемы, или шаблона; памятьпри этом невыделяется. Компилятор извлекает из этого шаблона информацию о расположении полей структуры и их значениях по умолчанию. Определение структуры означает указание транслятору на выделение памяти и присвоение этой области памяти символического имени. Описать структуру в программе можно только один раз, а определить — любое количество раз. После того как структура определена, то есть ее имя связано с именем переменной, возможно обращение к полям структуры по их именам.
Описание шаблона структуры
Описание шаблона структуры имеет следующий синтаксис:
имя_структуры STRUC
<описание полей>
имя_структуры ENDS
Здесь<описаниеполей> представляет собой последовательностьдиректив описания данных DB, DW, DD, DQ и DT. Их операнды определяют размер полей и при необходимости — начальные значения. Этими значениями будут, возможно, инициализироваться соответствующие поля при определении структуры.
Как мы уже отметили, при описании шаблона память не выделяется, так как это всего лишь информация для транслятора. Местоположение шаблона в программе может быть произвольным, но, следуя логике работы однопроходного транслятора, шаблон должен быть описан раньше, чем определяется переменная с типом данных структуры. То есть при описании в сегменте данных переменной с типом некоторой структуры ее шаблон необходимо описать в начале сегмента данных либо перед ним.
Рассмотрим работу со структурами на примере моделирования базы данных о сотрудниках некоторого отдела. Для простоты, чтобы уйти от проблем преобразования информации при вводе, условимся, что все поля символьные. Определим структуру записи этой базы данных следующим шаблоном:
worker struc
;информация о сотруднике
пат
db
30 dup
("")
;фамилия, имя , отчество
sex
db
;пол
position
db
30 dup
("")
;должность
age
db
2 dup
(一)
;возраст
standing
db
2 dup
("")
;стаж
salary
db
4 dup
("")
;оклад в рублях
bi rthdate
db
8 dup
(…)
;дата рождения
worker ends
Определение данных с типом структуры
Для использования описанной с помощью шаблона структуры в программе необходимо определить переменную с типом данных структуры. Для этого используется следующая синтаксическая конструкция:
[имя переменной] нмя_структуры <[список значений]>
Здесь:
Ж имяпеременной — идентификатор переменной данного структурного типа. Задание имени переменной необязательно. Если его не указать, будет просто выделена область памяти размером в сумму длин всех элементов структуры;
幾 список значений — заключенный в угловые скобки список начальных значений элементов структуры, разделенных запятыми. Его задание также необязательно. Если список указан не полностью, то все поля структуры для данной переменной инициализируются значениями из шаблона, если таковые заданы. Допускается инициализация отдельных полей, но в этом случае пропущенные поля, которые будут инициализированы значениями из шаблона структуры, должны отделяться запятыми. Если при определении новой переменной с ти-
пом данных структуры мы согласны со всеми значениями полей в ее шаблоне (то есть заданными по умолчанию), то нужно просто написать угловые скобки. К примеру, victor worker <>.
Для примера определим несколько переменных с типом описаннойструк
туры: datasegment
workerАндрей
,,,
sotr2 workerНаталья
'30','10','1680','27.10.58'> sotr3 workerЮрий
, sotr4workerЕлена
,,,
sotr5 worker <> ; здесь все значения по умолчанию data ends
Методы работы со структурами
Смысл введения структурного типа данных в любой язык программирования состоит в объединении разнотипных переменных в один объект. В языке должны быть средства доступа к этим переменным внутри конкретного экземпляра структуры. Для того чтобы сослаться в команде на поле некоторой структуры, используется специальный(точка):
адресное_выражение.имя_поля_структуры
Здесь:
я— идентификаторнекоторого
типа или выражение в скобках в соответствии с указаннымисинтакси
ческими правилами (рис. 13.1);
*— имя поля из шаблона структуры (это на самом деле тоже
адрес, а точнее, смещение поля от начала структуры).
Таким образом,(точка) вычисляет выражение+
+
—г^Т)~] Имя регистраН—г(Т) Константа
Константа
Рис.Синтаксис адресного выражения в операторе обращения к полю структуры
Продемонстрируем с помощью определенной нами структуры worker некоторые приемы работы со структурами. К примеру, требуется извлечь в регистр АХ значения поля с возрастом. Так как вряд ли возраст трудоспособного человека может быть больше 99 лет, то после помещения содержимого этого символьного поля в регистр АХ его будет удобно преобразовать в двоичное командой AAD (см. главу 8). Будьте внимательны, так как из-за принципа хранения данных «младший байт по младшему адресу» старшая цифра возраста будет
щенав AL, а младшая — в АН. Для корректировки достаточно использовать команду xchg al,ah:
mov ax,word ptr sotrl.age ;в al возраст sotrl
xchg ah,al
;a можно и так : lea bx,sotrl mov ax,word ptr [bx].age xchg ah, al
Давайте представим, что сотрудников не четверо, а намного больше, и к тому же их число и информация о них постоянно меняются. В этом случае теряется смысл явного определения переменных с типом worker для конкретных личностей. Ассемблер разрешает определять не только отдельную переменную с типом структуры, но и массив структур. К примеру, определим массив из 10 структур типа worker:
mas_sotrworker 10 dup (<>)
Дальнейшая работа с массивом структур производится так же, как и с одномерным массивом. Здесь возникает несколько вопросов. Как быть с размером и как организовать индексацию элементов массива?
Аналогично другим идентификаторам, определенным в программе, транслятор назначает имени типа структуры и имени переменной с типом структуры атрибут типа. Значением этого атрибута является размер в байтах, занимаемый полями структуры. Извлечь это значение можно с помощью оператора TYPE. После того как становится известным размер экземпляра структуры, организация индексации в массиве структур не представляет особой сложности. К примеру, worker struc worker ends
mas__sotr worker 10 dup (<>)
mov bx,type worker ;bx=77
lea di,mas_sotr
;извлечь и вывести на экран пол всех сотрудников: mov ex, 10
mov dl,[di].sex
...;вывод на экран содержимого
;ПОЛЯ sex структуры worker
add di,bx :к следующей структуре в массиве mas_sort loop cycl
Как выполнить копирование поля из одной структуры в соответствующее поле другой структуры? Или как выполнить копирование всей структуры? К примеру, выполним копирование поля пат третьего сотрудника в поле пат пятого сотрудника: worker struc
worker ends
mas_sotr worker 10 dup (<>)
mov bx,offset mas_sotr
mov si,(type worker)*2 ;si=77*2
add si ,bx
mov di,(type worker)*4 ;si=77*4 add di, bx
mov ex,30 rep movsb
Среди прилагаемых к книге файлов в каталоге ..\Lessonl3\struct\приведена программа обслуживания базы данных о сотрудниках1. На ее примере вы можете глубже познакомиться с тем, как организовать работу со структурами в своей программе. Возможно, для читателя имеет смысл в полном объеме исследовать эту программу только после знакомства с макрокомандами в следующей главе.
Ремесло программиста рано или поздно делает человека похожим на хорошую домохозяйку. Он, подобно ей, постоянно находится в поиске: где бы чего-нибудь сэкономить, урезать, из минимума продуктов сделать прекрасный обед. И если это удается, то и моральное удовлетворение получается ничуть не меньше, а может, и больше, чем от прекрасного обеда у домохозяйки. Степень этого удовлетворения, как мне кажется, зависит от степени любви к своей профессии. С другой стороны, успехи в разработке программного и аппаратного обеспечения несколько расслабляют программиста, и довольно часто наблюдается ситуация, когда для решения некоторой мелкой задачи привлекаются тяжеловесные средства, эффективность которых в общем случае значима только при реализации сравнительно больших проектов.
В следующих двух разделах описаны два типа данных, наличие которых в языке помогает более эффективно распоряжаться памятью, выделенной программе.
Объединения
Представим ситуацию, когда мы используем некоторую область памяти для размещения того или иного объекта программы (переменной, массива или структуры). Вдруг после некоторого этапа работы у нас отпадает надобность в этих данных. В обычном случае память остается занятой до конца работы программы. Конечно, ее можно было бы задействовать для хранения других переменных, но без принятия специальных мер нельзя изменить тип и имя данных/Неплохо было бы иметь возможность переопределить эту область памяти для объекта с другими типом и именем. Ассемблер предоставляет такую возможность в виде специального типа данных, называемого объединением. Объединение — тип данных, позволяющий трактовать одну и ту же область памяти как данные, имеющие разные типы и имена.
Описание объединений в программе напоминает описание структур, то есть сначала указывается шаблон, в котором с помощью директив описания данных перечисляются имена и типы полей:
имя_объединения UNION
<описание полей> имя_объединения ENDS
Отличие объединений от структур состоит, в частности, в том, что при определении переменной типа объединения память выделяется в соответствии с разме-
Все прилагаемые к книге файлы можно найти по адресу http://www.piter.com/download. — Примеч. ред.
ром максимального элемента. Обращение к элементам объединения происходит по их именам, но при этом нужно, конечно, помнить, что все поля в объединении накладываются друг на друга. Одновременная работа с элементами объединения исключена. В качестве элементов объединения можно использовать и структуры.
Листинг 13.6, который мы сейчас рассмотрим, примечателен тем, что кроме демонстрации собственно типа данных объединение, в нем показывается возможность взаимного вложения структур и объединений. Постарайтесь внимательно отнестись к анализу этой программы. Основная идея здесь в том, что указатель на память, формируемый программой, может быть представлен в виде:
смещения;
32-разрядного смещения;
пары из 16-разрядного смещения и 16-разрядной сегментной составляющей адреса;
пары из 32-разрядного смещения и 16-разрядного селектора.
Какие из этих указателей можно применять в конкретной ситуации, зависит от режима адресацииили use32) и режима работы процессора. Шаблон объ
единения, описанный в листинге 13.6, позволяет упростить формирование и использование указателей различных типов.
ЛистингПример использования объединения
masm
model small stack256
структура pnt, содержащая вложенное объединение описание вложенного в структуру объединенияструктура pnt, содержащая вложенное объединение описание вложенного в структуру объединенияконец описания объединенияконец описания структурыопределение объединения, содержащего вложенную структуруконец описания объединенияконец описания структурыопределение объединения, содержащего вложенную структурудля тестирования "<> ;определение экземпляра объединениядля тестирования "<> ;определение экземпляра объединения.586P
структура pnt, содержащая вложенное объединение описание вложенного в структуру объединения
структура pnt, содержащая вложенное объединение описание вложенного в структуру объединения
конец описания объединения
конец описания структуры
определение объединения, содержащего вложенную структуру
конец описания объединения
конец описания структуры
определение объединения, содержащего вложенную структуру
для тестирования "
<> ;определение экземпляра объединения
для тестирования "
<> ;определение экземпляра объединения
pnt struc union
offs_16
offs_32 dd ends
segmdw
ends
.data
point union
off_16 dw ?
off_32 dd ?
point_16pnt <> point_32pnt <> pointends
tst db "Строка adr_datapoint
.code main:
mov
mov
movtst
адрес сегмента строки tst в поле структуры adr_data mov
;когда понадобится, можно извлечь значение из этого поля обратно,
;к примеру, в регистр mov
;формируем смещение в поле структуры adr_data
movtst;смещение строки в ах
mov
Л истинг13.6(продолжение)
аналогично, когда понадобится, можно извлечь ;значение из этого поля:
mov bx,adr_data.point_16.offs_16
exit:
mov ах,4c00h
int 21h end main
Когда вы будете работать в защищенном режиме процессора и использовать 32-разрядные адреса, то аналогичным способом можете заполнить и задействовать описанное ранее объединение.
Записи
На практике довольно часто возникает необходимость работы с различными программными индикаторами со значениями «включено—выключено». Минимальная величина для операций с памятью — байт. А для программного индикатора достаточно одного бита. То есть из восьми разрядов нужно задействовать всего один. При большом количестве таких индикаторов расход оперативной памяти может быть весьма ощутимым.
Когда мы знакомились с логическими командами, то говорили, что их можно применять для решения подобной проблемы. Но это не совсем эффективно, так как велика вероятность ошибок, особенно при составлении битовых масок. Компиляторы TASM и MASM предоставляют в распоряжение программиста сиеци- альный тип данных, использование которого помогает решить проблему работы с битами более эффективно. Речь идет о специальном типе данных — записях. Запись — структурный тип данных, состоящий из фиксировашюго числа элементов длиной от одного до нескольких битов.
При описании записи для каждого элемента указывается его длина в битах и, что необязательно, некоторое значение. Суммарный размер записи определяется суммой размеров ее полей и не может быть более 8,16 или 32 битов. Если суммарный размер записи меньше указанных значений, то все поля записи «прижимаются» к младшим разрядам.
Использование записей в программе, так же как и структур, организуется в три этапа.
Задание шаблона записи, то есть определение набора битовых полей, их длин и, при необходимости, инициализация полей.
Определение экземпляра записи. Так же как и для структур, этот этап подразумевает инициализацию конкретной переменной типом заранее определенной с помощью шаблона записи.
Организация обращения к элементам записи.
Описаниезаписи
Описание шаблона записи имеет следующий синтаксис: имя записи RECORD <описание элементов>
Здесь <описание элементов> представляет собой последовательность описаний отдельных элементов записи согласно синтаксической диаграмме (рис. 13.2).
т Имя записи |~] RECORD Н Имя画^1~0^~Размерт Имя записи |~] RECORD Н Имя画^1~0^~Размер0——Рис. 13.2. Синтаксис описания шаблона записи0——Рис. 13.2. Синтаксис описания шаблона записик^НЗн&че 丽 $Ук^НЗн&че 丽 $У
т Имя записи |~] RECORD Н Имя画^1~0^~Размер
т Имя записи |~] RECORD Н Имя画^1~0^~Размер
0——
Рис. 13.2. Синтаксис описания шаблона записи
0——
Рис. 13.2. Синтаксис описания шаблона записи
к^НЗн&че 丽 $У
к^НЗн&че 丽 $У
При описании шаблона память не выделяется, так как это всего лишь информация для транслятора ассемблера о структуре записи. Так же как и для структур, местоположение шаблона в программе может быть любым, но при этом необходимо учитывать логику работы однопроходного транслятора.
Определение экземпляра записи
Для использования шаблона записи в программе необходимо определить переменную с типом данной записи, для чего применяется синтаксическая конструкция, показанная на рис. 13.3.
Анализируя эту синтаксическую диаграмму, можно сделать вывод, что инициализация элементов записи осуществляется довольно гибко. Рассмотрим несколько вариантов инициализации.
Если инициализировать поля не требуется, то дстаточно указать символ ? (вопрос) при определении экземпляра записи:
iotest, record 11:1.12:2=11,13:1,14:2=11,15:2=00
flagflagiotestiotest??
flag
flag
iotest
iotest
?
?
Если составить тестовый пример с таким определением записи, то в отладчике можно увидеть, что все поля переменной типа запись flag обнуляются. Это происходит несмотря на то, что в определении записи заданы начальные значения полей.
Если требуется частичная инициализация элементов, то они заключаются в угловые (< и >) или фигурные ({ и }) скобки. Различие здесь в том, что в угловых
скобках элементы должны быть заданы в том же порядке, что и в определении записи. Если значение некоторого элемента совпадает с начальным, то его можно не указывать, но обязательно обозначить запятой. Для последних элементов идущие подряд запятые можно опустить.
К примеру, согласиться со значениями по умолчанию можно так:
iotest record 11:1,12:2=11,13:1,14:2=11,15:2=00
flagiotest <> ;согласились со значением по умолчанию
Изменить значение поля i2 можно так:
iotest record 11:I,i2:2=ll,i3:1,14:2=11.15:2=00
flagiotest <■10,> ; переопределили 12
Применяя фигурные скобки, можно провести также выборочную инициализацию полей, но при этом обозначать запятыми поля, со значениями по умолчанию которых мы согласны, не обязательно:
iotest record 11:1,12:2=11,13:1,14:2=11,15:2=00
flagiotest {12=10} переопределили 12, не обращая
;внимания на порядок следования других компонентов записи
Работа с записями
Как организовать работу с отдельными элементами записи? Обычные механизмы адресации здесь бессильны, так как они работают на уровне ячеек памяти, то есть байтов, а не отдельных битов. Здесь программисту нужно приложить некоторые усилия. Прежде всего, для понимания проблемы нужно усвоить несколько моментов.
II Каждому имени элемента записи ассемблер присваивает числовое значение, равное количеству сдвигов вправо, которые нужно произвести для того, чтобы этот элемент оказался «прижатым» к началу ячейки. Это дает нам возможность локализовать его и работать с ним. Но для этого нужно знать длину элемента в битах.
^ Сдвиг вправо производится с помощью команды сдвига SHR.
Ассемблер содержит операторWIDTH, который позволяет узнать размер элемента записи в битах или полностью размер записи. Варианты применения оператора WIDTH:
Р width имя_элемента_записи — значением оператора будет размер элемента в битах;
□ width имя_экземпляра_записи или width имя„типа_записи — значением оператора будет размер всей записи в битах.
Например, mov al.width 12 mov ax,width iotest ж Ассемблер содержит оператор MASK, который позволяет локализовать биты нужного элемента записи. Эта локализация производится путем создания маски,
размер которой совпадает с размером записи. В этой маске должны быть обнулены биты во всех позициях, за исключением тех, которые занимает элемент в записи.
Сами действия по преобразованию элементов записи производятся с помощью логических команд.
Подчеркнем еще раз то обстоятельство, что непосредственное обращение к конкретному элементу записи невозможно. Для этого нужно сначала выделить его, сдвинуть при необходимости к младшим разрядам, выполнить требуемые действия и поместить обратно на свое место в записи. Поэтому, чтобы вам каждый раз «не изобретать велосипед», далее мы опишем типовые алгоритмы осуществления этих операций над элементами записи. Вам останется лишь оформить эти алгоритмы в виде кода соответствии с требованиями конкретной задачи.
Для выделения элемента записи требуется выполнить описанную далее процедуру.
Поместить запись во временную память — регистр (8-, 16- или 32-разрядный в зависимости от размера записи).
Получить битовую маску, соответствующую элементу записи, с помощью оператора MASK.
Локализовать биты в регистре с помощью маски и команды AND.
Сдвинуть биты элемента к младшим разрядам регистра командой SHR. Число разрядов для сдвига получить с использованием имени элемента записи.
В результате этих действий элемент записи будет локализован в начале рабочего регистра, и далее с ним можно производить любые действия (см. далее).
В ходе предыдущих рассуждений мы показали, что с элементами записи производятся любые действия, как с обычной двоичной информацией. Единственное, что нужно отслеживать, — это размер битового поля. Если, к примеру, размер поля увеличится, то впоследствии может произойти случайное изменение соседних полей битов. Поэтому желательно на этапе проектирования предусматривать все варианты функционирования программы с тем, чтобы исключить любые изменения размеров полей.
Измененный элемент помещается на его место в запись следующим образом.
Используя имя элемента записи в качестве счетчика сдвигов, сдвинуть влево биты элемента записи.
Если вы не уверены в том, что разрядность результата преобразований не превысила исходную, можно выполнить «обрезание» лишних битов, используя команду AND и маску элемента.
Подготовить исходную запись к вставке измененного элемента путем обнуления битов в записи на месте этого элемента. Это можно сделать наложением командой AND инвертированной маски элемента записи на исходную запись.
С помощью команды OR наложить значение в регистре на исходную запись.
В качестве примера рассмотрим листинг 13.7, в котором поле i2 в записи iotest обнуляется.
10 Зак. 256
Листинг 13.7. Работа с полем записи
;P「g」3_7.asm
masm"
modelsmall
stack 256
iotest record 11:1,12:2=11,13:1,14:2=11,15:2=00 .data
flag iotest <>
.code
main:
mov ax,@data
mov ds,ax
mov al,mask i2
shr al,i2;биты i2 в начале ах
and al,0fch;обнулили i 2
;помещаем i 2 на место
shl al,i 2
movbl,[flag]
xor bl.mask i2 ;сбросили i2
or bl,al;наложили
exi t:
mov ax, 4c00h стандартный выход
int 21h
end main;конец программы
В заключение стоит еще раз проанализировать приведенные сведения о записях и особенностях работы с ними. При этом обратите внимание на то обстоятельство, что мы нигде явно не просчитываем расположение битов. Поэтому, если понадобится изменить размер элемента или его начальное значение, то достаточно лишь внести изменения в экземпляр записи или в описание ее типа. Функциональную часть программы, работающую с этой записью, трогать не нужно.
Дополнительные возможности обработки
Понимая важность типа данных «запись» для эффективного программирования, разработчики транслятора TASM, начиная с версии 3.0, включили в систему его команд две дополнительные команды на правах директив. Последнее означает, что эти команды внешне имеют формат обычных команд ассемблера, но после трансляции они приводятся к одной или нескольким машинным командам. Введение этих команд в язык TASM повышает наглядность работы с записями, оптимизирует код и уменьшает размер программы. Эти команды позволяют скрыть от программиста действия по выделению и установке отдельных полей записи (см. ранее).
Для установки значения некоторого поля записи используется команда SETFIELD с синтаксисом:
setfield имя_элемента_записи приемник,регистр_источник
Для выборки значения некоторого поля записи используется команда GETFIELD с синтаксисом:
getfield имя_элемента_записи регистр_приемник,источник
Работа команды SETFIELD заключается в следующем. Местоположение записи определяется операндом приемник, который может представлять собой имя регистра или адрес памяти. Операнд имя_элемента__записи определяет элемент записи, с которым ведется работа (по сути, если вы были внимательны, он определяет смещение элемента в записи относительно младшего разряда). Новое значение, в ко-
торое необходимо установить указанный элемент записи, должно содержаться воперандерегистр_источник. Обрабатываяданнуюкоманду,трансляторгенериру- ет последовательность команд, которые выполняют следующие действия.
Сдвиг содержимого операнда регистр_источник влево на количество разрядов, соответствующее расположению элемента в записи.
Выполнение логической операции OR над операндами приемник и регистр_ис- точник. Результат операции помещается в операнд приемник.
Важно отметить, что SETFIELD не производит предварительной очистки элемента, в результате после логического сложения командой OR возможно наложение старого содержимого элемента и нового устанавливаемого значения. Поэтому требуется предварительно подготовить поле в записи путем его обнуления.
Действие команды GETFIELD обратно действию SETFIELD. В качестве операнда источник может быть указан либо регистр, либо адрес памяти. В регистр, указанный операндомрегистр_приемник, помещается результат работы команды — значе- ниеэлементазаписи. Интереснаяособенностьсвязанасоперандомрегистр_прием- ник. Команда GETFIELD всегда использует 16-разрядный регистр, даже если вы укажете в этой команде имя 8-разрядного регистра.
В качестве примера применения команд SETFIELD и GETFIELD рассмотрим листинг 13.8.
Листинг 13.8.Работа с полями записи
;prg_13_8.asm
masm-
modelsmall
stack256
iotest record 11:1,12:2=11,13:1,14:2=11,15:2=00
.data
flag iotest <>
.code
main:
mov ax,@data
mov ds,ax
mov al,flag
mov bl,3
setfield 15 al,bl
xorbl,bl
getfield 15 bl,al
mov bl , 1
setfield 14 al,bl
setfield 15 al,bl
exit:
mov ax,4c00h;стандартный выход
int 21h
end main;конец программы
В листинге 13.8 демонстрируется порядок извлечения и установки некоторых полей записи. Результат работы команд SETFIELD и GETFIELD удобнее всего изучать в отладчике. При установке значений полей не производится их предварительная очистка. Это сделано специально. Для такого рода операций лучше использовать некоторые универсальные механизмы, иначе велик риск внесения ошибок, которые трудно обнаружить и исправить. В качестве такого механизма можно предложить макрокоманды, к рассмотрению которых мы и приступим в следующей главе.
В заключение хотелось бы привести еще один пример использования записей. Это описание регистра EFLAGS. Для удобства мы разбили описание на три части:
eflags_l_7 — младший байт регистра EFLAGS/FLAGS;
я eflags_8_15 — второй байт регистра EFLAGS/FLAGS;
т eflags_h — старшая половина регистра EFLAGS.
eflags_l_7record
sf7:1=9,zf6:1=0,c5:1=0,af4:1=0,c3:1=0,pf2:1=0,cl:=l,cfO:1=0
eflags_l_15 record
C15:1=0,ntl4:1=0,iopl:2=0,ofll:l=0,df10:1=0,1f9:l=l,tf8:l=0 eflags』record c:13=0,acl8:1=0,vml7:1=0,rf16:1=0
К этому описанию стоит вернуться после изучения материала следующей главы, посвященного макрокомандам. Возможно, что для работы с регистром флагов вам захочется разработать соответствующую макрокоманду. Подобная хорошо протестированная макрокоманда в будущем поможет вам избежать многих трудно обнаруживаемых ошибок.
Итоги
TASM поддерживает несколько дополнительных типов данных, значительно расширяющих возможности базовых директив резервирования и инициализации данных. По сути, эти типы заимствованы из языков высокого уровня и призваны облегчить разработку прикладных программ на ассемблере.
Практическое использование дополнительных типов данных требует повышенного внимания и отражает специфику программирования на языке ассемблера.
嚣 Понятия массива и индексации массива весьма условны, и логическая интерпретация области памяти, отведенной под массив, определяется алгоритмом обработки.
魏^ Тип структуры в языке ассемблера позволяет создать совокупность логически взаимосвязанных разнотипных данных и рассматривать их как отдельный объект. Это очень удобно, когда в программе необходимо иметь несколько таких объектов. В этом случае обычно организуют массив структур.
is Основное достоинство объединений — в возможности «плюрализма суждений» о типе одной и той же области памяти.
в Записи в языке ассемблера расширяют возможности логических команд для работы на уровне битов, что подчеркивает значение ассемблера как языка системного программирования.
Глава 14
Макросредства языка ассемблера
Понятие о макросредствах языка ассемблера
Псевдооператоры EQU и =
Макрокоманды и макродирективы
Директивы условной компиляции
飘Директивы генерации ошибок пользователя
Директивы управления листингом
Язык ассемблера не относится к простым языкам программирования. По истечении некоторого времени после начала программирования на ассемблере становятся видны свойственные этому языку проблемы. Перечислим некоторые из них: 後 плохая читаемость исходных текстов программы — спустя некоторое время при недостаточном комментировании могут возникнуть проблемы с пониманием особенностей алгоритма, лежащего в основе программы;
經 ограниченность набора команд;
* полная или почти полная повторяемость некоторых фрагментов программы;
9 необходимость включения в каждую новую программу одних и тех же фрагментов кода и т. д.
При написании программы на машинном языке данные проблемы были бы принципиально неразрешимы. Но язык ассемблера, являясь символическим представлением машинного языка, в то же время предоставляет ряд средств более высокого уровня для их решения. Основной целью, которая при этом преследуется,
является повышение удобства написания программ. В общем случае эта цель достигается за счет:
卷爰 расширения набора директив;
введения некоторых дополнительных команд, не имеющих аналогов в системе команд процессора (за примером далеко ходить не нужно — это рассмотренные в главе 13 команды SETFIELD и GETFIELD, которые скрывают от программиста рутинные действия и генерируют наиболее эффективный код);
戀 введения сложных типов данных.
Однако этот перечень исчерпывает все глобальные направления, по которым развивается транслятор от версии к версии. Что же делать программисту для решения его локальной задачи, для адаптации процесса разработки к нуждам определенной проблемной области? Для этого разработчики компиляторов ассемблера включают в язык и постоянно совершенствуют аппарат макросредств. Этот аппарат является очень мощным и важным. В общем случае есть смысл говорить о том, что транслятор ассемблера состоит из двух частей — непосредственно транслятора, формирующего объектный модуль, и макроассемблера (рис. 14.1). Если вы знакомы с языком С или C++, то, конечно, знаете используемый в нем механизм препроцессориой обработки. Основные принципы его работы аналогичны принципам работы макроассемблера. Для тех, кто ничего раньше не слышал об этом механизме, поясню его суть. Основная идея — использование подстановок, когда определенным образом организованная символьная последовательность заменяется другой символьной последовательностью. Создаваемая последовательность может описывать как данные, так и программные коды. Главное здесь то, что на входе макроассемблера текст программы может быть весьма далеким по виду от ассемблера, но на выходе обязательно будет текст на чистом ассемблере, содержащем символические аналоги машинных команд процессора. Таким образом, обработка программы на ассемблере с использованием макросредств неявно осуществляется транслятором в две фазы. На первой фазе работает часть компилятора,
Транслятор1ASM.EXEТранслятор1ASM.EXE
Транслятор
1ASM.EXE
Транслятор
1ASM.EXE
Рис. 14.1. Макроассемблер в общей схеме трансляции программы для компилятора TASM
называемая макроассемблером, основные функции которого мы описали ранее. Во второй фазе трансляции участвует непосредственно ассемблер, задачей которого является формирование объектного кода, содержащего текст исходной программы в машинном виде.
Далее мы обсудим основной набор макросредств компилятора TASM. Отметим, что большинство этих средств доступно и в компиляторе MASM фирмы Microsoft. Обсуждение начнем с простейших средств и закончим более сложными.
Псевдооператоры EQU и =
К простейшим макросредствам языка ассемблера можно отнести псевдооператоры EQU и = (равно). Их мы уже неоднократно использовали при написании программ. Данные псевдооператоры предназначены для присвоения некоторому выражению символического имени или идентификатора. Впоследствии, когда в ходе трансляции такие идентификаторы встречаются в теле программы, макроассемблер подставляет вместо них соответствующее выражение. Выражениями могут быть константы, имена меток, символические имена и строки в апострофах. После присвоения этим конструкциям символических имен их можно использовать везде, где эти конструкции могли бы присутствовать.
Синтаксис псевдооператора EQU:
имя_идентификатора equ строка или числовое_выражение
Синтаксис псевдооператора =:
имя_идентификатора =
Несмотря на внешнее и функциональное сходство, псевдооператоры EQU и = различаются следующим:
с помощью псевдооператора EQU идентификатору можно ставить в соответствие как числовые выражения, так и текстовые строки, а псевдооператор = может использоваться только с числовыми выражениями;
Ж идентификаторы, определенные с помощью псевдооператора =, можно переопределять в исходном тексте программы, а определенные с использованием псевдооператора EQU — нельзя.
Ассемблер всегда пытается вычислить значение строки, воспринимая ее как выражение. Для того чтобы строка воспринималась именно как текстовая, необходимо заключить ее в угловые скобки:
<строка>
Угловые скобки являются оператором ассемблера, называемым оператором выделения. С его помощью транслятору сообщается, что заключенная в угловые скобки строка должна трактоваться как текст, даже если в нее входят служебные слова ассемблера или операторы.
Псевдооператор EQU удобно использовать для настройки программы на конкретные условия выполнения, замены сложных в обозначении объектов, многократно встречающихся в программе, более простыми именами и т. п. К примеру, masm modelsmall
stack 256
mas_si zeequ 10;размерность массива
akk equ ax;переименовать регистр
mas_elemequ mas[bx][si] ; адресовать элемент массива
.daTa
;описание массива из 10 байт:
mas db mas_size dup (0) . code
mov akk,@data mov ds,akk
фактически фактически
mov mov
ax,©data ds, ax
mov al,mas_elem
фактически
mov
al,mas[bx][si]
Псевдооператор = удобно использовать для определения простых абсолютных (то есть не зависящих от места загрузки программы в память) математических выражений. Главное условие — транслятор должен быть в состоянии вычислять эти выражения во время трансляции. К примеру,
.data
adrldb 5 dup (0)
adr2dw 0
;можно и так, через предыдущее определение;можно и так, через предыдущее определениеlen = 43 len =
;можно и так, через предыдущее определение
;можно и так, через предыдущее определение
len = adr2-adrl
Как видно из примера, в правой части псевдооператора = можно использовать метки и ссылки на адреса — главное, чтобы в итоге получилось абсолютное выражение.
Компилятор TASM, начиная с версии 3.00, содержит директивы, значительно расширяющие его возможности по работе с текстовыми макросами. Эти директивы аналогичны некоторым функциям обработки строк в языках высокого уровня. Под строками здесь понимается текст, описанный с помощью псевдооператора EQU. Эти директивы перечислены далее.
Директива слияния строк CATSTR:
идентификатор catstr строка_1, строка_2
Значением этого макроса будет новая строка, состоящая из сцепленной слева направо последовательности строк строка_1, строка_2 и т. д. В качестве сцепляемых строк могут быть указаны имена ранее определенных макросов. К примеру,
рге equ Привет,
nameequ < Юля>
privet catstr pre,name;privet= "Привет, Юля"
•綫 Директива выделения подстроки в строке SUBSTR:
идентификатор substr строка,номер_позиции,размер
Значением данного макроса будет часть строки, заданной операндом строка, начинающаяся с позиции с номером номер_позиции и длиной, указанной операндом размер. Если требуется только остаток строки начиная с некоторой позиции, то достаточно указать номер позиции (без размера). К примеру,
;продолжение предыдущего фрагмента:
privet catstr pre,name ;privet= "Привет, Юля"
namesubstrprivet,7,3:пате="Юля"
Директива определения вхождения одной строки в другую INSTR:
идентификатор instr номер_нач_позиции,строка_1,строка一2
После обработки данного макроса транслятором операндуидентификатор будет присвоено числовое значение, соответствующее номеру (первой) позиции,
с которой совпадают операнды строка_1 и строка_2. Если такого совпадения нет, то идентификатор получит значение 0.
Директива определения длины строки в текстовом макросе SIZESTR: идентификатор sizestr строка
В результате обработки данного макроса значение идентификатор устанавливается равным длине строки:
;как продолжение предыдущего фрагмента:
privet catstr pre,name :privet= "Привет, Юля"
len sizestr privet;len=10
Эти директивы очень удобно использовать при разработке макрокоманд, которые являются следующим типом макросредств, предоставляемых компилятором ассемблера.
Макрокоманды
По смыслу макрокоманда представляет собой дальнейшее развитие механизма замены текста. С помощью макрокоманд в текст программы можно вставлять последовательности строк (которые логически могут быть данными или командами) и даже более того — привязывать их к контексту места вставки.
Представим ситуацию, когда необходимо выполнить некоторые повторяющиеся действия. Механизм макрокоманд предоставляет возможность заменить одинаковые участки кода одной строкой, а первоначальный исходный текст описать один раз в определенном месте программы или во внешнем файле. Дальнейшее наше обсуждение будет посвящено тому, как это сделать.
Определимся с терминологией. Осмысленное рассмотрение данного механизма предполагает понимание смысла терминов макрокоманда и макроопределение. Макрокоманда представляет собой строку, одним из компонентов которой является символическое имя макрокоманды. Имя макрокоманды может сопровождаться параметрами. Если данная строка встречается в теле исходного текста программы, то транслятор замещает ее одной или несколькими другими строками. Какими именно строками — определяется макроопределением, которое представляет собой шаблон (описание) макрокоманды.
Таким образом, для использования макрокоманды в программе первым делом задают ее макроопределение. Синтаксис макроопределения следующий: имя_макрокоманды macro список_формальных_аргументов тело макроопределения endm
Где должны располагаться макроопределения? Есть три варианта.
經 Макроопределения могут располагаться в начале исходного текста программы до сегмента кода и данных с тем, чтобы не ухудшать читабельность программы. Этот вариант следует применять в случаях, если определяемые вами макрокоманды актуальны только в пределах одной этой программы.
* Макроопределения могут располагаться в отдельном файле. Этот вариант подходит при работе над несколькими программами одной проблемной области. Чтобы сделать доступными эти макроопределения в конкретной программе,
необходимо в начале исходного текста этой программы записать директиву include имя_файла, к примеру:
masm
modelsmall
i nclude show.inc
;в это место будет вставлен текст файла show.1nc
Макроопределения могут располагаться в макробиблиотеке. Если у вас есть универсальные макрокоманды, которые используются практически во всех ваших программах, то их целесообразно записать в так называемую макробиблиотеку. Сделать актуальными макрокоманды из этой библиотеки можно с помощью все той же директивы INCLUDE. Недостаток этого и предыдущего способов — в том, что в исходный текст программы включаются абсолютно все макроопределения. Для исправления ситуации можно задействовать директиву PURGE, в качестве операндов которой через запятую перечисляются имена макрокоманд, которые не должны включаться в текст программы. К примеру,
include i omac. i nc
purge outstr.exit
В данном случае в исходный текст программы перед началом компиляции транслятор TASM вместо строкиinclude iomac.inc вставит строки из файлаiomac.inc, отличающиеся от оригинала тем, что в этих строках будут отсутствовать макроопределения OUTSTR и EXIT.
А теперь вернемся к главе б и вспомним программу из листинга 6.1. Проанализируем ее текст, выявим повторяющиеся участки и составим для них макроопределения (листинг 14.1).
Листинг 14_1. Первый пример создания и использования макрокоманд
1>;prg_6_l.asm с макроопределениями
2>init_ds macro
3>;Макрос настройки ds на сегмент данных
4>movax,data
5>movds, ax
6>endm
7>out_strmacrostr
8>;Макрос вывода строки на экран.
9>;Ha входе - выводимая строка.
10>;H a выходе - сообщение на экране.
11>push ax
12>movah,09h
13>movdx,offset str
14>int21h
15>popax
16>endm
<17>
18.>clear_r macrorg
19>;очистка регистра rg
20>xor rg,rg
21>endm
<22>
23>get_char macro
24>:ввод символа
25>;введенный символв al
26>mov ah. Ih
27>int 21h
28>endm
<29>
30>conv_16_2 macro
31> ;макрос преобразования символа шестнадцатеричной цифры
32>;в ее двоичный эквивалент в al
33>sub al,30h
34>cmp,9h
35>j te$+4
36>subal,7h
37>endm
<38>
39>exit macro
40>;макрос конца программы
41>mov ax,4c00h
42>int 21h
43>endm
<44>
45>datasegment para public "data"
46>message db "Введите две шестнадцатеричные цифры (буквы
A,B,C,D,E,F -
47>dataends
48>stk segment stack
49>db 256 dup(”?”)
5®>stk ends
51>
52>codesegment para public "code"
53>assumecs:code,ds:data,ss:stk
54>mainproc
55>n i t_d s
56>out_strmessage
57>clear_rax
58>get_char
59>conv_16_2
60>mov dl, al
61>mov cl,4h
62>shl dl,cl
63>get_char
64>conv_16_2
65>add dl,51
66>xchgdl,al :результат в al
67>exit
68>main endp
69>code ends
70>end main
В листинге 14.1 в строках 2-7,8-16,18-21,23-28,30-37, 39-43 описаны макроопределения. Описание их назначения приведено сразу после заголовка в теле каждого макроопределения. Если вынести эти макроопределения в отдельный файл, то впоследствии можно использовать их при написании других программ. Посмотрите на модернизированный исходный текст программы из листинга 6.1 в листинге 14.1 (строки 54-68). Если не обращать внимания на некоторые неясные моменты, то сам сегмент кода стал внешне более читабельным, и даже можно сказать, что в его новых командах появился какой-то смысл. Откомпилируйте листинг 14.1 и получите файл листинга. После этого сравните текст программы до и после обработки ассемблером. Вы увидите, что исходный текст изменился, — после строк программы, в которых были макрокоманды, появились фрагменты текста. Содержимое этих фрагментов определяется содержимым макроопределений (шаблонов), заданных в начале программы. Видно, что путем ввода параметров
для макрокоманды можно модернизировать содержимое фактического текста, замещающего макрокоманду.
Функционально макроопределения похожи на процедуры. Сходство в том, что и те, и другие достаточно один раз где-то описать, а затем вызывать их специальным образом. На этом сходство заканчивается и начинаются различия, которые в зависимости от целевой установки можно рассматривать и как достоинства, и как недостатки.
變 В отличие от процедуры, текст которой неизменен, макроопределение в процессе макрогенерации может меняться в соответствии с набором фактических параметров. При этом коррекции могут подвергаться как операнды команд, так и сами команды. Процедуры в этом отношении менее гибки.
醛 При каждом вызове макрокоманды ее текст в виде макрорасширения вставляется в программу. При вызове процедуры процессор осуществляет передачу управления на начало процедуры, находящейся в некоторой области памяти в одном экземпляре. Код в этом случае получается более компактным, хотя быстродействие несколько снижается за счет необходимости осуществления переходов.
Макроопределение обрабатывается компилятором особым образом. Для того чтобы использовать описанное макроопределение, его нужно «активизировать» с помощью макрокоманды. Для этого в нужном месте исходного кода программы на основе текста заголовка макроопределения указывается следующая синтаксическая конструкция:
имя_макрокоманды список_фактических_аргументов
В результате применения данной синтаксической конструкции соответствующая строка исходного текста программы заменяется строками из тела макроопределения. Но это не простая замена. Обычно макрокоманда содержит некоторый списокаргументов(список_фактических_аргументов),которымикорректируется макроопределение. Места в теле макроопределения, которые будут замещаться фактическими аргументами из макрокоманды, обозначаются с помощью так называемых формальных аргументов. Таким образом, в результате применения макрокоманды в программе формальные аргументы в макроопределении замещаются соответствующими фактическими аргументами; в этом и заключается учет контекста. Процесс такого замещения называется макрогенерацией, а результатом этого процесса является макрорасширение.
К примеру, рассмотрим самое короткое макроопределение в листинге 14.1 — clear_rg. Как отмечено ранее, результаты работы макроассемблера можно узнать, просмотрев файл листинга после трансляции. Покажем несколько его фрагментов, которые демонстрируют, как был описан текст макроопределения clear_rg (строки 24-27), как был осуществлен вызов макрокоманды clear_rg с фактическим параметром ах (строка 74) и как выглядит результат работы макрогенератора, сформировавшего команду ассемблера xor ах,ах (строка 75): <24>clear_rmacrorg
25>;очистка регистра rg
2б>xor
27>endm
<74>clear_r ax
<75>000E 33 C0 xor ax,ax
В итоге мы получили то, что и требовалось, — команду очистки заданного регистра, в данном случае АХ. В другом месте программы вы можете выдать ту же макрокоманду, но уже с другим именем регистра. Если у вас есть желание, можете провести эксперименты с этой и другими макрокомандами. Каждый фактический аргумент макрокоманды представляет собой строку символов, для формирования которой применяются определенные правила.
■幾 Строка может состоять:
из последовательности символов без пробелов, точек, запятых, точек c запятой;
из последовательности любых символов, заключенных в угловые скобки (<.. .>), в которой можно указывать как пробелы, так и точки, запятые, точки с запятыми (мы упоминали об этом операторе выделения ассемблера при обсуждении директивы EQU).
後 Для того чтобы указать, что некоторый символ внутри строки, представляющей фактический параметр, является собственно символом, а не чем-то иным, например, некоторым разделителем или ограничивающей скобкой, применяется специальный оператор !. Этот оператор ставится непосредственно перед нужным символом, и его действие эквивалентно заключению этого символа в угловые скобки. Таким образом, оператор ! также является оператором выделения, но одиночного символа.
я Для вычисления в строке, представляющей фактический параметр в макрокоманде, некоторого константного выражения в начале этого выражения нужно поставить знак % (процент):
% константное_выражение
Значение константное_выражение вычисляется и подставляется в текстовом виде в соответствии с текущей системой счисления*.
Теперь обсудим вопрос, как транслятор распознает формальные аргументы в теле макроопределения для их последующей замены фактическими аргументами?
Прежде всего по их именам в заголовке макроопределения. В процессе генерации макрорасширения компилятор ассемблера ищет в тексте тела макроопределения последовательности символов, совпадающие с теми последовательностями символов, из которых состоят формальные параметры. После обнаружения такого совпадения формальный параметр из тела макроопределения замещается соответствующим фактическим параметром из макрокоманды. Этот процесс называется подстановкой аргументов. Здесь нужно отметить еще раз особо список формальных аргументов в заголовке макроопределения. В общем случае он может содер-
1 Под текущей системой счисления понимается то, как интерпретируются транслятором числа или строки с фиксированным числовым значением — как двоичные, десятичные или шестнадцатгрич- ные числа. По умолчанию транслятор трактует их какдесятичные. Ассемблер имеет специальную директиву .radix, которая дает возможность изменить текущую системусчисления. В качестве операнда директивы .radixможет быть значение2,10или 16, что означает выбор, соответственно, двоичной, десятичной или шестнадцатеричнойсистемы счисления.
жать не только разделенные запятыми формальные элементы, но и некоторую дополнительную информацию. Полный синтаксис формального аргумента следующий:
имя_формального_аргумента(:тип]
Здесь тип может принимать значения:
REQ — это значение говорит о том, что требуется обязательное явное задание фактического аргумента при вызове макрокоманды;
т =<любая_строка> — если аргумент при вызове макрокоманды не задан, то в соответствующие места в макрорасширении по умолчанию будет вставлено значение любая_строка (будьте внимательны: символы, входящие в операнд любая_ строка, должны быть заключены в угловые скобки).
Однако распознать в теле макроопределения формальный аргумент ассемблер может не всегда. Это, например, может не произойти в случае, когда он является частью некоторого идентификатора. В этом случае последовательность символов формального аргумента отделяют от остального контекста с помощью специального оператора замены (символа &). Этот прием часто используется для задания модифицируемых идентификаторов и кодов операций. К примеру, определим макрос, который предназначен для генерации в программе некоторой таблицы, причем параметры этой таблицы можно задавать с помощью аргументов макрокоманды:
def table macrotype:=b Дen:REQ
tabT_&type d&type len dup (0) endm~
.data
def_tablb,10
def_tablw, 5
После трансляции текста программы, содержащей эти строки, получатся следующие макрорасширения: tabl_b db 10 dup (0) tabl_w dw 10 dup (0)
Символ & можно применять и для распознавания формального аргумента в строке, заключенной в двойные кавычки (""). Например, num_char macromessage
;подсчитать количество (num) символов в строке
jmp ml
elemdb "Строка &message содержит"
;число символов в строке message в коде ASCII num db 2 dup (0)
db ” символови,10,13.'$';конец строки
;дпя вывода функцией 09h ml:
;вывести elem на экран endm
В связи с последним фрагментом разберем ситуацию, когда тело макроопределения содержит метку или имя в директиве резервирования и инициализации данных. Если некоторая макрокоманда вызывается в программе несколько раз, то при
макрогенерации один и тот же идентификатор будет определен несколько раз, что, естественно, транслятор посчитает ошибкой. Для выхода из подобной ситуации применяют директиву LOCAL, которая имеет следующий синтаксис:
local список_идентификаторов
Эту директиву необходимо указывать непосредственно за заголовком макроопределения. Результатом работы директивы LOCAL будет генерация в каждом экземпляре макрорасширения уникальных имен для всех идентификаторов, перечисленных в операнде список_идентификаторов. Эти уникальные имена имеют вид ??хххх, где хххх — шестнадцатеричное число. Для первого идентификатора в первом экземпляре макрорасширения хххх = 0000, для второго — хххх = 0001 и т. д. Контроль правильности размещения и использования этих уникальных имен берет на себя транслятор ассемблера. Для того чтобы окончательно разобраться в деталях, введем и оттранслируем листинг 14.2. В нем помимо некоторых ранее рассмотренных макрокоманд содержится макрокоманда num_char. Ее назначение — подсчитывать количество символов в строке, адрес которой передается этой макрокоманде в качестве фактического параметра. Строка должна удовлетворять требованию, предъявляемому к строке, предназначенной для вывода на экран функцией 09h прерывания 21h, то есть заканчиваться символом $. Другой момент, который нашел отражение в этой программе, — использование символа & для распознавания формального аргумента в строке, заключенной в кавычки (см. последний фрагмент).
Листинг 14.2. Второй пример создания и использования макрокоманд
;prg_14_2.asm init_ds macro ;макрос настройки ds на сегмент данных mov ах.data mov ds, ах xor ах, ах endm
out_str macro str
;макрос вывода строки на экран. ;На входе - выводимая строка.
;На выходе - сообщение на экране.
push ax
mov ah,09h
mov dx,offset str int 21h
pop ax endm exit macro ;макрос конца программы mov ax,4c00h int 21h endm
num_charmacromessage
local ml,elem(num,err_nies.find,num_exi t ;макрос подсчета количества "символов в строке. ;Длина строки - не более 99 символов.
;Вход: message - адрес строки символов, ограниченной "J"
;Выход: в al - количество символов в строке message
:и вывод сообщения
jmp ml
elem db "Строка &message содержит "
продолжение 按
Листинг 14.2 (продолжение)
num db 2 dup (0)db ” символов",10,13,'$'erjmes db"Строка &message неml:num db 2 dup (0)db ” символов",10,13,'$'erjmes db"Строка &message неml:;число символов в строке
num db 2 dup (0)
db ” символов",10,13,'$'
erjmes db"Строка &message не
ml:
num db 2 dup (0)
db ” символов",10,13,'$'
erjmes db"Строка &message не
ml:
;message в коде ASCII
;конец строки
;для вывода функцией 09h
содержит символа конца строки" ,10,13/$
сохраняем используемые в макросе регистры
push es
push ex push ax push di push ds pop es mov$
cld
lea di.message
pushdi mov ex,99
repnescasb
je find
;настройка es на ds
;символ для поиска - "$”
;сброс флага df
;загрузка в es:di смещения
;строки message
;запомним di - адрес начала строки
;ДЛЯ префикса repne - максимальная
;длина строки
;поиск в строке (пока нужный символ
;и символ в строке не равны)
;выход - при первом совпавшем
;если символ найден - переход на обработку
;вывод сообщения о том, push ds
что символ не найден
;подставляем cs вместо
push cs
pop ds
out_str err_mes
pop ds
jmp num_exit
find:
pop ax
sub di, ax
xchg di ,ax
sub al,3
aam
or ax 3030h
mov cs :num, ah
mov cs num+1,al ;вывести elem на экран
push ds
ds
для
функции 09h (int21h)
;выход из макроса
;совпали
;считаем количество символов в строке:
;восстановим адрес начала строки
;(di) = (di)-(ax)
; (di) < > (ax)
;корректировка на служебные
;символы - 10, 13, "$"
;в al две упакованные BCD-цифры результата подсчета
;преобразование результата в код ASCII
;подставляем cs вместо push cs
pop ds
out_str elem
pop ds
num_exi t:
push di
push ax
push ex push es endm
ds
для
функции 09h (int21h)
datasegment para public "data"
msg_ldb "Строка_1 для испытания",10,13,'$'
耳 Sg一2db "Строка二2 для второго испытания",10,13,*$
data ends
stk segment stack
db 256 dup("?") stk ends
codesegment para public "code"
assume cs:code,ds:data(ss:stk
mainproc
1ni t_ds
out_str msg_l
num_charmsg_l
out 二 str~msg_2
num2charmsg_2 ~
exit-
main endp code ends end main
В теле макроопределения можно размещать комментарии, и делать это особым образом. Если применить для обозначения комментария не один, как обычно, а два последовательных символа точки с запятой (;;), то при генерации макрорасширения этот комментарий будет исключен. Если по какой-то причине необходимо присутствие комментария в макрорасширении, то его нужно задавать обычным образом, то есть с помощью одного символа точки с запятой, например: mes macromesssage
...;этот комментарий будет включен в текст листинга
...:;этот комментарий не будет включен в текст листинга
endm
Макродирективы
С помощью макросредств ассемблера можно не только частично изменять входящие в макроопределение строки, но и модифицировать сам набор этих строк и даже порядок их следования. Сделать это можно с помощью набора макродиректив (далее — просто директив). Их можно разделить на две группы.
趣Директивы повторения WHILE, REPT, IRP и IRPC предназначены для создания макросов, содержащих несколько идущих подряд одинаковых последовательностей строк. При этом возможна частичная модификация этих строк.
я Директивы управления процессом генерации макрорасширений EXITM и GOTO предназначены для управления процессом формирования макрорасширения из набора строк соответствующего макроопределения. С помощью этих директив можно как исключать отдельные строки из макрорасширения, так и вовсе прекращать процесс генерации. Директивы EXITM и GOTO обычно используются вместе с условными директивами компиляции, поэтому они будут рассмотрены вместе с ними.
Директивы WHILE и REPT
Директивы WHILE и REPT применяются для повторения определенное количество раз некоторой последовательности строк. Эти директивы имеют следующий синтаксис: WHILE константное_выражение последовательность__строк ENDM-
REPT константное_выражение
последовательность строк
ENDM
Обратите внимание на то, что последовательность повторяемых строк в обеих директивах ограничена директивой ENDM.
При использовании директивы WHILE макрогенератор транслятора будет повторять последовательность строк до тех пор, пока значение константное_выра- жение не станет равным нулю. Это значение вычисляется каждый раз перед очередной итерацией цикла повторения (то есть значение константное„выражение в процессе макрогенерации должно подвергаться изменению внутри последовательности строк).
Директива REPT, подобно директиве WHILE, повторяет последовательность строк столько раз, сколько это определено значением константное_выражение. Отличие этой директивы от WHILE состоит в том, что она автоматически уменьшает на единицу значение константное_выражение после каждой итерации. В качестве примера рассмотрим листинг 14.3. В нем демонстрируется применение директив WHILE и REPT для резервирования области памяти в сегменте данных. Имя идентификатора и длина области задаются в качестве параметров соответствующих макросов def_sto_l и def_sto_2. Заметьте, что счетчик повторений в директиве REPT уменьшается автоматически после каждой итерации цикла. Проанализируйте результат трансляции листинга 14.3.
Листинг 14.3. Использование директив повторения
;prg_14_3.asm
defmacro id__table . In:=<5>
;макрос резервирования памяти длиной len
:Используется WHILE id-table label byte len=ln whilelen
db 0 len=len-l endm endm def_sto_2macroid_table,len
;макрос резервирования памяти длиной len ;Используется REPT id_tablelabel byte
reptlen
db 0
endm endm data segment para public 'data' def_sto_ltab_lf10
def_sto_2 tab二2,1® dataends
init_ds macro
;Макрос настройки ds на сегмент данных
mov ax,data
mov ds, ax
endm
exitmacro
;макрос конца программы
mov ax,4c00h int 21h endm
code segment para public "code"
assume cs:code,ds:data
mainproc
init_ds ex i
main endp code ends end main
Таким образом, директивы REPT и WHILE удобно применять для «размножения» в тексте программы последовательности одинаковых строк без внесения в эти строки каких-либо изменений на этапе трансляции. В частности, эти директивы можно использовать при построении элементов списочных структур [8]. Следующие две директивы, IRP и IRPC, делают процесс «размножения» более гибким, позволяя модифицировать на каждой итерации некоторые элементы последовательности строк.
Директива IRP
Директива IRP имеет следующий синтаксис:
IRP Зормапьнь启_ар「умент , <строка__символов_1строка_симвопов_п> последовател ьноеть_строк一一~*
ENDM~
Действие данной директивы заключается в том, что она повторяет последовательность строк п раз, то есть столько раз, сколько строк символов заключено в угловые скобки во втором операнде директивы IRP. Но это еще не все. Повторение последовательности строк сопровождается заменой в этих строках формального аргумента очерёдной строкой символов из второго операнда. Так, при первой генерации последовательности строк формальный аргумент в них заменяется первой строкой символов (то есть аргументом строка_символов_1). Если есть вторая строка символов (строка_символов_2), это приводит к генерации второй копии последовательности строк, в которой формальный аргумент заменяется второй строкой символов. Эти действия продолжаются до последней строки символов (строка_символов_п) включительно.
К примеру, рассмотрим результат определения в программе такой конструкции:
irp ini ,<1,2,3,4,5> db ini
endm
Макрогенератором будет сгенерировано следующее макрорасширение: db 1 db 2 db 3 db 4 db 5
ДирективаIRPC
Директива IRPC имеет следующий синтаксис:
IRPC формапьный_аргумент,строка_симвопов последовательность строк
ENDM
Действие данной директивы подобно действию директивы IRP, но отличается тем, что она на каждой очередной итерации заменяет формальный аргумент очередным символом из строки символов. Понятно, что количество повторений
последовательности строк будет определяться количеством символов в строке символов. К примеру,
i rpc rg.abcd'
pushrg&x
endm
В процессе макрогенерации эта директива развернется в следующую последовательность строк: pushax
pushbx
pushex
pushdx
Если строка символов, задаваемая в директиве IRP, содержит спецсимволы вроде точек и запятых, то она должна быть заключена в угловые скобки: <ab„cd>.
Директивы условной компиляции
Последний тип макросредств — директивы условной компиляции. Существует два вида этих директив:
煞 директивы компиляции по условию позволяют проанализировать определенные условия в ходе генерации макрорасширения и при необходимости изменить этот процесс;
I» директивы генерации ошибок по условию контролируют ход генерации макрорасширения с целью генерации или обнаружения определенных ситуаций, которые могут интерпретироваться как ошибочные.
С этими директивами применяются упомянутые ранее директивы управления процессом генерации макрорасширений EXITM и GOTO.
Директива EXITM не имеет операндов, она немедленно прекращает процесс генерации макрорасширения, как только встречается в макроопределении. Это дает возможность сократить объем исходного кода путем удаления неиспользуемых команд.
Директива GOTO имя„метки переводит процесс генерации макроопределения в другое место, прекращая тем самым последовательное разворачивание строк макроопределения. Метка, на которую передается управление, имеет специальный формат:
:имя_метки
Примеры применения этих директив будут приведены далее.
Директивы компиляции по условию
Директивы компиляции по условию предназначены для выборочной трансляции фрагментов программного кода. Это означает, что в макрорасширение включаются не все строки макроопределения, а только те, которые удовлетворяют определенным условиям. Какие конкретно условия должны быть проверены, определяется типом условной директивы. Введение в язык ассемблера этих директив значительно расширяет его возможности. Всего имеются 10 типов условных директив компиляции. Их логично попарно объединить в четыре группы:
* IF и IFE — условная трансляция по результату вычисления логического выражения;
Я IFDEF и IFNDEF — условная трансляция по факту определения символического имени;
Я IFB и IFNB — условная трансляция по факту определения фактического аргумента при вызове макрокоманды;
經 IFIDN.IFIDNIJFDIFи IFDIFI — условная трансляция по результату сравнения строк символов.
Условные директивы компиляции имеют общий синтаксис и применяются в составе следующей синтаксической конструкции:
IFxxx логическое_выражение_или_аргументы
4)ра「мент_про「раммь匚 1一 一
ELSE
фрагмент_программы_2
ENDIF
Заключение некоторых фрагментов текста программы (фрагмент_программы_1 и фрагмент_программы_2) между директивами IFxxx, ELSE и ENDIF приводит к их выборочному включению в объектный модуль. Какой именно из этих фрагментов будет включен в объектный модуль, зависит от конкретного типа условной директивы, задаваемого значением ххх, и значения условия, определяемого операндом (операндами) условной директивы логическое_выражение_или_аргумент(ы).
Директивы IF и IFE
Синтаксис директив IF и IFE следующий:
IF(E) логическое__выражение
фрагмент__программьГ1
ELSE
фрагмент_программы_2
ENDIF
Обработка этих директив макроассемблером заключается в вычислении логического выражения и включении в объектный модуль первого (фрагмент_програм- мы_1) или второго (фрагмент_программы_2) фрагмента программы в зависимости от того, в какой директиве (IF или IFE) это выражение встретилось.
經 Если в директиве IF логическое выражение истинно, то в объектный модуль помещается первый фрагмент программы. Если логическое выражение ложно, то при наличии директивы ELSE в объектный код помещается второй фрагмент программы. Если же директивы ELSE нет, то вся часть программы между директивами IF и ENDIF игнорируется, и в объектный модуль ничего не включается. Кстати сказать, понятие' истинности и ложности значения логического выражения весьма условно. Ложным оно будет считаться, если его значение равно нулю, а истинным — при любом значении, отличном от нуля.
• Директива IFE аналогично директиве IF анализирует значение логического выражения. Но теперь для включения первого фрагмента программы в объектный модуль требуется, чтобы логическое выражение было ложным.
Директивы IF и IFE очень удобно использовать для изменения текста программы в зависимости от некоторых условий. К примеру, составим макрос для опре-
деления в программе области памяти длиной не более 50 и не менее 10 байт (листинг 14.4).
Листинг 14.4. Использование условных директив IF и IFE
1>;prg_14_4.asm
2>masm
3>model small
4>stack 256
5>def_tab_50macro len
6>if len GE50
7>GOTO exit
8>endif
9>if len LT10
“@>: exit
11>EXITM
12>endif
13>rept len
14>db 0
15>endm
16>endm
17>.data
18>def_tab_5015
19>def_tab_505
20>.code -
21>main:
22>movax,@data
23>movds.ax
24>exit:
25>movax(4c00h'
26>i nt21h
27>end main
Введите и оттранслируйте листинг 14.4. Не забывайте о том, что условные директивы действуют только на шаге трансляции, и поэтому результат их работы можно увидеть лишь после макрогенерации, то есть в листинге программы. В нем вы обнаружите, что в результате трансляции строка 18 листинга 14.4 развернется в пятнадцать нулевых байтов, а строка 19 оставит макрогенератор совершенно равнодушным, так как значение фактического операнда в строках б и 9 будет ложным. Обратите внимание на то, что для обработки реакции на ложный результат анализа в условной директиве мы использовали макродирективы EXITM и GOTO. Наверное, в данном случае можно было бы составить более оптимальный вариант макрокоманды для резервирования некоторого пространства памяти в сегменте данных, а данный способ выбран, исходя из учебных целей.
Другой интересный и полезный вариант применения директив IF и IFE — отладочная печать. Суть здесь в том, что в процессе отладки программы почти всегда возникает необходимость динамически отслеживать состояние определенных программно-аппаратных объектов, в качестве которых могут выступать переменные, регистры процессора и т. п. После этапа отладки отпадает необходимость в таких диагностических сообщениях. Для их устранения приходится корректировать исходный текст программы, после чего подвергать ее повторной трансляции. Но есть более изящный выход. Можно определить в программе некоторую переменную, к при- меруdebug, и использовать ее совместно с условными директивами IF или IFE:
2>debug equ 1
<4>
5>
б>if debug
7>команды и директивы ассемблера
<8>на печать или монитор)
<9>
На время отладки и тестирования программы вы можете заключить отдельные участки кода в своеобразные операторные скобки в виде директив IF и(стро
ки 6-9 последнего фрагмента), реагирующие на значение логической переменной debug. При значении debug = 0 транслятор полностью проигнорирует текст внутри этих условных операторных скобок; при debug = 1, наоборот, будут выполнены все действия, описанные внутри них.
Директивы IFDEF и IFNDEF
Синтаксис директив IFDEF и IFNDEF следующий:
IF(N)DEF
фрагмент_программы_1 ~
ELSE
фрагмент_программы_2
ENDIF
Данные директивы позволяют управлять трансляцией фрагментов программы в зависимости от того, определено или нет в программе некоторое символическое имя.
Директива IFDEF проверяет, описано или нет в программе символическое имя, и если это так, то в объектный модуль помещается первый фрагмент программыВ противном случае при наличии директивы ELSE
в объектный код помещается второй фрагмент программы
Если же директивы ELSE нет (и символическое имя в программе не описано), то вся часть программы между директивами IF и ENDIF игнорируется и в объектный модуль не включается.
Действие IFNDEF обратно действию IFDEF. Если символического имени в программе нет, то транслируется первый фрагмент программы. Если оно присутствует, то при наличии ELSE транслируется второй фрагмент программы. Если ELSE отсутствует, а символическое имя в программе определено, то часть программы, заключенная между IFNDEF и ENDIF, игнорируется.
В качестве примера рассмотрим ситуацию, когда в объектный модуль программы должен быть включен один из трех фрагментов кода в зависимости от значения некоторого идентификатора switch:
嶷 если switch = 0, то сгенерировать фрагмент для вычисления выражения» = х • 2" 觴 если switch = 1, то сгенерировать фрагмент для вычисления выражения у =х/2"; L если идентификатор switch не определен, то ничего не генерировать.
Соответствующий фрагмент исходной программы может выглядеть так: ;если не определено, то выйти из макроса EXITM else;иначе - на вычисление
п
i fe sw
sal clна степень 2
:сдвигом влево else
sar x ,cl ;деление на степень 2
;сдвигом вправо endi f
endi f
Как видим, эти директивы логически связаны с директивами IF и IFE, то есть их можно применять в тех же самых случаях, что и последние. Есть еще одна интересная возможность использования этих директив. В главе б мы обсуждали формат командной строки TASM и говорили о ключах, которые можно в ней задавать. Далее приведен один из них (см. приложение В, http://www.piter.com/download): /бимя_идентификатора[=значение]
Использование этого ключа дает возможность управлять значением идентификатора прямо из командной строки, не меняя при этом текста программы. В качестве примера рассмотрим листинг 14.5. В этом коде мы пытаемся с помощью макроса контролировать процесс резервирования и инициализации некоторой области памяти в сегменте данных.
Листинг 14.5. Инициализация значения идентификатора из командной строки
;prg_14_5.asm
masm-—
modelsmall
stack 256
def_tab_50 macrolen
ifndef len
display "size_m не определено, задайте значение 10<si ze_m<50" exi tm--
else
if len GE 50
GOTO exit
endif
if len LT 10
:exit
EXITM
endif rept len db 0
endm
endif endm ;si ze_m=15 .data-
def_tab_50 size_m
.code main: mov ax,©data mov ds.ax exit:
mov ax,4c00h
int 21h end main
Запустив этот пример на трансляцию, вы получите сообщение о том, что забыли определить значение переменной size_m. Исправить эту ошибку можно одним из двух способов:
ii в начале исходного текста программы определите значение этой переменной с помощью псевдооператоа EQU:
si ze_m equ 15
ii запустите программу на трансляцию командной строкой вида
tasm /dsize_m=15 /zi prg_13_2,,,
В листинге 14.5 мы использовали еще одну возможность транслятора — директиву DISPLAY, с помощью которой можно формировать пользовательское сообщение в процессе трансляции программы. Директива DISPLAY будет рассмотрена в конце данной главы.
Директивы IFB и IFNB
Синтаксис директив IFB и IFNB следующий:
IF(N)B аргумент
фрагмент_программы_1
ELSE
фрагмент_программы_2
ENDIF
Данные директивы используются для проверки фактических параметров, передаваемых в макрос. При вызове макрокоманды они анализируют значение аргумента и в зависимости от того, равно оно пробелу или нет, транслируется либо первый фрагмент программы (фрагмент_программы_1),либо второй (фрагмент_про- граммы_2). Какой именно фрагмент будет выбран, зависит от кода директивы.
趣 Директива IFB проверяет равенство аргумента пробелу. В качестве аргумента могут выступать имя или число. Если его значение равно пробелу (то есть фактический аргумент при вызове макрокоманды не был задан), то транслируется и помещается в объектный модуль первый фрагмент программы. В противном случае при наличии директивы ELSE в объектный код помещается второй фрагмент программы. Если же директивы ELSE нет, то при равенстве аргумента пробелу вся часть программы между директивами IFB и ENDIF игнорируетсяи в объектный модуль не включается.
總Действие IFNB обратно действию IFB. Если значение аргумента в программе не равно пробелу, то транслируется первый фрагмент программы. В противном случае при наличии директивы ELSE в объектный код помещается второй фрагмент программы. Если же директивы ELSE нет, то вся часть программы (при неравенстве аргумента пробелу) между директивами IFNB и ENDIF игнорируется и в объектный модуль не включается.
Типичным примером применения этих директив являются строки в макроопределении, проверяющие, указывается ли фактический аргумент при вызове соответствующей макрокоманды: showmacroreg
ifb <reg>
display "не задан регистр "
exi
endif
endm
Если теперь в сегменте кода вызвать макрос SHOW без аргументов, то будет выведено сообщение о том, что не задан регистр, и генерация макрорасширения прекратится директивой EXITM.
Директивы IFIDN, IFIDNI, IFDIF и IFDIFI
Директивы IFIDN, IFIDNI, IFDIF и IFDIFI позволяют не просто проверить наличие или значения аргументов макрокоманды, но и выполнить идентификацию аргументов как строк символов. Синтаксис этих директив:
IFIDN(I)аргумент_1,аргументу
фра「минт_про「раммь1_1
ELSE
фрагмент_программы_2
ENDIF
IFDIF(I) ар「умент_1,аргументу
$ра「мент_про「раммь1_1
ELSE
фрагмент_программы_2
ENDIF
В этих директивах проверяются аргумент_1 и аргумент_2 как строки символов. Какой именно код (фрагмент_программы_1 или фрагмент_программы_2) будет транслироваться по результатам сравнения, зависит от кода директивы. Наличие двух пар этих директив объясняется тем, что они позволяют учитывать либо не учитывать различие строчных и прописных букв. Так, директивы IFIDNI и IFDIFI игнорируют это различие, a IFIDN и IFDIF — учитывают.
選禽 Директива IFIDN(I) сравнивает символьные значения аргумент_1 и аргумент^.
Если результат сравнения положительный, то транслируется и помещается в объектный модуль первый фрагмент программы. В противном случае при наличии директивы ELSE, в объектный код помещается второй фрагмент программы, Если же директивы ELSE нет, то вся часть программы между директивами IFIDN(I) и ENDIF игнорируется и в объектный модуль не включается.
後 Действие IFDIF(I) обратно действию IFIDN(I). Если результат сравнения отрицательный (строки не совпадают), транслируется первый фрагмент программы. В противном случае все происходит аналогично рассмотренным ранее директивам.
Как мы уже упоминали ранее, эти директивы удобно применять для проверки фактических аргументов макрокоманд. К примеру, проверим, какой из регистров — al или ah — передан в макрос в качестве параметра (проверка проводится без учета различия строчных и прописных букв): show macro r g ifdifi <al>, <rg> goto else ifdifi <ah>,<rg> gotoM_ah
else exi tm endif endif :M al
endm
Вложенность директив условной трансляции
Как мы неоднократно видели в приведенных ранее примерах, транслятор Т допускает вложенность условных директив компиляции. Более того, так как вложенность требуется довольноTASM предоставляет набор дополнительных
директив форматакоторые заменяют последовательность идущих подряд директив ELSE ив структуре
IFxxx
ELSE
IFxxx
ENDIF ENDIF
Эту последовательность условных директив можно заменить эквивалентной последовательностью дополнительных директив IFxxx
ELSEIFxxx
ENDIF
Символы ххх в ELSExxx говорят о том, что каждая из директив — IF,IFIDN
и т. д. — имеет соответствующую директиву ELSEIF, ELSEIFB, ELSEIFIDN и т. д. В конечном итоге это улучшает читабельность кода. В последнем примере фрагмента макроса, проверяющем, имя какого регистра было передано в макрос, наблюдается подобная ситуация. Последовательности ELSE и IFDIFI можно записать так, как в строке 4: <1>rg
<2> ifdifi <3> goto <4> else ifdifi <S> goto M_ah <6> else <7> <8> <9>
CD :M_ah
<13>
Директивы генерации ошибок
В языке TASM есть ряд директив, называемых директивами генерации тельской ошибки. Их можно рассматривать и как самостоятельное средство, и как метод, расширяющий возможности директив условной компиляции. Они предназначены для обнаружения различных ошибок в программе, таких как неопределенные метки или пропуск параметров макроса. Директивы генерации пользовательской ошибки по принципу работы можно разделить на два типа:
Я безусловные директивы генерируют ошибку трансляции без проверки каких- либо условий;
it условные директивы генерируют ошибку трансляции после проверки определенных условий.
Большинство директив генерации ошибок имеет два обозначения, хотя принцип их работы одинаков. Второе название отражает их сходство с директивами условной компиляции. При дальнейшем обсуждении такие парные директивы будут приводиться в скобках. •
Безусловная генерация пользовательской ошибки
К директивам безусловной генерации пользовательской ошибки относится только одна директива ERR (.ERR).
Данная директива, будучи вставлена в текст программы, безусловно приводит к генерации ошибки на этапе трансляции и удалению объектного модуля. Она очень полезна при использовании с директивами условной компиляции и при включении в тело макрокоманды с целью отладки. К примеру, эту директиву можно было бы вставить в ту ветвь программы (в последнем рассмотренном нами макроопределении), которая выполняется, если указанный в качестве аргумента регистр отличен от■ al и ah:
showmacro rg
ifdifi <al> ,<rg>
goto M_al.
else
ifdifi <ah>,<rg>
goto M_ah
else
.Err
endif endif
endm
Если после определенного таким образом макроопределения в сегменте кода вызвать макрокоманду show с фактическим параметром, отличным от имен регистров ah или al, будет сгенерирована ошибка компиляции (с текстом «User error»), сам процесс компиляции прекращен и, естественно, объектный модуль создан не будет.
Остальные директивы являются условными, так как их поведение определяют некоторые условия.
Условная генерация пользовательской ошибки
Набор условий, на которые реагируют директивы условной генерации пользовательской ошибки, такой же, как и у директив условной компиляции. Поэтому и количество этих директив такое же. Принцип их работы ясен, поэтому рассматривать их мы будем очень кратко. Заметим только, что использовать большинство директив условной генерации пользовательской ошибки, как и директив условной компиляции, можно и в макроопределениях, и в любых местах программы.
Директивы .ERRB (ERRIFB) и .ERRNB (ERRIFNB)
Синтаксис директив .ERRB (ERRIFB) и .ERRNB (ERRIFNB):
.ERRB (ERRIFB) <имя_4юрмально「о_ар「умента>
.ERRNB (ERRIFNB) <имя_формально7о_аргумента>
後 Директива .ERRB (ERRIFB) вызывает генерацию пользовательской ошибки, если формальный аргумент с именем <имя_формального_аргумента> пропущен.
Директива .ERRNB (ERRIFNB) вызывает генерацию пользовательской ошибки, если формальный аргумент с именем <имя_формального_аргумента> присутствует.
Данные директивы применяются для генерации ошибки трансляции в зависимости от того, задан или нет при вызове макрокоманды фактический аргумент, соответствующий формальному аргументу в заголовке макроопределения с именем <имя_формального_аргумента>. По принципу действия эти директивы полностью аналогичны соответствующим директивам условной компиляции IFB и IFNB. Их обычно используют для проверки задания параметров при вызове макроса. Строка, являющаяся именем формального аргумента, должна быть заключена в угловые скобки.
К примеру, определим обязательность задания фактического аргумента, соответствующего формальному аргументу rg, в макросе show:
1>showmacro rg
2>;если rg в макрокоманде не будет задан,
3>;т。 завершитькомпиляцию
4>.errb <rg>
5>;текст макроопределения
<б>
7>endm
Директивы .ERRDEF(ERRIFDEF) и .ERRNDEF (ERRIFNDEF)
Синтаксис директив .ERRDEF (ERRIFDEF) и • ERRNDEF (ERRIFNDEF):
.ERRDEF (ERRIFDEF) символическое_имя
.ERRNDEF (ERRIFNDEF) символическое_имя
и Директива .ERRDEF (ERRIFDEF) генерирует пользовательскую ошибку, если указанное символическое имя определено до выдачи этой директивы в программе.
趣 Директива .ERRNDEF (ERRIFNDEF) генерирует пользовательскую ошибку, если указанное символическое имя не определено до момента обработки транслятором этой директивы.
Данные директивы генерируют ошибку трансляции в зависимости от того, определено или нет некоторое символическое имя в программе. Не забывайте о том, что компилятор TASM по умолчанию формирует объектный модуль за один проход исходного текста программы. Следовательно, директивы .ERRDEF (ERRIFDEF) h.ERRNDEF(ERRIFNDEF)отслеживают факт определения символического имени только в той части исходного текста, которая находится до этих директив.
Директивы .ERRDIF (ERRIFDIF) и .ERRIDN (ERRIFIDN)
Синтаксис директив ,ERRDIF (ERRIFDIF) и .ERRIDN (ERRIFIDN):
.ERRDIF (ERRIFDIF) <строка_1>, <строка__2> .ERRIDN (ERRIFIDN) 〈строка二〈строка 二 2>
冬级 Директива .ERRDIF (ERRIFDIF) генерирует пользовательскую ошибку, если две строки посимвольно не совпадают. Строки могут быть символическими именами, числами или выражениями и должны быть заключены в угловые скобки. Аналогично директиве условной компиляции IFDIF, при сравнении учитывается различие прописных и строчных букв.
ж Директива .ERRIDN (ERRIFIDN) генерирует пользовательскую ошибку, если строки посимвольно идентичны. Строчные и прописные буквы воспринимаются как разные.
Для того чтобы игнорировать различия строчных и прописных букв, существуют аналогичные директивы:
ERRIFDIFI <строка_1>,<строка_2> '
ERRIFIDNI <строка^1>.〈строка二2>
я Директива ERRIFDIFI аналогична директиве ERRIFDIF, но игнорируется различие строчных и прописных букв при сравнении строк <строка_1> и <строка_2>.
9 Директива ERRIFIDNI аналогична директиве ERRIFIDN, но игнорируется различие строчных и прописных букв при сравнении строк <строка_1> и <строка_2>. Данные директивы, как и соответствующие им директивы условной компиляции, удобно применять для проверки передаваемых в макрос фактических параметров.
Директивы .ERRE (ERRIFE) и .ERRNZ (ERRIF)
Синтаксис директив •ERRE (ERRIFE) и .ERRNZ (ERRIF):
ERRE (ERRIFE) константное_выражение .ERRNZ (ERRIF) константное^выражение
в Директива .ERRE (ERRIFE) вызывает пользовательскую ошибку, если константное выражение ложно (равно нулю). Вычисление константного выражения должно приводить к абсолютному значению, и это выражение не может содержать ссылок вперед.
靈Директива .ERRNZ(ERRIF) вызывает пользовательскую ошибку, если константное выражение истинно (не равно нулю). Вычисление константного выражения должно приводить к абсолютному значению и не может содержать ссылок вперед.
Константные выражения в условных директивах
Как вы успели заметить, во многих условных директивах в формировании условия участвуют выражения. Результат вычисления этого выражения обязательно должен быть константой. Хотя его компонентами могут быть и символические параметры, но их сочетание в выражении должно давать абсолютный результат. К примеру,
.data mas db len dd
.code
.erre(len-mas) 1t 10 ;генерация ошибки, если длина
;области mas меньше 10 байт
Кроме того, константное выражение не должно содержать компоненты, которые транслятор еще не обработал к тому месту программы, где находится условная директива. Также мы отметили, что логические результаты «истина» и «ложь» являются условными в том смысле, что ноль соответствует логическому результа-
ту «ложь», а любое ненулевое значение — «истине». Однако в языке ассемблера существуют операторы, которые позволяют сформировать и «чисто логический» результат. Это так называемые операторы отношений, выражающие отношение двух значений или константных выражений. В контексте условных директив вместе с операторами отношений можно рассматривать и логические операторы. Результатом работы тех и других может быть одно из двух значений:
裟 истина — число, которое содержит двоичные единицы во всех разрядах;
終 ложь — число, которое содержит двоичные нули во всех разрядах.
Операторы, которые можно применять в выражениях условных директив и которые формируют логические результаты, приведены в табл. 14.1 и 14.2.
Таблица 14.1. Операторы отношений
Оператор отношения
Синтаксис
Результат
EQ (equal) — равно
выражение_1 EQ выражение_2
Истина, если выражение_1 равно выражение_2|
NE (not equal) — не равно
выражение__1 NE выражение_2
Истина, если выражение_1 не равно выражение_2|
LT (less than) — меньше
выражение__1 LT выражение_2
Истина, если выражение_1 меньше выражение_2[
LE (less or equal) — меньше или равно
выражение_1 LE выражение 2
Истина, если выражение_1 меньше или равно выражение 2
GT (greater than) - больше
выражение_1 GT выражение_2
Истина, если выражение_1 больше выражение_2|
GE (greater or equal) — больше или равно
выражение_1 GE выражение_2
Истина, если выражение_1 больше или равно выражение^?|
Таблица 14.2.Логические операторы
Логический оператор
Синтаксис
Результат
NOT — логическое отрицание
NOT выражение
Истина, если выражение ложно; ложь, если выражение истинно
AND — логическое И
выражение_1 AND выражение_2
Истина, если выражение」 и выражение_2 истинны
OR — логическое ИЛИ
выражение_1 OR выражение 2
Истина, если выражение_1 или выражение 2 истинны
XOR — исключающее ИЛИ
выражение」 XOR выражение__2
Истина, если выражение_1 равно логическому отрицанию выражения_2
Дополнительные средства управления трансляцией
TASM предоставляет средства для вывода текстового сообщения во время трансляции программы — директивы DISPLAY и %OUT. С их помощью можно при необходимости следить за ходом трансляции. К примеру,
display недопустимые аргументы макрокоманды
%out недопустимое имя регистра
В результате обработки этих директив на экран (стандартный выход) будут выведены тексты сообщений. Если эти директивы использовать совместно с директивами условной компиляции, то, к примеру, можно отслеживать путь, по которому осуществляется трансляция исходного текста программы.
Можно предложить читателю уже с этого момента начать формировать набор полезных в его практической работе макрокоманд. В качестве основы вы можете взять файл mac.inc, который находится в каталоге данной главы среди файлов, прилагаемых к книге1. В дальнейшем, если в этом возникнет необходимость, вы будете самостоятельно дополнять его вашими макросами. Использовать макроопределения из этого файла очень просто: достаточно включить в нужном месте вашей программы строку с директивой include, в результате в ваш файл будут вставлены строки из файла, указанного в качестве операнда этой директивы.
Основная задача этой книги — научить вас программировать на языке ассемблера. Как вы уже успели понять, нельзя изучать этот язык в отрыве от рассмотрения процессов, происходящих во время выполнения программы на компьютере. Одно из средств изучения таких процессов — отладчик. Однако он решает проблему глобально, что нужно далеко не всегда. Тем более, как мы увидим далее, что возможности отладчика не безграничны. Поэтому необходимо иметь более универсальное средство, которое позволило бы «подглядывать» за содержимым регистра или области памяти динамически, во время выполнения программы. Для этого разработаем еще один макрос и назовем его, к примеру, show. Его аргументом может быть один из четырех регистров — AL, АН,АХ, ЕАХ. С помощью этого макроса можно визуализировать содержимое любого из доступных регистров или области памяти длиной до 32 битов. Для этого достаточно лишь переслать содержимое нужного объекта (регистра или ячейки памяти) с учетом его размера в один из регистров AL, АН, АХ, ЕАХ. Имя одного из этих регистров указывается затем в качестве фактического аргумента макрокоманды show. Второй аргумент этого макроса — позиция на экране. Задавая определенные значения, мы можем судить о том, какая именно макрокоманда show сработала. Еще одна немаловажная особенность данного макроса — в его возможности работать как в реальном, так и в защищенном режимах. Распознавание текущего режима работы процессора выполняется автоматически. Текст макроопределения show довольно велик и по этой причине находится среди прилагаемых к книге файлов в каталоге данной главы. Пример использования этого макроса приведен в листинге 14.6.
Листинг 14.6. Пример использования макроса show ;prg_14_6.asm MASM~ - MODEL small STACK256
486p
Include show.inc
.data
Все прилагаемые к книге файлы можно найти по адресу http://www.piter.com/download. — Примеч. ред.
poledd 3cdf436fh
.code main:
mov ax,@data mov ds. ax xor ax,ax mov ax,IfOfh show al,0 show ah,160 show ax,320 mov eax,pole show eax,480 exit: mov ax,4c0Oh int 21h.
end main
Посвятить время рассмотрению этого макроса полезно еще и потому, что при его разработке было использовано большинство средств, обсуждавшихся в этой главе.
Директивы управления файлом листинга
Ассемблер предоставляет ряд директив для управления содержимым файла листинга. Далее приведена их общая характеристика.
Общие директивы управления листингом призваны управлять видом файла листинга. Все директивы являются парными, это означает, что если одна директива что-то разрешает, то другая, наоборот, запрещает.
в Директивы %LISF и %NOLIST (.LIST и .XLIST) определяют необходимость вывода в файл листинга всех строк исходного кода (по умолчанию). Для запрета вывода в файл листинга всех строк исходного кода необходимо использовать директивы .XLIST или 7oN0LIST. В тексте программы их можно применять произвольное количество раз, при этом очередная директива отменяет действие предыдущей.
綴 Директивы %CTLS и %NOCTLSуправляют выводом в файл листинга самих директив управления листингом, в то время как директивы %LIST и %NOLIST (.LIST и .XLIST) влияют на полноту представления исходного кода в целом.
颠 Директивы %SYMS и %NOSYMS определяют включение (%SYMS) или не включение (%N0SYMS) таблицы идентификаторов в файл листинга.
Директивы вывода текста включаемых файлов °ZoINCL и %NOINCL регулируют включение в файл листинга текста включаемых файлов (по директиве INCLUDE). По умолчанию включаемые файлы записываются в файл листинга. Директива %NOINCL запрещает вывод в файл листинга всех последующих включаемых файлов, пока вывод снова не будет разрешен директивой %INCL
Директивы вывода блоков условного ассемблирования регулируют включение блоков условной компиляции в листинг программы.
緩 Директива %CONDS (.LFCOND) заставляет ассемблер выводить в файл листинга все операторы условных блоков (в том числе с условием false). Директива %NOCONDS (.SECONDS) запрещает вывод в файл листинга блоков условного ассемблирования с условием false.
11 Зак. 256
舞之 Директива .TFCOND переключает режимы вывода%CONDS(.LFCOND) и %NOCONDS (.SECONDS). Эту директиву можно использовать как отдельно, так и совместно с директивами .LFCOND и •SFCONDS. Первая директива .TFCOND, которую обнаруживает TASM, разрешает вывод в файл листинга всех блоков условного ассемблирования. Следующая директива .TFCOND запретит вывод этих блоков. С директивой .TFCOND можно использовать ключ /х командной строки транслятора TASM — в этом случае блоки условного ассемблирования будут сначала выводиться в файл листинга, но первая же директива .TFCOND запретит их вывод.
Директивы вывода макрорасширений, как явствует из названия, управляют выводом макрорасширений:
•急 Директива %MACS (1ALL) разрешает вывод в файл листинга всех макрорасширений.
Директива %N0MACS (.SALL) запрещает вывод всех операторов макрорасширения в файл листинга.
В трансляторе MASM можно использовать директиву .XALL, позволяющую выводить в листинг только те макрорасширения, которые генерируют код или данные.
Итоги
Преимущества языка ассемблера связаны, в частности, с макросредствами. Как говорят, если бы макросредств в нем не было, то их нужно было бы придумать.
л Макросредства — это основные инструменты модификации текста программы на этапе ее трансляции. Принцип работы макросредств основан на препроцес- сорной обработке, которая заключается в том, что текст, поступающий на вход транслятора, перед собственно компиляцией подвергается преобразованию и может значительно отличаться от синтаксически правильного текста, воспринимаемого компилятором. Роль препроцессора в трансляторе TASM выполняет макрогенератор.
彌 Для того чтобы макрогенератор мог выполнить свою работу, текст программы должен удовлетворять определенным требованиям. Макрогенератору необходимо сообщить, на какие элементы исходного текста он должен реагировать и какие действия должны быть произведены. Можно выделить несколько типов таких элементов.
纖 Псевдооператоры EQU и = предназначены для присвоения некоторому выражению символического имени или идентификатора. Эти действия выполняет макрогенератор, заменяя в последующем тексте программы символические имена из правой части этих операторов строками из левой.
Макрокоманда — строка в исходной программе, которой соответствует специальный блок — макроопределение. Макрокоманда может иметь аргументы, с помощью которых можно изменять текст макроопределения. Макрогенератор, встречая макрокоманду в тексте программы, корректирует текст соответствующего макроопределения, исходя из аргументов этой макрокоманды, и вставля-
Итоги323
ет его в текст программы вместо данной макрокоманды. Процесс такого замещения называется макрогенерацией.
轡 Условные директивы компиляции позволяют не просто модифицировать отдельные строки программы, но и, исходя из определенных условий, управлять включением в загрузочный модуль отдельных фрагментов программы. Эти директивы наиболее эффективны для работы с аргументами, передаваемыми при макрогенерации в макроопределения из макрокоманд, хотя отдельные директивы есть смысл применять и вне макроопределений в любом месте программы.
隱 Директивы генерации ошибок, подобно условным директивам, позволяют анализировать определенные условия в процессе трансляции программы и генерировать ошибку по результатам анализа.
畿 Для удобства формирования файла листинга компиляторы ассемблера предоставляют в распоряжение программиста ряд директив. С помощью этих директив можно довольно гибко изменять формат и полноту информации, выводимой ассемблером в файл листинга.
Глава 15
Модульное
программирование
^ Основы структурного программирования
除Средства ассемблера для поддержки структурного программирования'
> Процедуры и организация связей между процедурами на языке ассемблера
義Директива INVOKE (MASM)
第Связь между программами на языках высокого уровня и программами на ассемблере
Мы неоднократно подчеркивали один из существенных недостатков программ на языке ассемблера, а значит, и самого языка, — недостаточную наглядность. По прошествии даже небольшого времени программисту порой бывает трудно разобраться в деталях им же написанной программы. А о чужой программе и говорить не приходится. Если в ней нет хотя бы минимальных комментариев, то разобраться с тем, что она делает, довольно трудно. Причины этого тоже понятны — при написании программы на языке ассемблера человек должен запрограммировать самые элементарные действия или операции. При этом он должен учитывать и контролировать состояние большого количества данных. Из-за элементарности программируемых операций реализация одного и того же алгоритма может быть произведена по крайней мере несколькими способами. Эта неоднозначность влечет за собой непредсказуемость, что и затрудняет процесс обратного восстановления исходного алгоритма по ассемблерному коду.
По мере накопления опыта эти проблемы частично снимаются. Но одного опыта мало. Ситуация усугубляется, если работает коллектив разработчиков. Тут уже
нужны специальные средства. Далее перечислены организационные и программные (предоставляемые ассемблером) мероприятия, позволяющие хотя бы частично снять остроту этой проблемы.
蠶 Документирование программистом своей работы и ее результатов. Делается это в первую очередь путем комментирования строк исходного текста программы. При этом комментарии должны коротко, но точно выражать то, что делает данная программа в целом, выделять ее наиболее важные фрагменты и особенности применения отдельных команд. В конечном итоге, комментирование облегчает понимание сути программы, но все-таки полностью не снимает проблему.
犠 Упрощение кода программы путем замены сложных фрагментов более понятным кодом. Для этого, в частности, можно использовать рассмотренные нами макрокоманды.
緻; Использование при разработке программных проектов достижений современных технологий программирования.
К настоящему моменту наиболее популярными и жизнеспособными оказались две технологии программирования: структурная и объектно-ориентированная.
Последние версии пакетов TASM и MASM языка ассемблера поддерживают объектно-ориентированное программирование, но реализация его довольно сложна и требует отдельного рассмотрения. Типичному процессу написания программы на ассемблере больше всего удовлетворяют концепции структурного программирования. Можно даже сказать, что для процессорной архитектуры IA-32 эти концепции поддерживаются на аппаратном уровне с помощью таких архитектурных механизмов, как сегментация памяти и аппаратная реализация команд передачи управления. На программном уровне поддержка заключается, в основном, в соответствующих средствах конкретного компилятора, в частности, такие средства имеют компиляторы TASM и MASM. Данная глава посвящен этим программно-аппаратным средствам.
Структурноепрограммирование
Структурное программирование — методология программирования, базирующаяся на системном подходе к анализу, проектированию и реализации программного обеспечения. Эта методология зародилась в начале 70-х гг. и оказалась настолько жизнеспособной, что и до сих пор является основной в большом количестве проектов. Ее основу составляет концепция модульного программирования.
Концепцию модульного программирования можно сформулировать в виде нескольких понятий и положений. Основа концепции модульного программирования — модуль, который является продуктом процесса разбиения большой задачи на ряд более мелких функционально самостоятельных подзадач. Этот процесс называется функциональной декомпозицией задачи. Каждый модуль в функциональной декомпозиции представляет собой «черный ящик» с одним входом и одним выходом. Модули связаны между собой только входными и выходными данными.
Модульный подход позволяет безболезненно производить модернизацию программы в процессе ее эксплуатации и облегчает ее сопровождение. Дополнительно модульный подход позволяет разрабатывать части программ одного проекта на разных языках программирования, после чего с помощью компоновочных средств объединять их в единый загрузочный модуль.
Основная цель модульного подхода — простота и ясность реализуемых решений. Если вы затрудняетесь четко сформулировать назначение модуля, это говорит о том, что один из предыдущих этапов декомпозиции задачи был проведен недостаточно качественно. В этом случае необходимо вернуться на один или более шаг назад и еще раз проанализировать задачу этапа, вызвавшего проблему. Это, возможно, приведет к появлению дополнительных модулей в процессе функциональной декомпозиции.
При наличии в проекте сложных мест их нужно подробно документировать с помощью продуманной системы комментариев. Комментарии должны быть ясными в смысле отражения реализуемых идей и подчиняться некоторой системе — частной, если разработчик — частное лицо, или корпоративной, действующей внутри некоторого организованного коллектива разработчиков. По поводу системы комментирования можно еще раз напомнить несколько широко известных идей. Во-первых, желательно назначение всех переменных модуля описывать комментариями по мере их определения. Во-вторых, исходный текст модуля должен иметь заголовок, отражающий назначение модуля и его внешние связи. Этот заголовок можно назвать интерфейсной частью модуля. В интерфейсной части с использованием комментариев нужно поместить информацию о назначении модуля, об особенностях функционирования, о входных и выходных аргументах, об использовании внешних модулей и переменных, а также о разработчике — для защиты авторских прав.
В ходе разработки программы следует предусматривать специальные блоки операций, учитывающие реакцию на возможные ошибки в данных или в действиях пользователя. Это очень важный момент, означающий, что в алгоритме программы не должно быть тупиковых ветвей, в результате работы которых программа «виснет» и перестает отвечать на запросы пользователя. Любые непредусмотренные действия пользователя должны приводить к генерации ошибочной ситуации или к предупреждению о возможности возникновения такой ситуации.
Из этих положений видно, какое большое значение придается организации управляющих и информационных связей между структурными единицами программы (модулями), совместно решающими одну или несколько больших задач. Применительно к языку ассемблера можно рассматривать несколько форм организации управляющих связей.
Si Макроподстановки позволяют изменять исходный текст программы в соответствии с некоторыми предварительно описанными параметризованными объектами. Эти объекты имеют формальные аргументы, замещаемые в процессе макрогенерации их фактическими аргументами. Такая форма образования структурных элементов носит некоторый предварительный характер из-за того, что процессы замены происходят на этапе компиляции и есть смысл рассматривать их только как настройку на определенные условия функционирования программы.
缴 Объединение в одну программу подпрограмм, написанных на ассемблере.
В языке ассемблера такие подпрограммы называют процедурами. В отличие от макрокоманд, взаимодействие процедур осуществляется на этапе выполнения программы.
ii Объединение в единый модуль на этапе компоновки подпрограмм, написанных на разных языках программирования. Эта возможность реализуется благодаря унифицированному формату объектного модуля, однозначным соглашениям о передаче аргументов и единым схемам организации памяти на этапе выполнения.
Динамический (на этапе выполнения) вызов исполняемых модулей и динамическое подключение библиотек (DLL-файлов) для операционной системы Windows.
В качестве основных информационных связей можно выделить следующие: ii общие области памяти и общие программно-аппаратные ресурсы процессора для связи модулей;
緻 унифицированная передача аргументов при вызове модуля (эту унификацию можно представлять двояко: на уровне пользователя и на уровне конкретного компилятора);
犠 унифицированная передача аргументов при возвращении управления из модуля.
Чуть позже мы подробно рассмотрим процессы, происходящие при передаче аргументов. Сейчас в качестве некоторого итога приведенных ранее общих рассуждений перечислим средства языка ассемблера по осуществлению функционалъ- ной декомпозиции программы:
Ii макросредства;
麵; процедуры;
幾 средства компилятора ассемблера в форме директив организации оперативной памяти и ее сегментации.
Макросредства подробно рассмотрены в главе 14. Предварительное обсуждение процедур проведено при обсуждении команд передачи управления в главе 10. Но процедуры — это не просто механизм тривиальной передачи управления из одной точки программы в другую. В частности, этот механизм тесно связан со средствами компилятора, поддерживающими организацию памяти и сегментацию. Поэтому дальнейшее обсуждение будет посвящено более глубокому изучению функциональной декомпозиции программ с использованием процедур и связанных с ними средств компилятора.
Процедуры в языке ассемблера
В Ассемблере для оформления процедур как отдельных объектов существуют специальные директивы PROC/ENDP и машинная команда RET (см. главу 10). Если сравнивать процедуры и макрокоманды, то можно сказать следующее. Процедуры, так же как и макрокоманды, могут быть активизированы в любом месте программы.
Процедурам, так же как и макрокомандам, могут быть переданы некоторые аргументы. Это позволяет, имея одну копию кода в памяти, изменять ее для каждого конкретного случая использования, хотя по гибкости процедуры уступают макрокомандам.
В главе 10 нами были рассмотрены возможные варианты размещения процедур в программе:
в начале программы (до первой исполняемой команды);
鳖 в конце программы (после команды, возвращающей управление операционной системе);
промежуточный вариант — тело процедуры располагается внутри другой процедуры или основной программы (в этом случае необходимо предусмотреть обход процедуры с помощью команды безусловного перехода JMP);
* в другом модуле.
Главная цель таких вариантов размещения — не допустить несанкционированной передачи управления коду процедуры. Три первых варианта относятся к случаю, когда процедуры находятся в одном сегменте кода. Мы их достаточно подробно обсудили. Что же касается последнего варианта, то он предполагает, что процедуры находятся в разных модулях. А это дает нам возможность говорить уже не об одном модуле, а о нескольких. Для реализации одной общей задачи эти модули должны быть связаны между собой по управлению и по данным. Если мы разберемся с тем, как организовать такую связь, то фактически сможем выполнить функциональную декомпозицию любой большой программы на нужное количество более мелких. В первой части главы мы рассмотрим, как организуется связь по управлению и по данным между модулями на ассемблере, a во второй части — между модулями на ассемблере и на языках высокого уровня (Pascal и C/C++).
Сначала необходимо отметить один общий для всех этих трех языков момент. Так как отдельный модуль в соответствии с концепцией модульного программирования — это функционально автономный объект, то он ничего не должен знать о внутреннем устройстве других модулей, и наоборот, другим модулям также ничего не должно быть известно о внутреннем устройстве данного модуля. Однако должны быть какие-то средства, с помощью которых можно связать модули. В качестве аналогии можно привести организацию связи (интерфейс) телевизора и видеомагнитофона через разъем типа «скарт». Связь унифицирована, то есть известно, что один контакт предназначен для видеосигнала, другой — для передачи звука и т. д. Телевизор и видеомагнитофон могут быть разными, но связь между ними одинакова. Та же идея лежит и в организации связи модулей. Внутреннее устройство модулей может совершенствоваться, они вообще могут в следующих версиях писаться на другом языке, но в процессе их объединения в единый исполняемый модуль этих особенностей не должно быть заметно. Таким образом, каждый модуль должен иметь такие средства, с помощью которых он извещал бы транслятор о том, что некоторый объект (процедура, переменная) видимым вне этого модуля. И наоборот, нужно объяснить транслятору, что некоторый объект находится вне данного модуля. Это позволит транслятору правильно сформировать машинные команды, оставив некоторые их поля не заполненными. Позднее, на этапе компо-
новки, программа TLINK (TASM) или программа компоновки языка высокого уровня произведут настройку модулей и разрешат все внешние ссылки в объединяемых модулях.
Для того чтобы объявить о подобного рода объектах, видимых извне, программа должна использовать две директивы TASM: EXTRN и PUBLIC. Директива EXTRN предназначена для объявления некоторого имени внешним по отношению к данному модулю. Это имя в другом модуле должно быть объявлено в директиве PUBLIC. Директива PUBLIC предназначена для объявления некоторого имени, определенного в этом модуле и видимого в других модулях. Синтаксис этих директив следующий:
extrn имя:тип . . . имя:тип public имя ….,имя
Здесь имя — идентификатор, определенный в другом модуле. В качестве идентификатора могут выступать:
имена переменных, определенных директивами типа DB, DW и т. д.;
ж имена процедур;
經? имена констант, определенных операторами = и EQU.
Аргумент тип определяет тип идентификатора. Указание типа необходимо для того, чтобы транслятор правильно сформировал соответствующую машинную команду. Действительные адреса вычисляются на этапе редактирования, когда будут разрешаться внешние ссылки. Возможные значения типа определяются допустимыми типами объектов для этих директив:
в если имя — это имя переменной, то тип может принимать значения BYTE, WORD, DWORD, PWORD, FWORD, QWORD и TBYTE;
綏если имя — это имя процедуры, тотип может принимать значения near или far; li если имя — это имя константы, то тип должен быть abs.
Покажем принцип использования директив EXTRN и PUBLIC на схеме связи модулей 1 и 2 (листинги 15.1 и 15.2).
Листинг15.1.Модуль 1
;Модуль 1 mas№
.model small
.stack 256
.data
.code
my_proc_l proc
my_proc_l endp
my_proc_2 proc
my_proc_2 endp
;объявляем процедуру my_proc_l видимой извне
public niy_proc_l —
start:
mov ax,@data
end start
Листинг 15.2. Модуль 2
;Модуль 2
masm
.model small .stack 256 .data
. code
extrn my_proc_l ;объявляем процедуру roy_proc_l внешней start:---
mov ax,@data
call my_p「oc_l ;вызов my_proc_l из модуля 1 end start
Рассмотренная нами схема связи — это, фактически, связь по управлению. Но не менее важно организовать информационный обмен между модулями. Рассмотрим основные способы организации такой связи.
Информационный обмен между модулями (процедурами) предполагает обмен данными. В этой связи важно понимать значение терминов аргумент, переменная, константа.
Аргумент — это ссылка на некоторые данные, которые требуются для выполнения возложенных на модуль функций и размещенных вне этого модуля. По аналогии с макрокомандами выделяют формальные и фактические аргументы. Исходя из этого, формальный аргумент можно рассматривать не как непосредственные данные или их адрес, а как «тару» для действительных данных, которые будут положены в нее с помощью фактического аргумента. Формальный аргумент можно рассматривать как элемент интерфейса модуля (конкретный вывод 《скарта»), а фактический аргумент — это то, что фактически передается на место формального аргумента.
Переменная — это нечто, размещенное в регистре или ячейке памяти, что может в дальнейшем подвергаться изменению.
Константа — информационный объект простого типа, значение которого никогда не изменяется.
Таким образом, если некоторые данные в модуле могут подвергаться изменению, то это переменные. Если переменная находится за пределами модуля (процедуры) и должна быть как-то передана в него, то для модуля она является формальным аргументом. Значение переменной передается в модуль для замещения соответствующего параметра при помощи фактического аргумента. После пояснения различия понятий формального и фактического аргументов далее по тексту они будут называться обобщенно — аргументы, а о каком виде аргументов идет речь, станет понятно по контексту.
Если входные данные для модуля (аргументы) — переменные, то один и тот же модуль можно использовать многократно для разных наборов значений этих переменных. Но как организовать передачу значений переменных в модуль (процедуру)? При программировании на языке высокого уровня программист ограничен в выборе способов передачи аргументов теми рамками, которые для него оставляет компилятор. В языке ассемблера практически нет никаких ограничений на этот счет, и, фактически, решение проблемы передачи аргументов предоставлено программисту.
На практике используются следующие варианты передачи аргументов в модуль (процедуру):
隨 через регистры;
黎 через общую область памяти;
через стек;
' с помощью директив EXTRN и PUBLIC.
Передача аргументов через регистры
Передача аргументов через регистры — наиболее простой в реализации способ передачи данных. Данные, переданные подобным способом, становятся доступными немедленно после передачи управления процедуре. Этот способ можно рекомендовать для передачи небольших объемов данных. Недостатки этого способа:
潑 небольшое число доступных для пользователя регистров;
' необходимость постоянно помнить о том, какая информация в каком регистре находится;
ограничение размера передаваемых данных размерами регистра — если размер данных превышает 8, 16 или 32 бита, то передачу данных посредством регистров произвести нельзя и передавать приходится не сами данные, а указатели на них.
Передача аргументов через регистры широко применяется при вызове функций DOS.
В главе 14 мы обсуждали программу (см. листинг 14.2) с макрокомандой, которая подсчитывала длину строки, оканчивающуюся символом $. Для сравнения эффективности применения макрокоманд и процедур при программировании разработаем аналогичную программу (листинг 15.3), но с использованием процеду- рыCountSymbol, подсчитывающей количество символов в строке с конечным символом $. Процедура располагается в конце программы. Длина строки — не более 99 символов. Адрес строки передается процедуре как аргумент через регистр si. Результат подсчета возвращается в регистр BL и выводится на экран в вызывающей программе. Для вывода на экран используется прямой доступ к видеобуферу.
Листинг 15.3. Передача аргументов через регистры
:prg_15_3. asm
MASM~
MODELsmall;модель памяти
STACK256;размер стека
include mac.1nc: подключение файла с макросами
.data;начало сегмента данных
maskddb 71h;маска вывода
string db "Строка для подсчета mes db "В строке string "
cnt db 2 dup ("*") ; количество db "символов",10,13, ' $ '
. code
main proc;точка входа
mov ах,@data mov ds,ах
на экран
$” ;тестовая строка
символов в строке
в главную процедуру
продолжение 少
Листинг 15.3 f продолжение)
;загрузка адреса строки
;(для передачи смещения в процедуру) lea si.string
;вызов процедуры call Countsymbol
mov cl,bl
;счетчик для lods и stosw
lea si .string mov ax,®b8®@h
;в si - указатель на строку
mov es, ax
;загрузка в es адреса видеопамяти
mov ah,maskd
;маска вывода на экран
mov di,160
;позиция вывода на экран
cld disp:
;просмотр вперед - для lodsb и stosw
lodsb
;пересылка байта из ds: si в al
stosw
;копирование значения ах ;в es:di (видеобуфер)
loop disp
;повтор цикла сх раз
;a теперь выведем mov al,bl
количество символов в строке
aam
;в al две упакованные BCD-цифры
;результата подсчета
or ax,3030h mov ent. ah mov cnt+1,al
;преобразование результата в код ASCII
OutStr mes
;вывод строки mes
Exit
;макрос выхода
main endp
;конец главной процедуры
CountSymbol procnear
процедура CountSymbol - подсчет символов в строке.
;На входе: si - смещение строки
;На выходе: bl - длина в виде упакованного BCD-числа push axсохранение используемых регистров
push сх
cld
;просмотр вперед
mov СХ,100
максимальная длина строки
;блок подсчета символов
go:
lodsb;загрузка символа строки в al
emp al,'$' je endstr jexz no_end inc Ы;приращение счетчика в Ы - количество
;подсчитанных символов в строке
loop go;повтор цикла
endstr:;конец строки
pop схвосстановление регистров из стека
pop ax
ret;возврат из процедуры
no_end:
;какие-то действия по обработке ситуации ;отсутствия в строке символа $
retCountSymbol endpend mainretCountSymbol endpend main:возврат из процедуры ;конец процедуры
ret
CountSymbol endpend main
ret
CountSymbol endpend main
;конец программы
Передача аргументов через общую область памяти
Передача аргументов через общую область памяти предполагает, что вызывающая и вызываемая программы условились использовать некоторую область памяти как
общую. Транслятор предоставляет специальное средство для организации такой области памяти. В главе 5 мы разбирали директивы сегментации и их атрибуты. Один из них — атрибут комбинирования сегментов. Наличие этого атрибута указывает компоновщику, как нужно комбинировать сегменты, имеющие одно имя. Значение common означает, что все сегменты, имеющие одинаковое имя в объединяемых модулях, располагаются компоновщиком начиная с одного адреса оперативной памяти. То есть они будут просто перекрываться в памяти и, следовательно, совместно использовать выделенную память.
Недостатком этого способа в реальном режиме работы процессора является отсутствие средств защиты данных от разрушения, так как нельзя проконтролировать соблюдение правил доступа к этим данным.
Рассмотрим листинг 15.4с примером использования общей области памяти для обмена данными между модулями. На этот раз программа состоит уже из двух независимых модулей, находящихся в разных файлах, и поэтому они представляют собой отдельные единицы трансляции. Функционально эти модули реализуют несложную задачу, которая заключается в том, что вызываемые процедуры формируют строку символов и передают ее через общую область, а вызывающая их процедура main выводит строку на экран.
Листинг 15.4. Передача аргументов через общую область памяти (модуль 1)
;prgl5_4.asm
include mac.1nc;подключение файла с макросами
stk segment stack
db 256 dup (0)
stk ends
common__data segment para common "data";начало общей области памяти
buf db 15 DUP ("") ;буфер для хранения строки
tempdw 0
common_data ends
extrnPutChar:far,PutCharEnd:far
code segment;начало сегмента кода
assume cs:code,es:common_data
mainproc
mov ax,common_data
mov es,ax-
;вызов внешних процедур
call PutChar
call PutCharEnd
pushes pop ds OutStr buf Exit;стандартный выход
mainendp;конец главной процедуры
code ends end main
Вызываемые процедуры находятся в другом модуле (листинг 15.5). Обратите внимание на то, что совсем не обязательно, чтобы данные в сегментах common имели одинаковые имена. Главное, и заэтим нужно следить с особой тщательностью, — структура общих сегментов. Она должна быть абсолютно идентичной во всех модулях программы, обменивающихся данными через общую память.
Листинг 155 Передача аргументов через общую область памяти (модуль 2)
;prgl5_5.asm
include~ mac.1nc;подключение файла с макросами1 л
Листинг 15.5 (продолжение)
stk segment stack
db 256 dup (0)
stk ends
pdata mes db tempi temp2 temp3 pdatapdata mes db tempi temp2 temp3 pdatapublic common__data buffer db tmpSI dw common datapublic common__data buffer db tmpSI dw common datasegment para public "data""Общий сегмент",0ah,®dh,'$'db ?dd ?dq ?endssegment para public "data""Общий сегмент",0ah,®dh,'$'db ?dd ?dq ?endsPutChar,PutCharEnd segment para common "data"PutChar,PutCharEnd segment para common "data"codesegmentcodesegment15 DUP (" 0ends15 DUP (" 0ends;буфер для ф;буфер для ф;начало общей области памяти ормирования строки;начало общей области памяти ормирования строкиначало сегмента коданачало сегмента кодаassume cs:code.es:common_data,ds:pdataassume cs:code.es:common_data,ds:pdataPutChar proc far cldmov si ,0irpc ch,<работает!>mov buffer[si],'&ch' inc siendmmov tmpSI,siretPutChar endpPutCharEnd procfarmov si,tmpSImov buffer[si] , '$' retPutCharEnd endpcodeendsendPutChar proc far cldmov si ,0irpc ch,<работает!>mov buffer[si],'&ch' inc siendmmov tmpSI,siretPutChar endpPutCharEnd procfarmov si,tmpSImov buffer[si] , '$' retPutCharEnd endpcodeendsendОбъявление процедурыОбъявление процедуры;возврат из процедуры ;конец процедуры;возврат из процедуры ;конец процедуры
pdata mes db tempi temp2 temp3 pdata
pdata mes db tempi temp2 temp3 pdata
public common__data buffer db tmpSI dw common data
public common__data buffer db tmpSI dw common data
segment para public "data"
"Общий сегмент",0ah,®dh,'$'
db ?
dd ?
dq ?
ends
segment para public "data"
"Общий сегмент",0ah,®dh,'$'
db ?
dd ?
dq ?
ends
PutChar,PutCharEnd segment para common "data"
PutChar,PutCharEnd segment para common "data"
codesegment
codesegment
15 DUP (" 0
ends
15 DUP (" 0
ends
;буфер для ф
;буфер для ф
;начало общей области памяти ормирования строки
;начало общей области памяти ормирования строки
начало сегмента кода
начало сегмента кода
assume cs:code.es:common_data,ds:pdata
assume cs:code.es:common_data,ds:pdata
PutChar proc far cld
mov si ,0
irpc ch,<работает!>
mov buffer[si],'&ch' inc si
endm
mov tmpSI,si
ret
PutChar endp
PutCharEnd procfar
mov si,tmpSI
mov buffer[si] , '$' ret
PutCharEnd endp
codeends
end
PutChar proc far cld
mov si ,0
irpc ch,<работает!>
mov buffer[si],'&ch' inc si
endm
mov tmpSI,si
ret
PutChar endp
PutCharEnd procfar
mov si,tmpSI
mov buffer[si] , '$' ret
PutCharEnd endp
codeends
end
Объявление процедуры
Объявление процедуры
;возврат из процедуры ;конец процедуры
;возврат из процедуры ;конец процедуры
Так как в данном примере программа состоит уже из двух модулей, то возникает естественный вопрос: как собрать ее в один исполняемый модуль? Можно предложить следующую последовательность шагов.
Выполнить трансляцию файла prgl5_4.asm и получить объектный модуль prgl5_4.obj:
tasm /zi prgl5_4.asm(,f
Выполнить трансляцию файла prgl5_5.asm и получить объектный модуль prgl5_5.obj:
tasm /zi p「gl5_5.asm,,,
Скомпоновать программу утилитой TLINK командной строкой вида:
tlink /v p®5_4.obj + prgl5_4.obj
В итоге будет создан исполняемый файл prgl5_4.exe. Интересно исследовать его с использованием отладчика. После загрузки prgl5_4.exe в отладчик в окне Module появится только исходный текст программы из файла prgl5_4.asm. И лишь после вызова процедуры по команде CALL (нажатием клавиши F7) в окно будет загружен текст вызванной процедуры.
Передача аргументов через стек
Передача аргументов через стек при вызове процедур используется наиболее часто. Суть этого способа заключается в том, что вызывающая процедура самостоя-
тельно заносит в стек передаваемые данные, после чего обращается к вызываемой процедуре. В главе 10 мы рассматривали процессы, происходящие при передаче управления процедуре и возврате из нее. При этом мы обсуждали содержимое стека до и после передачи управления процедуре (см. рис. 10.4 и 10.5). Как следует из этих рисунков, при передаче управления процедуре процессор автоматически записывает на вершину стека два (для процедур типа near) или четыре (для процедур типа far) байта. Вы помните, что эти байты являются адресом возврата в вызывающую программу. Если перед передачей управления процедуре командой CALL в стек были записаны переданные процедуре данные или указатели на них, то они окажутся под адресом возврата.
При рассмотрении архитектуры процессора мы выяснили, что стек обслуживается тремя регистрами: SS, SP и В Р. Процессор автоматически работает с регистрами SS и SP в предположении, что они всегда указывают на вершину стека. По этой причине их содержимое изменять не рекомендуется. Для произвольного доступа к данным в стеке архитектура процессора имеет специальный регистр ЕВР\ВР (Base Point — указатель базы). Так же как и для регистра ESP\SP, обращение к ЕВР\ВР автоматически предполагает работу с сегментом стека. Перед использованием этого регистра для доступа к данным стека содержимое стека необходимо правильно инициализировать, что предполагает формирование в нем адреса, который бы указывал непосредственно на переданные данные. Для этого в начало процедуры рекомендуется включить дополнительный фрагмент кода. Он имеет свое название — пролог процедуры. Типичный фрагмент программы, содержащий вызов процедуры с передачей аргументов через стек, может выглядеть так:
masmmodelsmallproc_l proc near ;начало прологаpush bpmov bp.sp;конец пролога;"близкая ” процедура(near) сп аргументамиmovmovах,[bp+4]ах, [bp+6];доступ к ;доступ к командыаргументу аргументу процедурыarg_n для arg_{n-l}near-процедурыmasmmodelsmallproc_l proc near ;начало прологаpush bpmov bp.sp;конец пролога;"близкая ” процедура(near) сп аргументамиmovmovах,[bp+4]ах, [bp+6];доступ к ;доступ к командыаргументу аргументу процедурыarg_n для arg_{n-l}near-процедуры>>>>>>>>>>>> > > > > > > > > >>В123456789012 1234567891111111111222 <<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>> > > > > > > > > >>В123456789012 1234567891111111111222 <<<<<<<<<<<<<<<<<<<<<<;подготовка к выходу из процедуры
masm
model
small
proc_l proc near ;начало пролога
push bp
mov bp.sp
;конец пролога
;"близкая ” процедура
(near) с
п аргументами
mov
mov
ах,[bp+4]
ах, [bp+6]
;доступ к ;доступ к команды
аргументу аргументу процедуры
arg_n для arg_{n-l}
near-процедуры
masm
model
small
proc_l proc near ;начало пролога
push bp
mov bp.sp
;конец пролога
;"близкая ” процедура
(near) с
п аргументами
mov
mov
ах,[bp+4]
ах, [bp+6]
;доступ к ;доступ к команды
аргументу аргументу процедуры
arg_n для arg_{n-l}
near-процедуры
>>>>>>>>>>>> > > > > > > > > >>В123456789012 1234567891111111111222 <<<<<<<<<<<<<<<<<<<<<<
>>>>>>>>>>>> > > > > > > > > >>В123456789012 1234567891111111111222 <<<<<<<<<<<<<<<<<<<<<<
;начало эпилога
mov sp,Ьр ;восстановление sp
pop bp:восстановление значения старого bp
;до входа в процедуру
ret;возврат в вызывающую программу
;конец эпилога
proc_l endp
. code
<23>mov ах,@data<24>mov ds, ах<25><2б>push arg_l;запись в стек 1-го аргумента<27>push arg二 2:запись в стек 2-го аргумента<28><29>push arg_n;запись в стек n-го аргумента<30>call proc_l;вызов процедуры proc_l<31>;действия по очистке стека после возврата из процедуры<23>mov ах,@data<24>mov ds, ах<25><2б>push arg_l;запись в стек 1-го аргумента<27>push arg二 2:запись в стек 2-го аргумента<28><29>push arg_n;запись в стек n-го аргумента<30>call proc_l;вызов процедуры proc_l<31>;действия по очистке стека после возврата из процедурыmain proc
<23>
mov ах,@data
<24>
mov ds, ах
<25>
<2б>
push arg_l;запись в стек 1-го аргумента
<27>
push arg二 2:запись в стек 2-го аргумента
<28>
<29>
push arg_n;запись в стек n-го аргумента
<30>
call proc_l;вызов процедуры proc_l
<31>
;действия по очистке стека после возврата из процедуры
<23>
mov ах,@data
<24>
mov ds, ах
<25>
<2б>
push arg_l;запись в стек 1-го аргумента
<27>
push arg二 2:запись в стек 2-го аргумента
<28>
<29>
push arg_n;запись в стек n-го аргумента
<30>
call proc_l;вызов процедуры proc_l
<31>
;действия по очистке стека после возврата из процедуры
<32>
33> ml:
34>_exit
35> main endp
36>end main
Код пролога состоит всего из двух команд. Первая команда push bp сохраняет содержимое ВР в стеке с тем, чтобы исключить порчу находящегося в нем значения в вызываемой процедуре. Вторая команда пролога movbp,sp настраивает ВР на вершину стека. После этого можно не волноваться о том, что содержимое SP перестанет быть актуальным, и осуществлять прямой доступ к содержимому стека. Что обычно и делается. Для доступа к arg_n достаточно сместиться от содержимого BP на 4, для arg_{n-l} — на 6 и т. д. Но эти смещения подходят только для процедур типа near. Для процедур типа far эти значения необходимо скорректировать еще на 2, так как при вызове процедуры дальнего типа в стек записывается полный адрес — содержимое CS и IP. Поэтому для доступа к arg_n в строке 9 команда будет выглядеть так: mov ax,[bp+6], а для arg_{n-l}, соответственно, mov ax,[bp+8] и т. д.
Конец процедуры также должен быть оформлен особым образом, обеспечивая корректный возврат из процедуры. Фрагмент кода, выполняющего такие действия, имеет свое название — эпилог процедуры. Код эпилога должен восстановить контекст программы в точке вызова процедуры из вызывающей программы. При этом, в частности, нужно откорректировать содержимое стека, убрав из него ставшие ненужными аргументы, передававшиеся в процедуру. Это можно сделать несколькими способами.
緣Можно использовать последовательность из п команд pop xx. Лучше всего это делать в вызывающей программе сразу после возврата управления из процедуры.
瀨Можно откорректировать регистр указателя стека SP на величину 2 • п, например, командой add sp,NN, где NN = 2 • п, а п — количество аргументов. Это также лучше делать после возврата управления вызывающей процедуре.
ii Можно использовать машинную команду RET n в качестве последней исполняемой команды в процедуре. Здесь п — количество байтов, на которое нужно увеличить содержимое регистра ESP\SP после того, как со стека будут сняты составляющие адреса возврата. Видно, что этот способ аналогичен предыдущему, но выполняется процессором автоматически.
В каком виде можно передавать аргументы в процедуру? Ранее упоминалось, что передаваться могут либо данные, либо их адреса (указатели на данные). В языке высокого уровня это называется передачей по значению и по адресу, соответственно.
Наиболее простой способ передачи аргументов в процедуру — передача по значению. Этот способ предполагает, что передаются сами данные, то есть их значения. Вызываемая программа получает значение аргумента через регистр или через стек. Естественно, что при передаче переменных через регистр или стек на их размер накладываются ограничения, связанные с размерностью используемых регистров или стека. Другой важный момент заключается в том, что при передаче аргументов по значению в вызываемой процедуре обрабатываются их копии. Поэтому значения переменных в вызывающей процедуре не изменяются.
Способ передачи аргументов по адресу предполагает, что вызываемая процедура получает не сами данные, а их адреса. В процедуре нужно извлечь эти адреса тем же методом, как это делалось для данных, и загрузить их в соответствующие регистры. После этого, используя адреса в регистрах, следует выполнить необходимые операции над самими данными. В отличие от передачи данных по значению, при передаче данных по адресу в вызываемой процедуре обрабатывается не копия, а оригинал передаваемых данных. Поэтому при изменении данных в вызываемой процедуре они автоматически изменяются и в вызывающей программе, так как изменения касаются одной области памяти.
Использование директив EXTRN и PUBLIC
Директивы EXTRN и PUBLIC мы уже упоминали, когда рассматривали варианты взаимного расположения вызывающей программы и вызываемой процедуры. Ко всему сказанному добавим, что директивы EXTRN и PUBLIC также можно использовать для обмена информацией между модулями. Назначение и форматы этих директив остаются теми же, поэтому сейчас опишем только порядок их использования для обмена данными. Возможны несколько вариантов их применения:
綴 оба модуля используют сегмент данных вызывающей программы;
趣 у каждого из модулей есть собственный сегмент данных;
趣 модули используют атрибут комбинирования (объединения) сегментов private в директиве сегментации SEGMENT.
Рассмотрим эти варианты на примере программы, которая определяет в сегменте данных две символьные переменные и вызывает процедуру, выводящую эти символы на экран.
В первом варианте, в котором два модуля используют только сегмент данных вызывающей программы, не требуется переопределения сегмента данных в вызываемой процедуре. В листинге 15.6 в вызывающей программе определены две переменные, вывод на экран которых осуществляет вызываемая программа (листинг 15.7).
Листинг 15.6. Первый вариант использования директив EXTRN и PUBLIC (модуль 1)
;prgl5_6..asm
;Вызывающий модуль
include mac.1nc
extrnmy_P「oc2:far
public ре?1,рег2 stk segment stack
db 256 dup (0) stk ends
datasegment
perldb "1"
per2db "2"
dataends
codesegment
mainproc far
assume cs:code,ds:data,ss:stk
mov ax,data
mov ds,ax
call my_proc2
exit.
продолжение iK
Листинг 15.6 (продолжение)
mainendp
codeends
end main
Листинг15.7.Первый вариант использования директив extrn и public (модуль 2)
;prgl5_7.asm
вызываемый модуль
include mac.i nc
extrn perl:byte,per2:byte
public my_P「oc2 codesegment
my~proc2 procfar assume cs:code ;вывод символов на экран
mov dl,perl OutChar mov dl,per2 OutChar ret
my_proc2 endp codeends
end
Сборка программы из двух модулей для этого и следующих вариантов осуществляется так же, как было показано в листингах 15.4 и 15.5.
Во втором варианте у каждого из модулей есть свой собственный сегмент данных. В этом случае для доступа к разделяемым переменным из другого модуля требуется переопределение сегмента данных в вызываемой процедуре (строки 1719 и 23-24 листинга 15.8).
Листинг 15.8. Второй вариант использованиядирективextrn и public
;prgl5_8.asm
;Вызывающий модуль - тот же, что и для предыдущего варианта.
1> ;Вызываемый модуль
2> include mac.inc
3> extrn perl:byte,per2:byte
4> public my_proc2
5> data segment
6> perQ db "0"
7> data ends
<10>assume cs:code,ds:data<11>;вывод символов наэкран<12>mov ax,data<13>mov ds,ax<14>mov dl,per©<15>OutChar<16>pushds;сохранили ds<17>mov ax.seg perl;сегментный адрес perl в ds<18>mov ds,ax<19>mov dl,perl<20>OutChar;вывод perl<21>mov dl,per2<22>OutChar:вывод per2<23>pop ds;восстановили ds<24>mov dl,pe「®<25>OutChar;и еще раз perO<26>ret<10>assume cs:code,ds:data<11>;вывод символов наэкран<12>mov ax,data<13>mov ds,ax<14>mov dl,per©<15>OutChar<16>pushds;сохранили ds<17>mov ax.seg perl;сегментный адрес perl в ds<18>mov ds,ax<19>mov dl,perl<20>OutChar;вывод perl<21>mov dl,per2<22>OutChar:вывод per2<23>pop ds;восстановили ds<24>mov dl,pe「®<25>OutChar;и еще раз perO<26>ret<9> my_p「oc2 procfar<9> my_p「oc2 procfar<8> code segment
<10>
assume cs:code,ds:
data
<11>
;вывод символов на
экран
<12>
mov ax,data
<13>
mov ds,ax
<14>
mov dl,per©
<15>
OutChar
<16>
pushds
;сохранили ds
<17>
mov ax.seg perl
;сегментный адрес perl в ds
<18>
mov ds,ax
<19>
mov dl,perl
<20>
OutChar
;вывод perl
<21>
mov dl,per2
<22>
OutChar
:вывод per2
<23>
pop ds
;восстановили ds
<24>
mov dl,pe「®
<25>
OutChar
;и еще раз perO
<26>
ret
<10>
assume cs:code,ds:
data
<11>
;вывод символов на
экран
<12>
mov ax,data
<13>
mov ds,ax
<14>
mov dl,per©
<15>
OutChar
<16>
pushds
;сохранили ds
<17>
mov ax.seg perl
;сегментный адрес perl в ds
<18>
mov ds,ax
<19>
mov dl,perl
<20>
OutChar
;вывод perl
<21>
mov dl,per2
<22>
OutChar
:вывод per2
<23>
pop ds
;восстановили ds
<24>
mov dl,pe「®
<25>
OutChar
;и еще раз perO
<26>
ret
<9> my_p「oc2 procfar
<9> my_p「oc2 procfar
<27> my_proc2 endp
<28>code ends
<29>end
Рассмотрим улучшенный второй вариант программы (листинг 15.9). В предыдущем случае мы использовали для адресации данных в разных сегментах данных один регистр DS, а теперь для доступа к разделяемым переменным из другого модуля задействуем один из дополнительных сегментных регистров данных, к примеру ES. Заметьте, что обращение к данным другого сегмента осуществляется с помощью префикса замены сегмента (строки 18 и 20).
Листинг15.9.Улучшенныйвторойвариантиспользованиядирективextrnиpublic
;prgl5_9.asm
:Вызывающий модуль - тот же, что и для предыдущего варианта.
1>;Вызываемый модуль
2>includeiomac.inc
3>extrnperl:byte.per2:byte
4>publicmy_proc2
5>datasegment
6>per0db "0"
7>dataends
8>codesegment
9>my_proc2 proc far
10>assume cs: code,ds :data
¥1>;вывод символов на экран
12>mov ax,data
13>mov ds, ax
14>mov dl,per0
15>OutChar
16>movax, seg perl
17>moves, ax
18>movdl,es:perl
19>OutChar
20>movdl,es:per2
21>OutChar
22>mov dl,per0
C3>OutChar
24>ret
25>my_proc2 endp
26> codeends
27>end
Третий вариант предполагает использование атрибута комбинирования (объединения) сегментов public в директиве сегментации SEGMENT для сегментов данных модулей (листинги 15.10и15.11).Это значение атрибута комбинирования заставляет компоновщик объединить последовательно сегменты с одинаковыми именами. Все адреса и смещения будут вычисляться относительно начала этого нового сегмента. Тогда не понадобится производить дополнительной настройки сегментных регистров (как было в двух предыдущих случаях).
Листинг 15.10. Третий вариант использования директив extrn и public (модуль 1)
;prg!5__10. asm
;ВЫЗЫВё1ЮЩИЙ модуль include mac. i nc extrn my_proc2:far,perO:byte public peFl,per2 stk segment stack db 256 dup (0)
stk endsпродолжение夕
Листинг 15-10(продолжение) datasegmentpara public "data"
perldb "1"
per2db "2"
dataends
codesegment
mainprocfar
assume cs;code,ds:data,ss : stk mov ax,data mov ds,ax mov dl ,per0 OutChar call my_proc2 exit mainendp
codeends
end main
Листинг15-11.Третий вариант использования директивextrn и public (модуль 2)
;prgl5_ll.asm
;Вызываемый модуль include mac.inc extrn perl:byte,per2:byte public my_proc2,perO datasegment para public "data"
pe「®db "0"
dataends
codesegment
my_proc2 proc far assume cs:code,ds:data
;ds загружать не надо, так как компоновщик его присоединит ;к сегменту данных первого модуля :вывод символов на экран
mov dl,perO OutChar mov dl.perl OutChar mov dl,per2 OutChar mov dl,perO OutChar ret
my_proc2endp
codeends
end
Возврат результата из процедуры
В отличие от языков высокого уровня, в языке ассемблера нет отдельных понятий для процедуры и функции. Организация возврата результата из процедуры полностью ложится на программиста. Если исходить из того, что получение результата — частный случай передачи аргументов, то программисту доступны три варианта возврата значений из процедуры.
is С использованием регистров. Ограничения здесь те же, что и при передаче данных, — это небольшое количество доступных регистров и их фиксированный размер. Функции DOS используют именно этот способ. Из рассматриваемых здесь трех вариантов данный способ является наиболее быстрым, поэтому его
есть смысл задействовать для организации критичных по времени вызова процедур с малым количеством аргументов.
畿 С использованием общей области памяти. Этот способ удобен при возврате большого количества данных, но требует внимательности в определении областей данных и подробного документирования, чтобы устранить неоднозначность при трактовке содержимого общих участков памяти.
趣 С использованием стека. Здесь, подобно передаче аргументов через стек, также требуется регистр В Р. При этом возможны следующие варианты:
□ использование для возвращаемых аргументов тех же ячеек в стеке, которые применялись для передачи аргументов в процедуру, то есть предполагается замещение ставших ненужными входных аргументов выходными данными;
D предварительное помещение в стек наряду с передаваемыми аргументами фиктивных аргументов с целью резервирования места для возвращаемого значения (в этом варианте процедура, конечно же, не должна пытаться очистить стек командой RET, эту операцию придется делать в вызывающей программе, например, командой POP).
В ходе приведенного обсуждения мы выяснили, что ассемблер не накладывает никаких ограничений на организацию процесса передачи данных и возврата значений между двумя процедурами, а в более общем случае — и между модулями, представляющими отдельные файлы. Наиболее быстрый способ такого обмена — использование регистров. Но часто требуется связывать между собой не только программы, написанные на ассемблере, но и программы на разных языках. В этом случае универсальным является обмен данными через стек. Далее мы рассмотрим, как решается проблема связи программных модулей, написанных на языке ассемблера и языках высокого уровня.
Директива INVOKE
Для более удобного вызова процедур с параметрами, передающимися через стек, MASM предоставляет специальное средство в виде директивы INVOKE:
INVOKE имя_процедуры [, аргументы]
Основная ее задача — сформировать код, который, во-первых, размещает аргументы в стеке, во-вторых, вызывает процедуру и, в третьих, чистит стек после завершения работы процедуры. Например, традиционный способ вызова процедуры выглядит так:
pushпараметру
pushпараметр^
pushпараметр^!
call имя_процедуры
С использованием INVOKE тот же самый вызов будет выглядеть следующим образом:
INVOKE имя_процедуры, параметр_1, параметр^? . . . лараметр_п
Для INVOKE аргумент имя_процедуры не должен быть опережающей ссылкой на адрес. Чтобы исключить подобные ситуации, существует «парная» для INVOKE директива PROTO:
имя_процедуры PROTO [расстояние] [язык] [, [параметр]:тип]...
Эта директива информирует ассемблер о количестве и типах аргументов, которые принимает процедура. Использование данной директивы позволяет ассемблеру выполнять проверку типов. Обычно все директивы PROTO для процедур собираются в начале исходного текста программы либо в отдельном включаемом файле. Директива PROTO принимает три типа аргументов — расстояние, язык, параметры процедуры с указанием их типов.
т Аргумент расстояние (NEAR, FAR, NEAR16, NEAR32, FAR16 или FAR32) влияет на размер адреса, формируемого ассемблером для вызова процедуры. По умолчанию значение этого параметра определяется исходя из текущей модели памяти и типа процессора.
綫 Аргумент язык для определения стиля и соглашения по вызову процедуры в качестве значения принимает имя языка (табл. 15.1).
綫Аргумент параметр представляет собой последовательность перечисленных через запятую параметров процедуры. Исходя из этой информации, при вызове процедуры ассемблер преобразует последовательность параметров в последовательность команд PUSH с формированием соответствующих адресов параметров процедуры в стеке.
經 Аргументтип — один из допустимых ассемблером простых типов данных. В качестве типа может быть указано слово VARARG. Оно предназначено для определения процедур с переменным числом аргументов. Тип VARARG указывается с последним параметром, заданным в директиве PROTO. Тип VARARG можно использовать, если аргументом язык является С, SYSCALL или STDCALL.
Таблица 15.1. Передача аргументов в языках высокого уровня
Операнд «язык»
Язык аргументов
Направление передачи стека
Процедура очистки
NOLANGUAGE
Ассемблер
Слева направо
Вызываемая
BASIC
Basic
Слева направо
Вызываемая
PROLOG
Prolog
Справа налево
Вызывающая
FORTRAN
Fortran
Слева направо
Вызываемая
С
С
Справа налево
Вызывающая
C++(CPP)
C++
Справа налево
Вызывающая
PASCAL
Pascal
Слева направо
Вызываемая
STDCALL
—
Справа налево
Вызываемая
SYSCALL
C++
Справа налево
Вызывающая
Приведем типовую последовательность действий в программе для вызова процедуры в MASM:
задание прототипа процедуры prod proclPROTOstdcalI :dword, :dword
;вызов процедуры
invoke prod, argl, arg2
Следует отметить разницу в описании процедур в MASM и TASM. Общий вид представления процедуры в MASM похож на стиль TASM, но имеет отличия в синтаксисе и содержании:
имя_процедуры PROC [расстояние] [язык] [видимость]
~[<аргументы пролога\эпилога>]
[USES спмсок_ре「истров] [,аргумент (: тип]]...
[LOCAL список_переменных]
тело процедуры
[RET [количество_байтов]] имя^процедуры ENDP
Параметры расстояние, язык и видимость имеют сходные с описанием процедуры в TASM значения. Параметр расстояние показывает локализацию места, из которого может быть вызвана процедура, с помощью одного из ключевых слов: NEAR, FAR, NEAR16, NEAR32, FAR16 или FAR32. Параметр язык — один из допустимых типов языков (см.табл. 15.1). Параметрвидимостьопределяетдоступностьпроцедурыдля других модулей и имеет одно из значений PRIVATE (по умолчанию), PUBLIC или EXPORT. Этот параметр можно изменить директивой OPTION PROC.
Последней командой процедуры обычно является RET. В качестве ее операнда можно указать число байтов, которые процедура должна удалить из стека при возврате управления в точку вызова.
Ассемблер автоматически генерирует код пролога и эпилога для правильной передачи аргументов в процедуру через стек и очистки стека при возврате из нее. Код пролога и эпилога можно несколько изменить применением директив OPTION PROLOGUE и OPTION EPILOGUE.
Локальные переменные объявляются в процедуре директивой LOCAL (перед любыми командами). В процедуре может быть несколько директив LOCAL.
Аргументы пролога/эпилога — аргументы, определяющие особенности пролога и эпилога процедуры. Аргументы по умолчанию — PROLOGUE и EPILOGUE. Другие значения:
- FORCEFRAME — генерация сегмента стека;
LOADDS — сохранение регистра DS в прологе процедуры и восстановление его в эпилоге.
Если аргументов несколько, то они разделяются запятыми.
При вызове процедуры можно указать регистры общего назначения, которые нужно сохранить в стеке и назначить символические имена адресам в стеке, которые логически являются параметрами процедуры. Все это делает директива USES. Имена перечисляемых регистров разделяются пробелами, а имена переменных — запятыми. Для переменных может указываться тип. В качестве значения типа может быть либо имя простого типа (например, DWORD), либо VARARG. Служебное слово VARARG позволяет задать переменное число параметров. При его использовании оно должно быть последним в списке параметров процедуры. Служебное слово VARARG указывается только, если параметр язык равен С, SYSCALL или STDCALL По умолчанию параметр тип равен WORD для 16-разрядного сегмента и DWORD — для 32-разрядного сегмента.
Связь ассемблера с языками высокого уровня
На протяжении всего учебника мы неоднократно подчеркивали сильные и слабые стороны языка ассемблера как языка программирования. Писать на нем достаточно объемные программы утомительно. И всегда ли это нужно? Конечно, не всегда. Если программа не предназначена для решения каких-то системных задач, требующих максимально эффективного использования ресурсов компьютера, если к ней не предъявляются сверхжесткие требования по размеру и времени работы, если вы не «фанат» ассемблера — то, на мой взгляд, следует подумать о выборе одного из языков высокого уровня. Существует и третий, компромиссный путь — комбинирование программ на языке высокого уровня с кодом на ассемблере. Такой способ обычно используют в том случае, если в вашей программе есть фрагменты, которые либо вообще невозможно реализовать без ассемблера, либо ассемблер может значительно повысить эффективность работы программы.
Большинство компиляторов учитывают возможность комбинирования их «родного» кода с ассемблером. Как именно? Это зависит от конкретного компилятора языка высокого уровня. Учитывая, что большинство программистов работают или, по крайней мере, владеют основами программирования на языках C/C++ и Pascal, дальнейшее обсуждение будет касаться именно этих языков. В настоящее время существует несколько их основных реализаций, поддерживаемых разными фирмами-производителями. В этих реализациях имеются, в основном, одинаковые механизмы связи с языком ассемблера. Единственное, что остается сделать при реализации конкретной задачи на конкретном компиляторе, — уточнить в документации на язык нужные параметры связи и, возможно, особенности организации связи с кодом на ассемблере. Невозможно дать универсальные рекомендации по этому вопросу и остается лишь сосредоточиться на отображении наиболее принципиальных моментов связи программ на языках Pascal и C/C++ с ассемблером, актуальных для большинства реализаций этих языков.
Вначале мы отметим общие моменты, актуальные как для C/C++, так и для Pascal. Затем на примерах конкретных программ мы обсудим моменты, специфичные для каждого из этих языков.
Существуют следующие формы комбинирования программ на языках высокого уровня с ассемблером.
Р Использование операторов типа inline и ассемблерных вставок в виде встраиваемого ассемблерного кода. Эта форма в значительной степени зависит от синтаксиса языка высокого уровня и конкретного компилятора. Она предполагает, что ассемблерные коды в виде команд ассемблера или прямо в машинных командах вставляются в текст программы на языке высокого уровня. Компилятор языка распознает их как команды ассемблера (машинные коды) и без изменений включает в формируемый им объектный код. Эта форма удобна, если надо вставить небольшой фрагмент.
緻 Использование внешних процедур и функций. Это более универсальная форма комбинирования. У нее есть ряд преимуществ:
написание и отладку программ можно производить независимо;
написанные подпрограммы можно использовать в других проектах;
П облегчаются модификация и сопровождение подпрограмм в течение жизненного цикла проекта (к примеру, если ваша процедура на ассемблере производит работу с некоторым внешним устройством, то при смене устройства вам нет необходимости перекраивать весь проект — достаточно заменить только работающую с ним процедуру,-оставив неизменным интерфейс программы на языке высокого уровня с ассемблером).
Встраиваемый ассемблерный код
Большинство компиляторов языков высокого уровня поддерживают возможность вставки ассемблерного кода в обрабатываемые ими программы. В данном разделе основное внимание уделяется ассемблерным вставкам в программах на C++: так как именно этот язык наиболее часто используется для реализации задач системного программирования.
Оператор inline языка Pascal представляет собой следующую синтаксическую конструкцию:
inline(машинные_коды)
В скобках указывается строка машинных кодов. Для получения такой строки целесообразно написатьнужный фрагментна ассемблере, скомпилировать исполняемый модуль, а затем запустить отладчик. В окне CPU отладчика вы увидите машинные коды ваших инструкций; их нужно переписать и вставить в скобки оператора inline.
Встраивание ассемблерного кода в программы C++ производится директивой _asm. Причем этой директивой возможна вставка как одной команды ассемблера, так и группы команд. Синтаксис:
asm команда_ассемблера [;комментарий]
asm {
команда_ассемблера [комментарий]
команда_ассемблера [//комментарий или /* комментарий */] }
Планируя использование встроенного ассемблера, важно хорошо представлять себе его возможности и ограничения.
Можно:
е передавать параметры, как в случае с внешней ассемблерной процедурой;
иметь непосредственный доступ к командам и регистрам процессора;
И ссылаться на метки и переменные вне текущего блока, находящиеся в пределах видимости ассемблерной вставки;
綾 вызывать функции вне пределов ассемблерной вставки, причем эти функции должны быть ранее объявлены в программе (на уровне прототипа).
использовать описание констант как в стиле ассемблера, так и C++;
緻 использовать операторы PTR, LENGTH, SIZE, TYPE и директивы EVEN и ALIGN.
Нельзя:
緻 использовать директивы определения данных простых (DB и DD) и сложных типов (STRUC, RECORD), то есть каким-либо образом определять данные любого типа;
описывать функции в пределах ассемблерной вставки;
漆 использовать в командах большинство операторов ассемблера типа OFFSET, SEG, SHR, SHL (вместо OFFSET можно использовать LEA);
использовать любые директивы макроопределений;
обращаться к полям структур и объединений.
Внешний ассемблерный код
Так как вариант с использованием операторов inline и ассемблерных вставок обладает довольно большими ограничениями, он не может быть признан универсальным, и для реализации сложных задач остается организация связи программ на языках высокого уровня с ассемблерным кодом через внешние процедуры и функции. Возможны два вида такой связи — программа на языке высокого уровня вызывает процедуру на ассемблере и наоборот. В данной главе ограничимся рассмотрением связи только в одну сторону, когда программа на языке высокого уровня вызывает процедуру на ассемблере. Это наиболее часто используемый вид связи.
Вспомним (см. главу 10) синтаксис директивы PROC компилятора TASM: имя_процедуры PROC [[модификатор_языка]язык] [расстояние]
Один из операндов — язык. Он служит для того, чтобы компилятор мог правильно организовать интерфейс (связь) между процедурой на ассемблере и программой на языке высокого уровня. Необходимость такого указания возникает вследствие того, что способы передачи аргументов при вызове процедур различны для разных языков высокого уровня.
TASM поддерживает несколько значений операнда язык. Ранее в табл. 15.1 для некоторых из этих значений были приведены характерные особенности передачи аргументов и соглашения о том, какая процедура очищает стек — вызывающая или вызываемая. Под направлением передачи аргументов понимается порядок, в котором аргументы включаются в стек, по сравнению с порядком их следования в вызове процедуры. Для языка Pascal характерен прямой порядок включения аргументов в стек: первым в стек записывается первый передаваемый аргумент из оператора вызова процедуры, вторым — второй аргумент и т. д. На вершине стека после записи всех передаваемых аргументов оказывается последний аргумент. Для языков C/C++, наоборот, характерен обратный порядок передачи аргументов. В соответствии с ним в стек сначала включается последний аргумент из оператора вызова процедуры (или функции), затем предпоследний и т. д. В конечном итоге на вершине стека оказывается первый аргумент. Это делает возможной передачу переменного количества параметров при вызове функций в языках C/C++. Наверху стека оказывается первый параметр функции, значение которого — предоставление информации о количестве аргументов в данном вызове функции. Что же касается очистки стека, то понятно, что должны быть определенные договорен-
ности об этом. В языке Pascal эту операцию всегда совершает вызываемая процедура, в языках C/C++ — вызывающая. При разработке программы с использованием только одного языка высокого уровня об этом задумываться не имеет смысла, но если мы собираемся связывать несколько «разноязыких» модулей, то эти соглашения нужно иметь в виду.
Вспомните действия, которые мы проделывали для того, чтобы настроиться на аргументы в стеке. Теперь, после указания языка, с программой на котором должна осуществляться связь, все действия по настройке стека будут производиться компилятором. При этом в текст процедуры он включит дополнительные команды входа в процедуру (пролог) и выхода из нее (эпилог), причем код эпилога может повторяться несколько раз — перед каждой командой RET. Для значения NOLANGUAGE и по умолчанию коды пролога и эпилога не создаются.
Для пояснения последних замечаний рассмотрим на конкретных примерах организацию связей между модулями на ассемблере и модулями наиболее популярных языков высокого уровня — С и Pascal. He стоит воспринимать нижеследующие примеры как образец оптимальности — они лишь в учебных целях призваны проиллюстрировать принципы организации межъязыковых связей.
Pascal и ассемблер
Организацию связи языков Pascal и ассемблер рассмотрим на следующем примере: разработаем программу на языке Pascal, которая выводит символ заданное количество раз начиная с определенной позиции на экране (листинги 15.12 и 15.13). Все числовые аргументы определяются в программе на Pascal. Вывод символа осуществляет процедура ассемблера. Очевидно, что основная проблема в этой задаче — организация взаимодействия модулей на Pascal и ассемблере.
Листинг 15.12. Взаимодействие Pascal—ассемблер (модуль на Pascal)
<l>{prgl5_12.pas}
2>{Программа, вызывающая процедуру на ассемблере}
3>program ray_pas;
4>{$D+} {включение полной информации для отладчика}
5>uses crt;
6>procedure asmproc(ch:char;х,у,koi:integer): external;
7>{процедураasmprocобьявленакак внешняя}
8>{$L с:\bp\work\prgl5_12,obj}
9>BEGIN
10>clrscr; {очистка экрана}
11>asmproc( 'а1 ,1,4(5);
12>asmproc('s',9,2,7);
13> END.
Листинг 15.13. Взаимодействие Pascal—ассемблер (модуль на ассемблере)
1> ;prgl5_12.asm
2> Процедура на ассемблере, которую вызывает
3>;программа на Pascal.
4> :Для вывода на экран используются службы BIOS:
5>;Q2h - позиционирование курсора.
б>;09h - вывод символа заданное количество раз.
7>MASM
8>MODELsmall
продолжение 按продолжение 按9>STACK256
продолжение 按
продолжение 按
10>.code
Листинг(продолжение)
<11>proc near
<12>PUBLIC asmproc
как внешняя
<13>
pushbp
;пролог
<14>
mov bp,sp
<15>
mov
строки для
вывода
<16>
у - в
<17>mov
dl,[bp+8]
столбца для
вывода
<18>;символа
х - в
<19>mov
ah,02h
службы BIOS
<20>
10h
прерывания
BIOS
<21>;вызов 1
функциипрерывания BIOS
<22>;вывод
символа из а\ на
экран
<23>mov
ah,09h
службы BIOS
<24>mov
al,[bp+10]
;символв
<25> mov
bl,07h
;атрибут символа -
в
<26>xor
bh,bh
<27> mov
ex,[bp+4]
;копичество "выводов"
<28>;символа - в сх
<29>int
10h
прерывания
BIOS
<30>pop
bp
bp
<31>;очистка стека и возврат
из процедуры
<32>ret
8
endp
процедуры
<34>end
Процесс организации такой связи состоит из нескольких шагов.
Написать процедуру на ассемблере дальнего (far) или ближнего типа (near). Назовем ее для примера asmproc. В программе на языке ассемблера (назовем ее в которую входит процедура asmproc, необходимо объявить имя этой процедуры внешним с помощью директивы
PUBLIC asmproc
Для того чтобы процедура на ассемблере при компоновке с программой на Pascal воспринималась компилятором Borland Pascal 7.0 как far или near, недостаточно просто объявить ее таковой в директиве PROC (строка 11 листинга 15.13). Кроме того, вам нужно включить или выключить параметр компилятора, доступный через меню интегрированной среды: OptionsForce far calls.
Установка этого параметра заставляет компилятор генерировать дальние вызовы подпрограмм. Альтернатива данного параметра — ключили(соответ
ственно, включено или выключено) в программе. Это — локальные ключи, то есть в исходном тексте программы на Pascal их может быть несколько, и они могут, чередуясь друг с другом, поочередно менять форму генерируемых адресов перехода: дляподпрограмм — дальние вызовы, для других — ближние.
Произвести компиляцию программыс целью устранения синтак
сических ошибок и получения объектного модуля
tasm /zi
В программена Pascal, которая будет вызывать внешнюю процедуру
на ассемблере, следует вставить директиву компилятора
Эта директива заставит компилятор в процессе компиляции программы загрузить с диска объектный модуль программы
В программенеобходимо объявить процедурукак внешнюю.
В итоге последние два объявления в программе на Pascal будут выглядеть так: external;
Если вы собираетесь исследовать в отладчике работу программы, то необходимо потребовать, чтобы компилятор включил отладочную информацию в генерируемый им исполняемый модуль. Для этого есть две возможности. Первая заключается в использовании глобального ключа {$D+}・Этот ключ должен быть установлен сразу после заголовка программы на Pascal. Вторая альтернативная возможность заключается в установке параметра компилятора: Options ► Compiler ► Debug Information.
Выполнить компиляцию программы на Pascal. Для компиляции удобно использовать интегрированную среду. Для изучения особенностей связки Pascal — ассемблер удобно прямо в интегрированной среде перейти к работе в отладчике командой Tools ► Turbo Debugger (или клавишами Shift+F4). Будет загружен отладчик. Его среда вамхорошознакома; вданном случае в окнеModule вы увидите текст программы на Pascal. Нажимая клавишу F7, вы в пошаговом режиме будете исполнять программу на Pascal. Когда очередь дойдет до вызова процедуры на ассемблере, отладчик откроет окно с текстом программы на ассемблере. Но наш совет вам — не ждать этого момента, так как вы пропустите некоторые интересные вещи. Дело в том, что отладчик скрывает момент перехода из программы на Pascal в процедуру на ассемблере. Поэтому лучше всего испол- нятьпрограммуприоткрытом окнеCPUотладчика. И тогдавы станете свидетелями тех процессов, которые мы будем обсуждать далее.
Если бы взаимодействие программ ограничивалось только передачей и возвратом управления, то на этом обсуждение можно было бы и закончить. Но дело значительно усложняется, когда требуется передать аргументы (в случае процедуры) или передать аргументы и возвратить результат (в случае функции). Рассмотрим процессы, которые при этом происходят.
Передача аргументов при связи модулей на разных языках всегда производится через стек. Компилятор Pascal генерирует соответствующие команды при обработке вызова процедуры ассемблера. Это как раз те команды, которые отладчик пытался скрыть от нас. Они записывают в стек аргументы и генерируют команду CALL для вызова процедуры ассемблера. Чтобы убедиться в этом, просмотрите на исполняемый кодпрограммы вокнеCPU отладчика. Послеобработки вызовапро- цедуры и в момент передачи управления процедуре asmproc содержимое стека будет таким, как показано нарис. 15.1,а. Для доступаКэтим аргументам можно применять различные методы, наиболее удобный из них — использование регистра ВР.
Регистр ВР, как уже отмечалось, специально предназначен для организации произвольного доступа к стеку. Когда мы рассматривали связь ассемблерных модулей, то говорили о необходимости добавления в текст вызываемого модуля фрагментов, настраивающих его на передаваемые ему аргументы. При объединении разноязыких модулей также нужно вставлять подобные дополнительные фрагменты кода. Они, кроме всего прочего, позволят учесть особенности конкретного языка. Фрагмент, вставляемый в самое начало вызываемого модуля, называется прологом модуля (процедуры). Фрагмент, вставляемый перед командами передачи управления вызывающему модулю, называется эпилогом модуля (процедуры). Его назначение — восстановление состояния вычислительной среды на момент вызова данного модуля.
0000:00000000:0000SS:$PfSS:$Pfss:spss:spss:ffff->ss:ffff->ss:ffff">ss:ffff">Старшие адреса оперативной памятиСтаршие адреса оперативной памятиааОперативная памятьОперативная памятьipipkolkolУУXXchchДно стекаДно стекаОперативная памятьОперативная память0000:00000000:0000Оперативная память ГОперативная память ГbpIPkolУXchДно стекаbpIPkolУXchДно стекаss:spss:bp 1yzss:spss:bp 1yzтт+4+6+8+10+4+6+8+10...-bR k'Pol.-bR k'Pol..тт--ss:ffff-»ss:ffff-»Xch Дно стекаXch Дно стекаСтаршиеадресаоперативной памятибСтаршиеадресаоперативной памятибСтаршиеадресаоперативной памятивСтаршиеадресаоперативной памятив
0000:0000
0000:0000
SS:$Pf
SS:$Pf
ss:sp
ss:sp
ss:ffff->
ss:ffff->
ss:ffff">
ss:ffff">
Старшие адреса оперативной памяти
Старшие адреса оперативной памяти
а
а
Оперативная память
Оперативная память
ip
ip
kol
kol
У
У
X
X
ch
ch
Дно стека
Дно стека
Оперативная память
Оперативная память
0000:0000
0000:0000
Оперативная память Г
Оперативная память Г
bp
IPkol
У
XchДно стека
bp
IPkol
У
XchДно стека
ss:sp
ss:bp 1yz
ss:sp
ss:bp 1yz
т
т
+4
+6
+8
+10
+4
+6
+8
+10
.
.
.-bR k'Pol
.-bR k'Pol
.
.
т
т
-
-
ss:ffff-»
ss:ffff-»
X
ch Дно стека
X
ch Дно стека
Старшиеадресаоперативной памятиб
Старшиеадресаоперативной памятиб
Старшиеадресаоперативной памяти
в
Старшиеадресаоперативной памяти
в
Рис.Изменение содержимого стека при передаче управления в связке
Pascal—ассемблер
Рассмотрим действия, выполняемые кодами пролога и эпилога при организации связи
Действия, выполняемые кодом пролога.
Сохранить значение Ьр в стеке. Это делается с целью запоминания контекста вызывающего модуля. Стек при этом будет выглядеть, как показанорис.б.
Записать содержимое SP в ВР. Тем самым ВР теперь тоже будет указывать на вершину стека (рис.в).
После написания кода пролога все обращения к аргументам в стеке можно организовывать относительно содержимого регистра ВР. Из рис.в видно, что для
обращения к верхнему и последующим аргументам в стеке содержимое Ьр необходимо откорректировать. Нетрудно посчитать, что величина корректировки будет различаться для процедур дальнего (far) и ближнего (near) типов. Причина понятна: при вызове процедуры типа near в зависимости от установленного режима адресацииили use32) в стек записывается 2(4) байта в качестве адреса
возврата (содержимое а при вызове процедуры типа far в стек записывается 4(8) байта (содержимое IP/EIP и
Таким образом, коды пролога для процедур ближнего и дальнего типа соответственно будут выглядеть следующим образом:
procnear
пролог для процедуры типа near
pushbp
mov bp,sp
прологу можно добавить команду
bp на 4 с тем, чтобы регистр
на верхний из передаваемых аргументов в стеке
addуказывает на kol
asmproc proc
Если действует режим адресациито в стек записываются двойные слова. По этой причине
запись 16-разрядного регистра cs также производится четырьмя байтами, при этом два старших бай-
та этого значения нулевые.
;пролог для процедуры типа far
pushbp
raovbp,sp,
;к прологу можно добавить команду
;корректировки bp на 6 с тем, чтобы регистр bp
указывал на верхний из передаваемых аргументов в стеке
add Ьр,6;теперь Ьр указывает на koi
Далее доступ к переданным в стеке данным осуществляется, как показано в листинге 15.7.
Как видите, все довольно просто. Но если мы вдруг решили изменить тип нашей процедуры ассемблера с far на near или наоборот, то нужно явно изменить и код пролога. Это не совсем удобно. TASM предоставляет выход в виде директивы ARG, которая служит для работы с аргументами процедуры. Синтаксис директивы ARG иллюстрирует рис. 15.2.
Т ARGРт-| Аргумент hr-j—।—
IЧЭЧ Аргумент卜 *"€Н HgeHUKt^gro戸
I©1
Аргумент:
Рис. 15.2. Синтаксис директивы ARG
Несколько слов об обозначениях на рисунке:
я имя — идентификатор переменной, который будет использоваться в процедуре на ассемблере для доступа к соответствующей переменной в стеке;
* тип — тип данных аргумента (по умолчанию WORD для usel6 и DWORD для use32); 經 значение_1 определяет количество аргументов с данным именем. Место в стеке для них будет определено, исходя из расчета:
значение _1• значение^ •размер_типа.
По умолчанию значение」 — 1;
^ значение_2 определяет, сколько элементов данного типа задает данный аргумент. По умолчанию его значение равно 1, но для типа byte значение^ = 2, так как стековые команды не работают с отдельными байтами. Хотя, если явно задать значение^ = 1, то транслятор действительно будет считать, что в ячейку стека помещен один байт;
я идентификатор — имя константы, значение которой присваивает транслятор.
Об идентификаторе мы подробно поговорим чуть позже.
Таким образом, директива ARG определяет аргументы, передаваемые в процедуру. Ее применение позволяет обращаться к аргументам по их именам, а не по смещениям относительно содержимого ВР. К примеру, если в начале рассматриваемой нами процедуры на ассемблере asmproc задать директиву ARG в виде arg kol:word,y:word, x:word,chr:byte, то к аргументам процедуры можно будет обращаться по их именам, без подсчета смещений. Ассемблер сам выполнит всю необходимую
работу. В этом можно убедиться, запустив программу в отладчике. Обратите внимание: порядок следования аргументов в директиве arg является обратным порядку их следования в описании процедуры (строка procedure asmproc(ch:char;xy koltinteger); external; в программе на Pascal). Процедура asmproc с директивой arg представлена в листинге 15.14.
Листинг15.14.Использованиедирективы arg
{prgl5_14.pas}
{Программа на Pascal, вызывающая процедуру на ассемблере, полностью совпадает с листингом 15.12}
;prgl5_14.asm
MASM —
MODELsmall
STACK256
.code
main:
asmproc proc near
;обьявление аргументов:
arg koi:WORD.у:WORD,x:WORD,chr:BYTE=a_size
PUBLIC push
asmproc bp
;сохранение указателя базы
mov
bp.sp
;настройка bp на стек через sp
mov
dh.byte ptr у
; у в dh
mov
dl,byte ptr x
; x в dl
mov
ah,02h
;номер службы BIOS
int
10h
;вызов прерывания BIOS
mov
ah,09h
;номер службы BIOS
mov
al ,chr
;символ - в 3 1
mov
bl,®7h
;маска вывода символа
хог
bh ,bh
mov
ex,koi
;kol в сх
int
10h
;вызов прерывания BIOS
pop
bp
;эпилог
ret
a_size
;будет ret 8 и выход из процедуры
asmproc
endp
;конец процедуры
end main
;конец программы
После того как решена проблема передачи аргументов в процедуру и выполнены все необходимые действия, возникает очередной вопрос: как правильно возвратить управление? При возврате управления в программу на Pascal нужно помнить, что соглашения этого языка требуют, чтобы вызываемые процедуры самостоятельно очищали за собой стек. Программа на ассемблере также должна удовлетворять этому требованию и заботиться об очистке стека перед своим завершением. Для этого необходимо составить эпилог.
Действия, выполняемые кодом эпилога для связи Pascal—ассемблер.
Записать содержимое bp в sp командой mov sp,bp. Это действие восстанавливает в sp значение, которое было на момент входа в процедуру. Необходимость в этом действии возникает в том случае, если в процедуре производилась работа со стеком. В листинге 15.13 такой работы не было, поэтому код эпилога реализует только следующие два действия.
Восстановить сохраненный в стеке регистр ВР.
Удалить из стека переданные процедуре аргументы.
Для удаления из стека аргументов можно использовать различные способы.
И Можно явно скорректировать значение SP, переместив указатель стека на необходимое количество байтов в положительную сторону. Это — не универсальный способ, к тому же он чреват ошибками, особенно при частых модификациях программы.
11 Можно использовать в директиве arg после записи последнего аргумента операнд, состоящий из символа равенства (=) и идентификатора, указанного за ним в следующей синтаксической конструкции:
=идентификатор
В этом случае TASM при обработке директивы arg подсчитает количество байТОВ, занятых всеми аргументами, и присвоит их значение идентификатору. В нашем случае директиву arg можно определить так:
arg ch:byte;х:word;у:word;koi:word=a_size
TASM после обработки данной директивы присвоит имени a_size значение 8 (байт). Это имя впоследствии нужно будет указать в качестве операнда команды ret:
ret a_size
Есть еще одна возможность организации данных Pascal—ассемблер — использовать операнды директивы MODEL. Вы помните, что она позволяет задать модель памяти и учесть соглашения языков высокого уровня о вызове процедур. Для связи Pascal—ассемблер ее можно задавать в виде MODELlarge,pascal
Задание в таком виде директивы MODEL позволяет:
綫 описать аргументы процедуры непосредственно в директиве proc: asmproc proc near ch:byte,x:word,у:word,koi:word
經 автоматически сгенерировать код пролога и эпилога в процедуре на ассемблере;
綫 для доступа к аргументам, объявленным в PROC, использовать их имена (в этом отношении данный вариант является аналогом предыдущего варианта с дирек- тивойARG).
Листинг 15.15 демонстрирует, как отражаются особенности данного варианта на тексте процедуры ассемблера. Обратите внимание на то, что пролога уже нет, так как он формируется транслятором автоматически; вместо эпилога обязательно нужно задавать команду RET, только без операндов. Интересно изучить текст листинга 15.16,который получается в результате трансляции листинга 15.15. В нем видны сформированные транслятором коды пролога и эпилога. Кроме того, транслятор заменил команду RET без операндов командой ret 0008, которая, в соответствии с требованиями к взаимодействию с программами на Pascal, удалит из стека аргументы, переданные вызываемой процедуре.
Листинг 15.15. Использование директивы MODEL
{prgl5_15.pas}
{Программана Pascal, вызывающая процедуру на ассемблере, полностью совпадает с листингом 15.12}
;P「gl5_15.a5nl
MASM 一
MODEL large, pascalпродолжение ^
12 Зак. 256
Листинг 15.15(продолжение)
STACK
256
.code
asmproc
proc near
chr ::BYTE , x :W0RD, у : WORD, koi: WORD
PUBLIC
asmproc
mov
dh,byte p
tr у
; у в dh
mov
dl,byte p
tr x
; x в dl
mov
ah,02h
;номер службы BIOS
int
10h
;вызов прерывания BIOS
mov
ah,09h
;номер службы BIOS
mov
al,chr
;символ - в al
mov
bl,07h
;маска вывода символа
xor
bh. bh
mov
ex,koi
;kol в сх
int
l@h
:вызов прерывания BIOS
ret
asmproc
endp
;конеи процедуры
end
;конец программы
Листинг 15.16. Результат трансляции листинга 15.15
Turbo
Assemblerversion
4.117/04/98
22:30:57Page 1
prgl4_82.asm
1 ;prgl4_92.asm
2 MASM
3 0000
MODEL large,pascal
4 0000
STACK 256
5 0000
.code
6 0000
7 0000
asmproc procnear chr:BYTE,x
:WORD,y:WORD,kol:WORD
8
PUBLIC asmproc
19
0000 55PUSH
IBP
1 10
0001 88 EC MOV
BP,SP
1 11
0003 8A 76 06
mov dh,byte
ptr уу в dh
12
0006 8A 56 08
mov dl, byte
ptr xx в dl
13
0009 B4 02
mov ah,02h
номер службы BIOS
14
000B CD 10
int 10h
вызов прерывания BIOS
15
000D B4 09
mov ah,09h
номер службы BIOS
16
000F 8A 46 0A
mov al, chr
символ - в al
17
0012 B3 07
mov bl,07h
маска вывода символа
18
0014 32 FF
xor bh,bh
19
0016 8B 4E 04
mov ex,kol
kol в сх
20
0019 CD 10
int 10h
вызов прерывания BIOS
1 21
001B 5D
POP BP
1 22
001c C2 0008RET
00008h
23
001f asmproc endp
конец процедуры
24
end
конец программы
Таковы стандартные способы вызова ассемблерных процедур из программ на Pascal и передачи им аргументов. Эти способы будут работать всегда, но, совершенствуясь, компилятор может предоставлять и более удобные средства. Их мы рассматривать не будем, так как, в конечном счете, они сводятся к рассмотренной нами процедуре. Остались открытыми два вопроса.
и Как быть с передачей данных остальных типов Pascal, ведь мы рассмотрели только данные размером в байт и слово?
綫 Как возвратить значение в программу на Pascal?
Что касается ответа на первый вопрос, то необходимо вспомнить, что в языке Pascal существуют два способа передачи аргументов в процедуру: по ссылке и по значению.
Тип аргументов, передаваемых по ссылке, совпадает с типом ассемблера dword и с типом pointer в Pascal. По сути, это указатель из четырех байтов на некоторый объект. Структура указателя обычная: два младших байта — смещение, два старших байта — значение сегментной составляющей адреса. С помощью такого указателя в программу на ассемблере передаются адреса следующих объектов:
缪 всех аргументов, объявленных при описании в программе на Pascal как var, независимо от их типа;
覇 аргументов pointer и longint;
i;' строк string;
緇 множеств;
舞 массивов и записей, имеющих размер более четырех байтов.
Аргументы по значению передаются следующим образом:
趣 для типов char и byte как байт;
藤 для типа boolean — как байт со значением 0 или 1;
後 для перечисляемых типов со значением 0...255 — как байт; более 255 — как два байта;
для типов integer и word — как два байта (слово);
для типа real — как шесть байтов (три слова);
массивы и записи, длина которых не превышает четырех байтов, передаются «как есть».
Заметим, что аргументы таких типов, как single, double, extended и comp, передаются через стек сопроцессора.
Что касается ответа на второй вопрос, то мы выясним его на конкретном примере. Напомню, что мы рассматриваем вызов из программы на Pascal внешней процедуры на ассемблере. Понятно, что вызов ради вызова вряд ли нужен — вызываемая процедура должна иметь возможность вернуть данные в вызывающую программу. Поэтому такую вызываемую процедуру правильнее рассматривать как функцию. В связке Pascal—ассемблер для того, чтобы возвратить результат, процедура на ассемблере должна поместить его значение в строго определенное место (табл. 15.2).
Таблица15.2.Возвратрезультата изпроцедурынаассемблере в программу наPascal
Тип возвращаемого значения
Место записи результата
Байт
AL
Слово
АХ
Двойное слово
DX:AX (старшее слово:младшее слово)
Указатель
DX:AX (сегмснтхмещение)|
В листинге 15.17 приведен текст вызывающего модуля на Pascal, а в листинге 15.18 — код вызываемого модуля на ассемблере. Программа на Pascal инициализирует две переменные, valuel и value?, после чего вызывает функцию на ассемблере AddAsm для их сложения. Результат возвращается в программу на Pascal и присваивается переменной rez.
Листинг 15,17.Вызывающая программа на Pascal
{prgl5_17.pas}
programprgl4101;
{внешние объявления}
function AddAsmiword; external;
{$L prgl5_18.obj}
var
valuelrword;{здесь как внешние}
value2:word;.
rez:word;
begi n
valuel:=2;
value2:=3;
{вызов функции}
rez:=AddAsm;
игИе1п("Результат: ", rez);
end.
Листинг 15.18. Вызываемая процедура на ассемблере
:prgl5_18.asm
MASM
MODEL small
data segment word public ;сегмент данных
;объявление внешних переменных
extrn valuel:W0RD
extrn value?:W0RD
dataends:конец сегмента данных
.code
assume ds :data;привязка ds к сегменту
;данных программы на Pascal
main:
AddAsm procnear
PUBLIC AddAsm ;внешняя
mov ex,ds:valuel;valuele ex
mov dx,ds:value2; value2B dx
add ex,dx;сложение
mov ax,ex;результат в ах, так как - слово
ret;возврат из функции
AddAsm endp;конец функции
end ша1п;конец программы
В последней программе следует обратить внимание еще на одну возможность доступа к разделяемым данным — с помощью сегментов типа PUBLIC (см. главу 5). Совместное использование сегментов данных стало возможным благодаря тому, что компилятор Pascal создает внутреннее представление программы в виде сегментов, как и положено программе, выполняющейся в архитектруре IA-32 на процессоре Intel. Сегмент данных в этом представлении тоже имеет название data, и директива SEGMENT для него со всеми вытекающими последствиями выглядит так:
data segment word public
Команды ENTER и LEAVE
Учитывая важность проблемы организации межмодульных связей, в систему команд процессора были введены специальные команды ENTER и LEAVE. Их использование позволяет облегчить написание кода пролога и эпилога в процедурах ассемблера, например:
;старый код пролога:
push bp
mov bp,sp
;новый код пролога: enter0.0
;старый код эпилога:
mov sp.bp
pop bp
;новый код эпилога:
leave
Транслятор ассемблера предоставляет средства в виде директив, которые еще- больше упрощают работу программиста по формированию кодов пролога и эпилога. Одной из них является директива ARG. Применение этой и других директив обсудим на конкретном примере. В главе 16рассматриваются довольно сложные программы, в которых, в частности, используются обсуждаемые здесь директивы. Влистинге 16.7 производится обращение кпроцедуреWindowProc. При этом ей в стеке передается ряд параметров. Кроме того, в процедуре имеются локальные переменные. Заголовок и конец процедуры выглядят следующим образом:
:Wi ndowProc
WindowProcproc.
arg@@hwnd:DWORD, @@mes:DWORD, @@wparam:DWORD, @@lparam:DWORD
uses ebx.edi,esi,еЬх;эти регистры обязательно должны сохраняться
local@@hdc:DWORD,@@hbrush:DWORD,@@hbit:DWORD
exi t_wndproc:
ret 一
WindowProc endp
В этом фрагменте новыми средствами ассемблера для нас являются директивы USES и LOCAL.
Директива USES содержит список регистров (см. рис. 10.3 в главе 10). Ее исполь-
зование заставляет транслятор генерировать код для сохранения в стеке этих ре-
гистров при входе в процедуру и их восстановления при выходе из нее.
Директива LOCAL (см. рис. 10.3) позволяет задействовать в процедуре локальные переменные. Эти переменные должны быть перечислены в списке аргументов вместе с их типами. Информацию о количестве и типах переменных транслятор использует для формирования соответствующего программного кода.
Оттранслируем исходный текст программы из листинга 16.7и откроем для просмотра файл prgl6_7.Lst. Посмотрим на результат применения этих директив:
-Wi ndowProc
0000012D WindowProc proc
arg @@hwnd:DWORD, @@mes:DWORD. @@wparam:DWORD, @@lparam:DWORD uses ebx.edi ,esi
0000012D C800000131 5300000132 5700000133 5600000134 830000012D C800000131 5300000132 5700000133 5600000134 83local @@hdc:DWORD,@@hbrush:DWORD,@@hbit:DWORD
0000012D C8
00000131 53
00000132 57
00000133 56
00000134 83
0000012D C8
00000131 53
00000132 57
00000133 56
00000134 83
000C 00 ENTERD 0000Ch,0
PUSH ebx
PUSH edi
PUSH esi
7D 0C 02 cmp @@mes,WM_DESTROY
000002CFexi t_wndproc:
000002CF 5E POP esi
000002D0 5F POP edi
000002D1 5B POP ebx
000002D2 C9 LEAVED
000002D3 C20010 RET 00010h : 000002D6 WindowProc endp
Видно, что транслятор хорошо «поработал» над кодами входа в процедуру и выхода из нее. Директива USES ebx, edi, esi заставляет транслятор генерировать «о манды PUSH и POP для сохранения-восстановления регистров EBX, EDI и ESI. А какое влияние на формирование кода входа в процедуру и выхода из нее оказывают директивы ARG и LOCAL? Чтобы разобраться с этим, выполним трансляцию трех вариантов программы из листинга 16.7 (глава 16), в каждом из которых закоммен-
тируем определенные строки:
в Закомментируем строку с директивой ARG. Фрагмент листинга будет выглядеть следующим образом:
;Wi ndowProc.
0000012DWindowProc proc
:arg@@hwnd:DWORD, @@mes:DWORD, @@wparam:DWORD, @@lparam:DWORD
uses ebx.edi,e$i ;эти регистры обязательно должны сохраняться
local@@hdc:DWORD,@@hb г u s h:DWORD,@@h b i t:DWORD
0000012D C8 000C 00 ENTERD 0000Ch,0
00000131 53 PUSH ebx
00000132 57 PUSH edi
00000133 56 PUSH esi
000002F9exi t_wndproc:000002F9exi t_wndproc:00000134 83 3D 00000000 02 cmp @@mes,WM_DESTROY
000002F9exi t_wndproc:
000002F9exi t_wndproc:
000002F9 5E000002FA 5F000002FB 5B000002FC C9000002FD C3000002F9 5E000002FA 5F000002FB 5B000002FC C9000002FD C3POP esi POP edi POP ebx LEAVED
000002F9 5E
000002FA 5F
000002FB 5B
000002FC C9
000002FD C3
000002F9 5E
000002FA 5F
000002FB 5B
000002FC C9
000002FD C3
RET 00000h
000002FEW1ndowProc endp
Закомментируем строки с директивами LOCAL и USES. Фрагмент листинга будет выглядеть так:
WindowProc
0000012DWi ndowProc proc
arg @@hwnd:DWORD, @@mes:DWORD, @@wparam:DWORD, @@lparam:DWORD
;uses ebx.edi.esi ;эти регистры обязательно должны сохраняться
;local @@hdc:DWORD,@@hbrush:DWORD,@@hbi t:DWORD
00000131 83 7D0C 02 cmp00000131 83 7D0C 02 cmp@@mes,WM_DESTROY@@mes,WM_DESTROY0000012D C8 0000 00 ENTERD 00000h,0
00000131 83 7D0C 02 cmp
00000131 83 7D0C 02 cmp
@@mes,WM_DESTROY
@@mes,WM_DESTROY
O00002E6exit_wndproc:
000002E6 C9 LEAVED
000002E7 C2 0010 RET 00010h
000002EAWi ndowProc endp
Наконец, закомментируем строки с директивами LOCAL и ARG. Фрагмент листинга:
-Wi ndowProc
0000012DWindowProcproc
0000012D
53
PUSH
ebx
0000012E
7
PUSH
edi
0000012F
56
PUSH
esi
00000130 83 3D 00000000 02♦♦Error** prgl6_3.asm(209)♦Warning* prg!623.asm(209)00000130 83 3D 00000000 02♦♦Error** prgl6_3.asm(209)♦Warning* prg!623.asm(209);arg @@hwnd:DWORD, @@mes:DWORD, @@wparam: DWORD, @@lparam: DWORD uses ebx,edi,esi ;эти регистры обязательно должны сохраняться ;local @@hdc:DWORD,@@hb「ush:DWORD,@@hbit:DWORD
00000130 83 3D 00000000 02
♦♦Error** prgl6_3.asm(209)
♦Warning* prg!623.asm(209)
00000130 83 3D 00000000 02
♦♦Error** prgl6_3.asm(209)
♦Warning* prg!623.asm(209)
cmp @@mes,WM_DESTROY
PROCBEG(4) Undefinedsymbol:@@mes
PROCBEG(4) Argument needs type override
0000030Fexit_wndproc:0000030Fexit_wndproc:0000030F 00000310 00000311 000003120000030F 00000310 00000311 000003125Е5F5BC35Е5F5BC3POP esi POP edi POP ebx
0000030Fexit_wndproc:
0000030Fexit_wndproc:
0000030F 00000310 00000311 00000312
0000030F 00000310 00000311 00000312
5Е
5F
5B
C3
5Е
5F
5B
C3
RET 00000h
00000313Wi ndowProc endp
Проанализируем эти фрагменты.
Применение директив ARG и LOCAL приводит к генерации команд ENTER и LEAVE при входе и выходе из процедуры. Поочередное комментирование этих директив показывает, что они влияют только на формирование операнда команды ENTER. При использовании директивы LOCAL он равен числу байтов, необходимых для размещения в стеке локальных переменных. Это так называемый кадр стека. Если строку с директивой LOCAL закомментировать, то команда ENTER все равно формируется, но с нулевым значением первого операнда. Это говорит о том, что пролог процедуры создается в любом случае, но директива LOCAL позволяет еще и сформировать кадр стека для хранения локальных переменных процедуры. Соответственно, во всех вариантах генерации команды ENTER в конце процедуры формируется команда LEAVE.
Теперь посмотрим на то, какое влияние оказывает директива ARG на формирование команды RET в процедуре WindowProc. Из анализа четырех приведенных ранее вариантов фрагмента листинга видно, что в некоторых из них транслятор вычисляет суммарный размер аргументов, передаваемых в процедуру, и делает это значение операндом команды RET. Это производится в тех случаях, когда директива ARG не закомментирована. Отсюда следует вывод о прямом влиянии директивы ARG на операнд команды RET. Ненулевое значение операнда команды RET приводит к изменению значения регистра SP\ESP — в нашем случае это означает очистку стека от аргументов, переданных в процедуру.
Отметим, что эти средства можно использовать не только для связи Pascal— ассемблер, но и для организации других межъязыковых связей, в том числе ассемблер—ассемблер.
С и ассемблер
Общие принципы организации связи С—ассемблер напоминают только что рассмотренное соединение Pascal и ассемблера. Поэтому мы коротко обсудим различия на примере конкретных программ. Но прежде отметим, что хотя язык C++ предоставляет дополнительные возможности связи программы с ассемблером, одновременно в нем продолжает поддерживаться традиционная организация связи. Поэтому мы рассмотрим связь с ассемблером в стиле С как стандартную. При необходимости читатель, зная основы подобной связи, без труда разберется с нюансами дополнительных возможностей связи в стиле C++.
Нас по-прежнему интересуют три вопроса: как передать аргументы в процедуру на ассемблере, как к ним обратиться в процедуре на ассемблере и как возвратить результат?
Вначале отметим, что всегда нужно сохранять (и перед выходом из процедуры восстанавливать) содержимое регистров ВР, SP, CS, DS и SS. Это делается перед вызовом процедуры. Остальные регистры нужно сохранять по необходимости, но, на
мой взгляд, хорошим тоном является сохранение и последующее восстановление всех регистров, которые подвергаются изменению внутри процедуры.
Передача аргументов в процедуру на ассемблере из программы на С осуществляется также через стек, но порядок их размещения в стеке является обратным рассмотренному ранее для связи В качестве примера используем ту же задачу. После передачи управления ближнего типа процедуре на ассемблере стек должен выглядеть так, как показано на рис. а.
Оперативная памятьОперативная память
Оперативная память 0000:0000
11』
0000:0000*
<ss:sp
ss:spip
Г
0000:0000
T
Ip
ss:sp ~ ss:bp —/*"2
+4
+6 +8 +10
ss:ffff->
iP
kol
koi
kol У X ch
У
У
х
X
ch
...i .ch .....
Дно стека
即心стека
Дно стека
Старшие адреса
Старшие адреса
Старшие адреса
оперативной памятиоперативной памятиоперативной памяти
абв
Рис.Изменение содержимого стека при передаче управления в связке
Процедуры на ассемблере получают доступ к аргументам, переданным в стеке, посредством регистра Р. Принцип доступа тот же, что и рассмотренный ранее (рис. 15.3, б). Прежде всего в начало процедуры ассемблера необходимо вставить код пролога: pushbp
mov bp,sp
После этого доступ к аргументам в стеке осуществляется по смещению относительно содержимогонапример:
переписать значение ch из стека в ах значение х - в регистр
ПриорганизациисвязиможноиспользоватьтакжедирективуARG.
Это избавит нас от необходимости подсчитывать смещения в стеке для к аргументам и позволит обращаться к ним просто по именам: arg push bp mov
movпереписать значение ch
из стека в ах
movзначение х — в регистр
Чтобы не повторяться, рассмотрим, как изменятся вызываемый и вызывающий модули (листинги 15.19 и 15.20) для связи по сравнению с листингами 15.17 и 15.18.
ЛистингВызывающий модуль на C++
//prgl5_19.cpp
#include
<conio.h>
externvoidunsigned x,
unsigned
void main (void) {
clrscr();
asmproc('a * , 2, 3, 5);
asmproc( 's', 9, 2, 7); }
ЛистингВызываемая процедура на ассемблере
;prgl5_20.asm
MASM -
MODEL
памяти и тип кода
STACK
256
PUBLIC
_asmproc
подчеркивания обязателен
.code
main:
proc
С near,y:
;у-координата символа в dh
BYTE.kol:WORD
mov
dh,У
mov
dl,x
символа в dl
mov
02h
службы BIOS
int
10h
прерывания BIOS
mov
ah,09h
службы BIOS
mov
kol
- количество "выводов"
в сх
mov
07h
;маска вывода в Ы
xor
bh
mov
al,с
- символ в al
int
10h
прерывания BIOS
ret
из процедуры
_asmproc endp end main
Что касается передачи аргументов в связкето здесь, как
все довольно прозрачно. В листинге 15.20 мы используем директиву MODEL с операндом С и директиву PROC с указанием языка С. Этим мы доверяем компилятору самому сформировать коды пролога и эпилога, а также организовать обращение к переменным в стеке по их именам. Но при использовании конкретных программных средств организация такой связи выглядит намного проблематичней. Не в последнюю очередь это связано с тем, что компиляторы языков C/C++ разрабатываются множеством фирм — в отличие от Pascal, компилятор для которого выпускает практически одна фирма Borland. Это обстоятельство, на мой взгляд, — основная причина сложности связитак как каждая фирма реализу
ет ее по-своему (хотя суть и остается практически неизменной). Поэтому, как мне кажется, нет смысла рассматривать множество частных случаев, тем более что это не является целью данной книги. Обращайтесь к документации на ваш компилятор C/C++. Во избежание излишних сложностей, по возможности применяйте ассемблерные вставки в программах на C/C++.
Как правило, компиляторы позволяют связывать модули на C/C++ и ассемблере с использованием средств командной строки. Так как этот процесс довольно хорошо стандартизован, есть смысл его рассмотреть. В качестве примера выберем компиляторфирмы Inprise (Borland). Типовая последовательность шагов
выглядит примерно так.
Составить текст программы на C++ (см. листинг 15.19). В этой программе объ- явитьпроцедуруasmprocвнешней:
extern voidunsigned x,
unsigned y, unsigned
Выполнить трансляцию модуля C++ и получить объектный модуль:
Ьсс -с
Параметр -с здесь означает, что выполняется только компиляция исходного файла, загрузочный модуль не создается. Результатом этого шага будет создание объектного модуля
Составить текст процедуры на ассемблере (см. листингв которой объ
явить процедуру asmproc общедоступной с помощью директивы PUBLIC. Заметьте, что идентификатору asmproc предшествует символ подчеркивания Компилятор C/C++ добавляет знак подчеркивания ко всем глобальным идентификаторам. Более того, некоторые компиляторы (VC++) могут кроме символа подчеркивания добавлять в конце исходного идентификатора комбинацию символовгдеозначает количество байтов, занимаемых аргументами
процедуры в стеке (см., например, листинг 16.2 в главе 16).
Выполнить трансляцию программы на ассемблере:
tasm prg!5_20,, /
Выполнить объединение объектных модулей:
bcc -ms
Исполняемому модулю будет присвоено имя prgl5_19.exe. Параметр -ms определяет модель памяти.
Компилятор Borland C++ предоставляет другую возможность для получения загрузочного модуля. Скопируйте файл tasm.exe впакета Borland C++.
Запустите исходные файлы на трансляцию командной строкой вида bcc
В результате будет получен файл prgl5_19.exe. Компилятор Borland C++ всю работу организует сам: обрабатывает файлвызывает транслятор
tasm.exe, который выполняет трансляцию файлапередает компоновщику объектные модулииВ результате создается загру
зочный модуль prgl5_19.exe.
Как возвратить результат в программу на С из процедуры на ассемблере? Для этого существуют стандартные соглашения (табл. 15.3). Перед возвратом управления в программу на С в программе на ассемблере необходимо поместить результат или сформировать указатель в заданных регистрах. Для иллюстрации работы с функцией С, текст которой написан на ассемблере, рассмотрим листинги 15.21 и 15.22. В них функция, написанная на ассемблере, подсчитывает сумму элементов массива. В функцию передаются адрес массива и его длина. Результат суммирования элементов массива возвращается обратно в вызывающую программу на С.
ТаблицаВозврат аргументов из процедуры на ассемблере в программу на C/C++
Тип возвращаемого значения (C++)
Место записи результата
Unsigned char
АХ
Char
АХ
Связь ассемблера с языками высокого уровня363
Тип возвращаемогозначения (C++)
Местозаписирезультата
Enum
AX
Unsigned short
AX
Short
AX
Unsigned int
AX
Int
AX
Unsigned long
DX:AX
Long
DX:AX
Указатель near
AX
Указатель far
DX:AX
Листинг15.21.Вызывающий модуль на C/C++
/*prgl5_21.c*/
#includ3 <stdio.h>
extern int sum_asm(1nt mass!v(],1 nt count);
main()一
intmas[5]={1.2,3(4,5};
int len=5;
int sum;
sum=sum_asni(mas, en) ;
printf("%d\n",sum) ;
return(0);
}
Листинг15.22.Вызываемая процедура на ассемблере;prgl5_22.asmMASM -MODELsmall.stack 100h.codepublic _5um_asm_sum_asm proc С near adr_mas: word, len__mas: wordЛистинг15.22.Вызываемая процедура на ассемблере;prgl5_22.asmMASM -MODELsmall.stack 100h.codepublic _5um_asm_sum_asm proc С near adr_mas: word, len__mas: wordmov mov mov cycl:addmov mov mov cycl:addax,0ex,len_mas si, adr•二mas add ax; [si]si ,2) cyclax,0ex,len_mas si, adr•二mas add ax; [si]si ,2) cycl;длину массива ;адрес массива;длину массива ;адрес массивав сх в siв сх в si;сложение аккумулятора с элементом массива;адресовать следующий элемент массива;возврат из функции, результат - в ах;сложение аккумулятора с элементом массива;адресовать следующий элемент массива;возврат из функции, результат - в ах_sum_asm endpendОбратите внимание на то, что листинг 15.19 содержит текст исходного файла с расширением .срр, а листинг 15.21 — с расширением .c. Соответственно, сами исходные тексты в части организации межмодульного взаимодействия также различаются.Дополнительную информацию о связи С—ассемблер вы найдете в [18].Несколько слов об организации связи С—ассемблер для компилятора Visual C/C++. Один из подходов к ее реализации — оформление ассемблерной программы в виде функции из библиотеки DLL. В этом случае можно уйти от «фирменных» проблем связи, возникающих из-за несовпадения форматов информации_sum_asm endpendОбратите внимание на то, что листинг 15.19 содержит текст исходного файла с расширением .срр, а листинг 15.21 — с расширением .c. Соответственно, сами исходные тексты в части организации межмодульного взаимодействия также различаются.Дополнительную информацию о связи С—ассемблер вы найдете в [18].Несколько слов об организации связи С—ассемблер для компилятора Visual C/C++. Один из подходов к ее реализации — оформление ассемблерной программы в виде функции из библиотеки DLL. В этом случае можно уйти от «фирменных» проблем связи, возникающих из-за несовпадения форматов информации
Листинг15.22.Вызываемая процедура на ассемблере
;prgl5_22.asm
MASM -
MODELsmall
.stack 100h
.code
public _5um_asm
_sum_asm proc С near adr_mas: word, len__mas: word
Листинг15.22.Вызываемая процедура на ассемблере
;prgl5_22.asm
MASM -
MODELsmall
.stack 100h
.code
public _5um_asm
_sum_asm proc С near adr_mas: word, len__mas: word
mov mov mov cycl:
add
mov mov mov cycl:
add
ax,0
ex,len_mas si, adr•二mas add ax; [si]
si ,2
) cycl
ax,0
ex,len_mas si, adr•二mas add ax; [si]
si ,2
) cycl
;длину массива ;адрес массива
;длину массива ;адрес массива
в сх в si
в сх в si
;сложение аккумулятора с элементом массива
;адресовать следующий элемент массива
;возврат из функции, результат - в ах
;сложение аккумулятора с элементом массива
;адресовать следующий элемент массива
;возврат из функции, результат - в ах
_sum_asm endp
end
Обратите внимание на то, что листинг 15.19 содержит текст исходного файла с расширением .срр, а листинг 15.21 — с расширением .c. Соответственно, сами исходные тексты в части организации межмодульного взаимодействия также различаются.
Дополнительную информацию о связи С—ассемблер вы найдете в [18].
Несколько слов об организации связи С—ассемблер для компилятора Visual C/C++. Один из подходов к ее реализации — оформление ассемблерной программы в виде функции из библиотеки DLL. В этом случае можно уйти от «фирменных» проблем связи, возникающих из-за несовпадения форматов информации
_sum_asm endp
end
Обратите внимание на то, что листинг 15.19 содержит текст исходного файла с расширением .срр, а листинг 15.21 — с расширением .c. Соответственно, сами исходные тексты в части организации межмодульного взаимодействия также различаются.
Дополнительную информацию о связи С—ассемблер вы найдете в [18].
Несколько слов об организации связи С—ассемблер для компилятора Visual C/C++. Один из подходов к ее реализации — оформление ассемблерной программы в виде функции из библиотеки DLL. В этом случае можно уйти от «фирменных» проблем связи, возникающих из-за несовпадения форматов информации
в объектных файлах Borland и Microsoft, и писать функции TASM и MASM для связи с программами Visual C/C++. Разработка библиотек DLL для ассемблерных программ описана в [8].
Таким образом, мы рассмотрели связь модулей на языках высокого уровня с модулями на ассемблере. Это обсуждение не могло быть полным из-за потенциальной глубины самой проблемы. Материал данного раздела можно рассматривать лишь как введение (хотя и достаточно подробное) в проблему межъязыковых отношений. Главная цель этого первого шага — разобраться с принципами. Следующим шагом в направлении реализации связи с ассемблером будет изучение документации на конкретный язык высокого уровня для выяснения особенностей настройки и требований конкретной среды программирования.
Итоги
Язык ассемблера содержит довольно мощные средства поддержки модульного подхода в рамках структурного программирования. В языке ассемблера эта технология поддерживается в основном с помощью механизма процедур и, частично, механизма макроподстановок.
Л Гибкость интерфейса между процедурами достигается за счет разнообразия вариантов передачи аргументов в процедуру и возвращения результатов. Для этого могут использоваться регистры, общие области памяти, стек, директивы EXTRN и PUBLIC.
и Компилятор MASM имеет мощное средство для работы с процедурами в виде директив PROTO и INVOKE. Их использование существенно облегчает вызов процедур и передачу в них параметров. Сочетание возможностей этих директив с возможностями директивы PROC позволяет организовывать корректный и более надежный вызов процедур, перекладывая заботу о формировании кодов пролога и эпилога на транслятор.
. Средства TASM поддерживают связи между языками. Ключевой момент при этом — организация обмена данными. Обмен данными между процедурами на языках высокого уровня и ассемблера производится через стек. Для доступа к аргументам используются регистр ВР или (что более удобно) директиваARG.
緻Можно доверить компилятору самому формировать коды пролога и эпилога, указав язык в директиве MODEL. Кроме того, указание языка позволяет задействовать символические имена аргументов, переданных процедуре в стеке, вместо прямого использования регистра BPдля доступа к ним. Тем самым повышаются мобильность разрабатываемых программ и устойчивость их к ошибкам.
* Для возвращения результата в программу на языке высокого уровня необходимо использовать конкретные регистры. Через них можно передать как сами данные, так и указатели.
и Довольно эффективным для организации связи С—ассемблер может быть подход, при котором ассемблерный код оформляется в виде функций библиотек DLL.
Глава
Создание
Windows-приложений на ассемблере
Особенности разработки Windows-приложений
Каркасное Windows-приложение на языке C/C++
Каркасное Windows-приложение на ассемблере
Средства TASM для разработки Windows-приложений
Расширенное программирование на ассемблере для Win32 API
I* Ресурсы Windows-приложений на языке ассемблера
Меню в Windows-приложениях
Перерисовка изображения
Использование окон диалога
Работа с графикой
Программирование для операционной системы Windows всегда было занятием не из легких, и эту тему написано немало книг. Сказать что-то новое довольно трудно, но и обойти эту тему в книге, посвященной одному из языков программирования, на современном этапе развития вычислительной техники было бы не совсем правильно. Сегодня трудно найти компьютер, на котором бы не была установлена одна из версий Windows, если, конечно, на нем не стоит что-то из «мира» UNIX. Но в этом учебнике речь будет идти исключительно о программировании для Windows на платформе Intel.
В подавляющем большинстве книг о программировании для Windows изложение, как правило, ведется на базе языковреже — на базе Pascal. А что же
ассемблер — в стороне? Конечно, нет! Мы не раз обращали ваше внимание на правильное понимание места ассемблера в архитектуре компьютера. Любая программа на языке самого высокого уровня по сути представляет собой последовательность машинных кодов. А раз так, то всегда остается теоретическая возможность написать ту же программу, но уже на языке ассемблера. Чем можно обосновать необходимость разработки Windows-приложений на языке ассемблера? Приведем следующие аргументы:
язык ассемблера позволяет программисту полностью контролировать создаваемый им программный код и оптимизировать его по своему усмотрению;
縉 компиляторы языков высокого уровня помещают в загрузочный модуль программы избыточную информацию, поэтому эквивалентные исполняемые модули, исходный текст которых написан на ассемблере, имеют в несколько раз меньший размер;
ж при программировании на ассемблере сохраняется полный доступ к аппаратным ресурсам компьютера;
8 приложение, написанное на ассемблере, как правило, быстрее загружается в оперативную память компьютера;
Ж приложение, написанное на ассемблере, обладает, как правило, более высокой скоростью работы и ответа на действия пользователя.
Разумеется, эти аргументы не следует воспринимать, как некоторую рекламную кампанию в поддержку языка ассемблера. Тем более что компиляторы языков высокого уровня постоянно совершенствуются и подчас способны создавать КОД, весьма близкий по эффективности к ассемблерному. По этой причине приведенные аргументы не являются бесспорными. И все же нельзя забывать о том, что существует бесконечное множество прикладных задач, ждущих своей очереди на компьютерную реализацию. Далеко не все из этих задач требуют тяжеловесных средств разработки — многие из них могут быть изящно исполнены на языке ассемблера, не теряя привлекательности, например, оконных Windows-приложений.
Перед началом обсуждения поясним, в чем состоит разница между программированием для DOS и для Windows. Операционные системы MS-DOS и Windows поддерживают две совершенно разные идеологии программирования. В чем разница? Программа DOS после своего запуска должна быть постоянно активной. Если ей, к примеру, требуется получить очередную порцию данных с устройства ввода-вывода, то она сама должна выполнять соответствующие запросы к операционной системе. При этом программа DOS работает по определенному алгоритму, она всегда знает, что и когда ей следует делать. В Windows все наоборот. Программа пассивна. После запуска она ждет, когда ей уделит внимание операционная система. Операционная система делает это посылкой специально оформленных групп данных, называемых сообщениями. Сообщения могут быть разного типа, они функционируют в системе довольно хаотично, и приложение не знает, какого типа сообщение придет следующим. Отсюда следует, что логика построения Windows-приложения должна обеспечивать корректную и предсказуемую работу при поступлении сообщений любого типа. Тут можно провести определенную аналогию между механизмом сообщений Windows и механизмом прерываний в архи-
тектуре IBM PC. Для нормального функционирования своей программы программист должен уметь эффективно использовать функции интерфейса прикладного программирования (Application Program Interface, API) операционной системы.
Windows поддерживает два типа приложений.
襄 Оконное приложение строится на базе специального набора функций API, составляющих графический интерфейс пользователя (Graphic User Interface, GUI). Оконное приложение представляет собой программу, которая весь вывод на экран производит в графическом виде. Первым результатом работы оконного приложения является отображение на экране специального объекта — окна. После того как окно появилось на экране, вся работа приложения направлена на то, чтобы поддерживать его в актуальном состоянии.
ж Неоконное приложение, также называемое консольным, представляет собой программу, работающую в текстовом режиме. Работа консольного приложения напоминает работу программы MS-DOS. Но это лишь внешнее впечатление. Поддержка работы консольного приложения обеспечивается специальными функциями Windows.
Вся разница между двумя типами Windows-приложений состоит в том, с каким типом информации они работают. Основной тип приложений в Windows — оконные, поэтому с них мы и начнем знакомство с процессом разработки программ для этой операционной системы.
Программирование оконных Windows-приложений
Любое оконное Windows-приложение имеет типовую структуру, основу которой составляет так называемое каркасное приложение, содержащее минимально необходимый для функционирования полноценного Windows-приложения программный код. Не случайно во всех источниках в качестве первого Windows-приложения рекомендуется изучать и исследовать работу некоторого каркасного приложения, так как именно оно отражает основные особенности взаимодействия программы с операционной системой Windows. Более того, написанное и однажды отлаженное каркасное Windows-приложение используется и в дальнейшем в качестве основы для написания любого другого значительно более сложного приложения.
Изложение материала будем иллюстрировать программами на двух языках — C/C++ и ассемблере. Такой подход значительно облегчает понимание технологии написания Windows-приложений на ассемблере. На его основе можно даже выработать некую методику, которая позволит конвертировать многие полезные программы на C/C++ в функционально эквивалентные программы на ассемблере.
Перед началом изложения отметим некоторые его характерные черты.
幾 Теоретический и практический материал главы будет отражать особенности разработки программ для 32-разрядных операционных систем Windows, к которым относятся Windows 95/98 и Windows NT/2000/XP. Хотя архитектуры этих систем в большей или меньшей степени различаются, их объединяет 32-раз- рядный программный интерфейс — Win32 API. Он представляет собой набор
функций, к которым может обращаться приложение. Основная идея Win32 API — обеспечение переносимости программ между различными программноаппаратными платформами.
緻Несмотрянаточтоизложениебудетвестись довольноподробно,мы несмо
жем описать все детали процесса построения Windows-приложения. Но в этом нет ничего страшного, так как в настоящее время доступно довольно много источников, где это сделано с необходимой степенью детализации. Неподготовленному читателю можно посоветовать подобрать другой источник, где начальный уровень программирования для Windows изложен с соответствующей степенью детализации. При этом ему совсем не нужно влезать в дебри. Вполне достаточно достичь уровня понимания логики работы каркасного Windows- приложения, и можно снова браться за данный учебник.
Для изучения материала этой главы и его практического использования в дальнейшей работе мало иметь только один пакет TASM. Кроме него также необходимы пакеты инструментальных средств разработки приложений на языке C/C++, например от Microsoft или Borland. В том и другом пакетах имеются все необходимые средства для разработки Windows-приложений. Пакет TASM, в отличие от этих пакетов, не обладает такими средствами, поэтому программисту приходится заимствовать их в том или ином виде в пакетах C/C++.
Каркасное Windows-приложение на C/C++
Обсуждение вопросов программирования для Windows на ассемблере начнем с обсуждения программы на языке C/C++. Не нужно удивляться такому подходу — «цель оправдывает средства», Нам необходимо, во-первых, понять общие принципы построения оконных Windows-приложений. Во-вторых, разобраться с тем, какие средства ассемблера при этом используются. Добиваться этих целей без предварительного обсуждения нецелесообразно. Сделаем это мягко и ненавязчиво, через рассказ о построении минимального Windows-приложения на языке C/C++. В ходе его разработки мы введем необходимую терминологию и сможем больше внимания уделить логике работы Windows-приложения, а не деталям его реализации. После этого мы с относительной легкостью разработаем эквивалентное приложение на ассемблере.
Приступая к разработке первого (и не только) Windows-приложения, важно понимать, что сам язык программирования мало влияет на его общую структуру. Это обстоятельство, кстати, и позволит нам чуть позже с относительной легкостью сменить инструментальное средство разработки Windows-приложений с C/C++ на ассемблер.
Минимальное приложение Windows состоит из трех частей:
главной функции;
幾 цикла обработки сообщений;
оконной функции.
Выполнение любого оконного Windows-приложения начинается с главной функции. Она содержит код, осуществляющий настройку (инициализацию) приложе-
ния в среде Windows. Видимым для пользователя результатом работы главной функции является появление на экране графического объекта в виде окна. Последним действием кода главной функции является создание цикла обработки сообщений. После его создания приложение становится пассивным и начинает взаимодействовать с внешним миром посредством специальным образом оформленных данных — сообщений. Обработка поступающих приложению сообщений осуществляется специальной функцией, называемой оконной. Оконная функция уникальна тем, что может быть вызвана только из операционной системы, а не из приложения, которое ее содержит (функция обратного вызова). Тело оконной функции имеет определенную структуру, о которой мы поговорим далее. Таким образом, Windows-приложение, как минимум, должно состоять из трех перечисленных элементов. В листинге 16.1 приведен вариант минимального приложения на языке C/C++.
Листинг 16.1. Каркасное Windows-приложение на языке C/C++
#include <windows.h>
LRESULT CALLBACK WindowProc(HWND , UINT , WPARAM , LPARAM);
char szClassWindow[] = "Каркасное приложение";/*Имя класса окна*/
int WINAPI WinMain(HINSTANCE hlnst, HINSTANCE hPrevInst,
LPSTR IpszCmdLine, int nCmdShow)
HWND hWnd;
MSG IpMsg:
WNDCLASSEX wcl;
/* Определение класса окна */
wcl.cbSize = sizeof (wcl);//длина структуры WNDCLASSEXA
wcl.style =//CS (Class Style) - стиль класса окна
wcl.IpfnWndProc = WindowProc; //адрес функции окна
wcl.cbClsExtra =0;//для внутреннего использования Windows
wcl.cbWndExtra = 0;//для внутреннего использования Windows
wcl.hlnstance = hlnst;//дескриптор данного приложения
wcl.hlcon =иконка
wcl.hCursor =курсор
wcl.hbrBackground =(HBRUSH)GetStocKObject (WHITE一BRUSH);//определить //заполнение
wcl.IpszMenuName = NULL;//окна белым цветом без меню
wcl.IpszClassName = szClassWindow; //имя класса окна
wcl.hIconSm=NULL ;//дескриптор значка, связываемого с классом окна
//зарегистрировать класс окна if (IRegisterClassEx (&wcl)) return 0;
hWnd=CreateWindowEx(0, //расширенный стиль окна szClassWindow,"Каркас программы для Win32 WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,NULL,NULL, hlnst,NULL);hWnd=CreateWindowEx(0, //расширенный стиль окна szClassWindow,"Каркас программы для Win32 WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,NULL,NULL, hlnst,NULL);//создать окно и присвоить дескриптор окна переменной hWnd
hWnd=CreateWindowEx(
0, //расширенный стиль окна szClassWindow,
"Каркас программы для Win32 WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL,
NULL, hlnst,
NULL);
hWnd=CreateWindowEx(
0, //расширенный стиль окна szClassWindow,
"Каркас программы для Win32 WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL,
NULL, hlnst,
NULL);
//имя класса окна
на C++",//заголовок окна
//стиль окна
//Х-координата верхнего левого угла окна
//Y-координата верхнего левого угла окна
//ширина окна
//высота окна
//дескриптор родительского окна
//дескриптор меню окна
//идентификатор приложения, создавшего окно //указатель на область данных приложения
//показать окно и перерисовать содержимое ShowWindow (hWnd, nCmdShow);
UpdateWindow (hWnd);
Листинг 16,1 (продолжение)
/* запустить цикл обработки сообщений */ while (GetMessage(&lpMsg, NULL, 0, 0))
TraqslateMessageC&lpMsg);//разрешить использование клавиатуры
DispatchMessage(&lpMsg);//вернуть управление Windows
return IpMsg.wParam;
}//конец WinMain
LRESULT CALLBACK Wi ndowProc (HWND hWnd, UINT message,
WPARAM wParam, LPARAM IParam)
//Функция WndProc вызывается операционной системой Windows
{{//и получает в качестве параметров сообщения из очереди //сообщений данного приложения
{
{
swi tch(message)
case WM_DESTROY: /* завершение программы */
PostQui~tMessage (0):
break;
default:
//Сюда попадают все сообщения,
//не обрабатываемые в данной оконной функции.
//Далее эти сообщения направляются обратно Windows
//на обработку по умолчанию
return DefWindowProc (hWnd, message, wParam, IParam);
return 0; }
Рассмотрим более подробно суть действий, выполняемых каждым из трех элементов Windows-приложения. В листинге 16.1 видно, что минимальное Windows- приложение на языке C++ состоит из двух функций: главной — WinMain и оконной — WindowProc. Цель WinMain — сообщить системе о новом для нее приложении, его свойствах и особенностях. Для достижения этой цели WinMain выполняет определенную последовательность действий.
Определяет и регистрирует класс окна приложения.
Создает и отображает окно приложения зарегистрированного класса.
Создает и запускает цикл обработки сообщений для приложения.
Завершает программу при получении оконной функцией соответствующего сообщения.
Оконная функция получает все сообщения, предназначенные данному окну, и обрабатывает их, возможно, вызывая для этого другие функции.
Видимая часть работы каркасного приложения заключается в создании нового окна на экране. Оно отвечает всем требованиям стандартного окна Windows-приложения, то есть его можно развернуть, свернуть, изменить размер, переместить в другое место экрана и т. д. Для этого, как вы увидите, нам не придется написать ни строчки кода, а нужно будет всего лишь удовлетворить определенные требования, предъявляемые к приложениям со стороны Windows.
Мы не будем больше обсуждать код листинга 16.1, так как это довольно подробно и полно сделано в других источниках. Взамен этого мы пойдем по другому пути — заглянем за «фасад» приведенного Windows-приложения и посмотрим на работу, выполняемую компилятором по формированию соответствующего испол-
няемого кода. Причем сделать это целесообразно на двух этапах: в процессе формирования объектного кода и после формирования загрузочного модуля.
В процессе формирования объектного модуля компилятор преобразует исходный текст на языке C/C++ в эквивалентный текст на языке ассемблера. В контексте нашего обсуждения это достаточно ценная информация. Для того чтобы получить такой текст, необходимо в командной строке компилятора задать специальный ключ ДА (см. make-файл в каталоге данной главы с программой ргд1б_1.срр среди файлов, прилагаемых к книге1):
cl /FA ...prgl6_l.срр
Полученный текст на ассемблере ценен тем, что в нем каждой строке исходного текста программы на C/C++ сопоставляется текст на ассемблере. В листинге 16.2 приведен фрагмент этого файла (prgl6_l・dsm), а полный его текст находится в каталоге \Lessonl6\prgl6_l\c среди файлов, прилагаемых к книге.
Листинг 16.2- Фрагмент ассемблерного представления исходного файла prg16_1.cpp
TITLEprgl6_l.cpp'
• 386Р
include Ii sting.Inc
if @Version gt 510
.model FLAT
else
_TEXT SEGMENT PARA USE32 PUBLIC 'CODE'
endif
PUBLIC ?szClassWi ndow@@3PADA ;szClassWi ndow
_DATASEGMENT
DATAENDS
PUBLIC _Wi nMai n@16
PUBLIC ?WindowProc@@YGJPAUHWND_@@IIJ@Z ;WindowProc
EXTRN _1mp_GetMessageA@16:NEAR
_TEXTSEGMENT
File prgl9_l.cpp
_WinMain@16 PROC NEAR
;Строка 5
pushebp
mov ebp, esp
sub esp, 80 ;00000050H.
pushebx
push esi
push edi
; Строка 10
; Строка 12
mov DWORD PTR_wcl$[ebp+8], OFFSET FLAT:?WindowProc@@YGJPAUHWND_@@IIJ@Z jWindowProc
; Строка 13
mov DWORD PTR_wcl$Iebp+12], 0
; Строка 16
push32512; 00007f00Hпродолжение 手
Все прилагаемые к книге файлы можно найти по адресу http://www.piter.com/download. — Примеч. ред.
Листинг 16.2 (продолжение)
push call
mov
DWORD PTR_imp_LoadIconA@8.
DWORD PTR>cl$[ebp+24], eax
Строка push call mov
18
0
DWORD PTR_imp_Get$tock0bject@4
DWORD PTRjjcir[ebp+32], eax
; Строка lea push call movzx test jne
; Строка xor jmp
; Строка $L297®5:
24
eax, DWORD PTR_wcl$[ebp]■
eax
DWORD PTR_imp_Regi sterClassExA@4
eax,ax eax, eax
$L29705
25 eax, eax
• $L29694
27
; Строка push mov push push push push push push push push push push push call mov
Строка mov
39 0 eax, DWORD PTR_hInst$[ebp] eax
0 0
-2147483648 ;80000000H
-2147483648 ;80000000H
-2147483648 ;80000000H
-2147483648 ;80000000H
13565952;60Cf0000H
OFFSET FLAT:$SG29710
OFFSET FLAT:?szClassWindow@@3PADA ;szClassWindow 0
DWORD PTR imp CreateWindowExA@48
DWORD PTRZbWnd$[ebp], eax
41
eax, DWORD PTR_nCmdShow$[ebp].
pusheax
mov push call
; Строка mov push call
; Строка 5L29712:
eax, DWORD PTR_hWnd$[ebp]
eax
DWORD PTR_imp_5howW1ndow@8 42
eax, DWORD PTR_hWnd$[ebp]
eax
DWORD PTR_imp_UpdateWindow@4 44
push push push lea push call test je
Строка lea push call
; Строка
0
0
0
eax, DWORD PTR_lpMsg$[ebp]
eax
DWORD PTR_imp_GetMessageA@16
eax, eax
$L29713
46
eax, DWORD PTR_lpMsg$[ebp]
eax
DWORD PTR_imp_TranslateMessage@4 47
lea eax, DWORD PTR_lpMsg$[ebp] pusheax
DWORD
; Строка 48
jmp 5L29712
5L29713:
; Строка 49
raov eax, DWORD
jmp
; Строка 50
$L29694:
popedi
popesi
popebx
leave
ret16
ENDP
PROC NEAR
push
ebp
mov
ebp, esp
sub
esp, 4
push
ebx
push
esi
push
edi
; Строка
56
mov
eax, DWORD
mov
DWORD PTReax
jmp
$L29719
; Строка
58
$l_29723:
; Строка
59
push
0
call
DWORD
; Строка
60
jmp
$L2972®
; Строка
61
SL29724:
Строка
64
mov
eax, DWORD
push
eax
mov
DWORD
push
eax
mov
DWORD
push
eax
mov
DWORD
push
eax
call
DWORD
jmp
$L29718 一 -
; Строка
65
jmp
SL29720
Я29719:
cmp
DWORD PTR2
$L29723
jmp
$L29724
$L29720:
; Строка
66
xor
eax, eax
jmp
$L29718
; Строка
67
$L29718:
pop
edi
pop
esi
; Строка 55
продолжение 夕
Листинг 16.2 (продолжение)
pop ebx
leave
ret 16;00000010H
?Wi ndowProc@@YGJPAUHWND_@@IIJ@Z ENDP:WindowProc
_TEXTENDS
END
Беглый взгляд на код листинга 16.2 показывает, что он оформлен как полноценная программа на ассемблере. Код программы был сгенерирован компилятором Visual C++ 6.0 от Microsoft, поэтому транслятор MASM может сделать из него загрузочный модуль без дополнительного редактирования. Для этого компания Microsoft даже подготовила специальный включаемый файл listing.inc (он находится в каталоге пакета VC++ 4.0:. .\Msdev\include, а также среди файлов, прилагаемых к книге).
Таким образом, имея исходный файл Windows-приложения на языке C/C++, можно получить текст на языке ассемблера. Теоретически, на его основе впоследствии можно сформировать функционально эквивалентный исполняемый модуль. Более того, если задаться такой целью, то не составит большого труда переделать его для обработки транслятором TASM 5.0.
Но не все так просто. Готов расстроить читателя, но мы изложили очень упрощенный подход к разработке Windows-приложения на ассемблере. Полученный код можно рассматривать лишь как схему (шаблон) такого приложения. Реально все несколько сложнее. Прежде чем мы объясним имеющиеся проблемы, проведем еще один эксперимент — дизассемблируем исполняемый модуль программы prgl6_l.cpp. Причем сделать это нужно тем дизассемблером, который «понимает» интерфейс Win32 API. В данной книге для этой цели был использован дизассемблер IDA. Дизассемблированный с его помощью файл можно сохранить как листинг (•1st) и как исходный текст ассемблера (.asm). Это говорит о том, что IDA также пытается по загрузочному модулю построить файл, пригодный для повторной трансляции. В начале дизассемблированного им текста даже приводятся рекомендации по использованию соответствующих параметров транслятора TASM.
В контексте нашего изложения наибольший интерес представляет файл листинга (•1st), формируемый IDA. Анализ его содержимого позволяет изнутри посмотреть на результаты процесса формирования исполняемого модуля транслятором и редактором связей. Файл листинга в своей левой части содержит колонку с адресными смещениями команд. Все метки и символические имена в дизассемблированном тексте формируются с учетом этих смещений, поэтому данный файл удобно использовать для анализа.
Файл листинга IDA имеет большой размер, поэтому привести весь его текст в книге невозможно, да это и не нужно. Для нас интерес представляют лишь отдельные фрагменты этого файла (листинг 16.3). Полностью Дизассемблированный код программы prgl6_l.cpp находится среди фалов, прилагаемых к книге.
Листинг 16.3. Фрагменты дизассемблированного кода каркасного Windows-приложения (дизассемблер IDA)
Этот файл сгенерирован интерактивным дизассемблером(IDA) Copyright (с) 1997 by DataRescue sprl, <ida@datarescue.com>
00401000 ;этот файл должен компилироваться следующей командной строкой: TASM /ml/m5
00401000р386
00401000model flat
00401000_
_Wi nMai n@16
proc near;CODE XREF: start+146_p
00401000
push
ebp
00401001
mov
ebp, esp
00401003
sub
esp,50h
00401006
push
ebx
00401007
push
esi
00401008
push
edi
00401037
push
0
00401039
call
ds: LoadlconA
0040103F
mov
[ebp+var .38], eax
00401042
push
7F00h
00401047
push
0
00401049
call
dsiLoadCursorA
00401072
lea
eax, [ebp+var_50]
00401075
push
eax
00401076
call
ds:RegisterClassExA
0040107C
movzx
eax, ax
0040108E
loc_40108E
:;CODE XREF:j
0040108E
push
0
00401090
mov
eax, [ebp+arg_0]
00401093
push
eax
00401094
push
0
00401096
push
0
00401098
push
80000000h
0040109D
push
80000000h
004010A2
push
80000000h
004010A7
push
80000000h
004010AC
push
0CF0000h
004010B1
push
offset unk 404034
004010B6
push
offset unk 404020
004010BB
push
0
004010BD
call
ds:CreateWindowExA
004010CE
call
ds:ShowWindow
00401004
mov
eax, [ebp+var_4]
004010D7
push
eax
004010D8
004010DE
call
ds:UpdateWi ndow
004010DE
loc_4010DE:
:; CODE XREF:j
004010DE
push
0
004010E0
push
0
004010E2
push
0
004010E4
lea
eax, [ebp+var_20]
004010E7
push
eax
004010E8
call
ds:GetMessageA
004010EE
test
eax, eax loc_40110F
004010F0
jz
004010F6
lea
eax, [ebp+var_20]
004010F9
push
eax
004010FA
call
ds:TranslateMessage
00401100
lea
eax, [ebp+var_20]
00401103
push
eax
00401104
call
ds :D-ispatchMessageA
0040110A
jmp
loc_4010DE金
продолжение
Листинг 16.3 (продолжение)
00401117
Юс_401117:
;CODE
XREF:_WinMain@16+89_j
00401117
pop
edi
00401118
pop
esi
00401119
pop
ebx
0040111A
leave
0040111В
retn
10h
0040111В,
__Wi nMai n@16 endp
;оконная
процедура
00401132
loc_401132
:;CODE
XREF: . text:00401163J
00401132
push
0
00401134
call
ds:PostQui tMessage
0040113A
jmp
loc_40116E
0040114Е
push
eax
0040114F
call
ds:DefWi ndowProcA
00401175
pop
edi
00401176
pop
esi
00401177
pop
ebx
00401178
leave
00401179
retn
10h
00401180
00401180
public start
00401180 start proc
near
004011A6
004011A6
00401205 0040120В 00401210
call ds:GetVersion ;получить текущую версию Windows
;и информацию о текущей операционной платформе
callds:GetCommandLineA
movds:dword_4050B0, eax
call_crtGetEnvi ronmentStri ngsA
004012A0
callds:GetStartupInfoA
004012BF 004012C5 004012C6 004012CB 004012CC
callds:GetModuleHandleA
pusheax
call_WinMain@16
pusheax
call_exit
0040130E 0040130F 00401310 00401312
00401313
pop esi
pop ebx
mov esp, ebp
popebp
retn
00401313 start endp;sp = -88h
00401313
0O401380_exit proc near ;CODEXREF: start+A9_p
00401380
00401380
; start+14C_p
00401389
callsub_4013C0 ;doexit
00401391
_exitendp
004013C0 ; подпрограмма doexit
004013C0 ; Атрибуты: Статическая библиотечная функция
604013C0
sub_4013C0
proc near; CODE XREF:_exit+9_p
004013D1
call
ds:GetCurrent Process
004013D7
push
eax
00401308
call
ds:TerminateProcess
00401458
push
esi
00401459
call
ds:ExitProcess
00401462
retn
00401462
sub_4013C0
endp
0040610C : Импорт из KERNEL32.dll
0040610C
0040610Cextrn GetModuleFileNameA:dword ; DATA XREF:_setargv+12_r
0040610C;_NMSG_WRITE+77_r
00406188 Импорт из USER32.dll
00406188 extrn PostQui tMessage:dword ;DATA XREF: .text:00401134_r 0040618Cextrn DispatchMessageA:dword;DATA XREF:_WinMain@16+104_7r
004061B4end start
Текст листинга 16.3 дает богатую пищудля размышлений. Его обсуждение нужно начать с последней строки. По правилам ассемблера, в последней директиве программы ENDдолжна стоять метка первой исполняемой команды. В нашем случае это метка start. Если теперь посмотреть на место в тексте, куда она ссылается, то мы увидим, что в данной строке содержится директива PROC, обозначающая начало процедуры с именем start. Можно сделать вывод, что исполнение программы начинается именно с этого места. Обратите внимание на то, что в теле процедуры start содержатся многочисленные вызовы функций.
В листинге 16.3 показаны только вызовы функций API, но если вы посмотрите полный вариант листинга 16.3, находящийся среди файлов к книге, то увидите, что в нем содержатся вызовы множества других функций. Названия этих функций начинаются символом подчеркивания (_), в отличие от названий функций API, которым предшествует префикс переопределения сегмента, например: ds:GetVersion. Задержимся немного и посмотрим внимательно на листинг 16.1,в частности, нас интересуют функции, к которым идет обращение. Видно, что в исходном тексте Windows-приложения, написанном на C/C++, нет и намека на какие- либо функции, за исключением функций API. Более того, вызов многих из функций API, которые присутствуют в дизассемблированном тексте, отсутствует в листинге 16.1. Отсюда следует естественный вывод о том, что их вставил компилятор C/C++ и, очевидно, они предназначены для инициализации среды, в которой будет функционировать программа. А так ли они необходимы? Забегая вперед, скажем, что нет. Но и отказаться от них нельзя, так как они фактически «навязываются» нам компилятором C/C++. В этом и состоит одна из главных причин того, что исполняемые модули на ассемблере по размеру в несколько раз меньше функционально эквивалентных модулей на языках высокого уровня. Можно сказать, что приложение, написанное на ассемблере, не содержит избыточного кода. Отметьте еще один характерный момент относительно функций API. Кодовый сегмент исполняемого модуля содержит только команды дальнего перехода к этим функциям. Сами же функции находятся в отдельном файле — библиотеке DLL. В конце
листинга 16.3 содержится таблица, в которой описано местонахождение всех импортируемых функций API.
Проследим за действиями, которые производит код исполняемого модуля, формируемый компилятором Visual C++ для инициализации Windows-приложения. Для этого откройте файл prg 1б_1-1st с дизассемблированным текстом программы на C/C++, находящийся среди прилагаемых к книге файлов, и переместитесь на начало процедуры start.
Вызов функции API GetVersion1 для определения текущей версии Windows и некоторой другой информации о системе. В настоящее время существует более информативная версия данной функции — GetVersionEx.
Инициализация кучи. Используется как для динамического выделения памяти функциями языков C/C++, так и для организации ввода-вывода на низком уровне.
Вызов функции API GetCommandLineAдля получения указателя на командную строку, с помощью которой было вызвано данное приложение.
Вызов функции API GetEnvironmentStringsA для получения указателя на блок с переменными окружения.
Инициализация глобальных переменных периода выполнения.
Вызов функции API Get5tartupInfoAдля заполнения структуры, поля которой определяют свойства основного окна приложения.
Вызов функции API GetModuleHandleAдля получения базового адреса, по которому загружен данный исполняемый модуль.
Вызов функции WinMain. Как можно судить по способу вызова и внутреннему названию, _WinMain@16 не является функцией API. Это локальная функция данного исполняемого модуля. Территориально она расположена в начале дизассемблированного текста (см. листинг 16.3). Позже обсудим ее подробнее.
Главный вывод приведенных рассуждений — текст процедуры start исполняемого модуля не соответствует исходному тексту программы на C/C++. В него добавлены локальные функции и функции Win32 API, которые вызываются в исполняемом модуле, например GetVersion, GetCommandLineA и т. д. Это и есть так называемый стартовый код программы. Если продолжить изучение этого стартового кода в файле prgl6_l.lst, то можно увидеть вызов функции^1пМа1п@16. Найдем теперь тело этой функции в тексте. Даже беглое сравнение фyнкции_WinMain@lб (см. листинг 16.3) и функции WinMain (см. листинг 16.1) показывает, что мы нашли место, где содержится код ассемблера, функционально эквивалентный коду на C/C++. В частности, хорошо видно, что в _WinMain@16 вызываются именно те функции (и никакие другие), что и в функции WinMain программы на C/C++.
Мы не будем сейчас обсуждать порядок и цель вызова каждой из этих функций, как это делалось для стартовой процедуры, по той причине, что следующий
Описания всех упомянутых в книге функций и структур данных Win32 API приводятся лишь частично. Полную информацию о них вы можете получить в многочисленных источниках, в которых рассматриваются вопросы разработки Windows-приложений. Предпочтительно использовать первоисточник, находящийся в Интернете по адресу: http://www.microsoft.com/msdn/.
наш шаг — это создание программы на языке ассемблера. В процессе ее разработки мы и рассмотрим эти вопросы достаточно подробно. Сейчас нам важно сделать вывод, что функция WinMain не имеет прямого отношения к функциям Win32 API. Эта функция используется лишь для того, чтобы компилятор мог сгенерировать код, выполняющий инициализацию приложения.
Кстати, если вы внимательно посмотрите на листинг 16.3, то без труда обнаружите другие фрагменты кода исполняемого файла, прямо соответствующие тексту исходного файла на C/C++. Например, в качестве упражнения найдите текст оконной функции.
В заключение обсуждения обратите внимание на код завершения программы:
004012С6call_WinMain@16
004012СВpusheax
004012ССcall_exit
Видно, что для завершения приложения вызывается процедура _exit. Код, который она содержит, является обязательным для корректного завершения любого Windows-приложения. Более подробно мы его обсудим при разработке каркасного Windows-приложения на языке ассемблера.
Каркасное Windows-приложение на ассемблере
Одним из главных критериев выбора языка разработки Windows-приложения является наличие в нем средств, способных поддержать строго определенную последовательность шагов. Язык ассемблера является универсальным языком и пригоден для реализации любых задач, поэтому можно смело предположить, что на нем можно написать также любое Windows-приложение. Материал, изложенный ранее, наглядно это доказал. Более того, стали видны некоторые подробности кода, который должно содержать Windows-приложение на ассемблере. Но мало написать сам текст Windows-приложения, необходимо знать средства пакета транслятора, специально предназначенные для разработки таких приложений, и уметь пользоваться этими средствами.
В листинге 16.4 приведен текст каркасного приложения на ассемблере, функционально эквивалентного Windows-приложению на C/C++ (см. листинг 16.1). Если не обращать внимания на особенности оформления кода, обусловленные требованиями синтаксиса ассемблера, то хорошо видно, что на уровне функций его структура аналогична рассмотренному ранее Windows-приложению на C/C++.
Листинг16.4. КаркасноеWindows-приложениенаассемблере
1>;Пример каркасного приложения для Win32
<2>.386
3> locals ;разрешает применение локальных меток в программе
4> .model flat, STDCALL;модель памяти flat
5> ;STDCALL - передача параметров в стиле С (справа налево),
б> ; вызываемая процедура чистит за собой стек
7> include wi ndowA.i nc:включаемый файл с описаниями базовых структур
;и • констант Win32
8> Объявление внешними используемых в данной программе
;функций Win32 (ASCII):
9> extrn GetModuleHandleA:PROCпродолжение身
Листинг 16.4 (продолжение)
<1G>
<11>
<12>
<13>
<14>
<1S>
<16>
<17>
<18>
<19>
<20>
<21>
<22>
<23>
<24>
<25>
<26>
<27>
<28>
<29>
<30>
<31>
<32>
<33>
<34>
extrnGetVersionExA:PROC
extrnGetCommandLineA:PROC
extrnGetEnvi ronmentStringsA:PROC
extrnGetEnvi ronmentStringsA:PROC
extrnGetStartupInfoA:PROC
extrnLoadlconA:PROCP
extrnLoadCursorA:PROC
extrnGetStockObject:PROC
extrnRegi sterClassExA:PROC
extrn CreateWi ndowExA:PROC
extrnShowWindow:PROC
extrnUpdateWindow:PROC
extrnGetMessageA:PROC
extrn TranslateMessage:PROC
extrnDispatchMessageA:PROC
extrnExitProcess:PROC
extrnPostQuitMessage:PROC
extrnDefWindowProcA:PROC
extrnPlaySoundA:PROC
extrn ReleaseDC:PROC
extrn TextOutA:PROC
extrnGetDC:PROC
extrn BeginPaint:PROC
extrnEndPaint:PROC
объявление оконной функции объектом, ;видимым за пределами данного кода
<35>
<36>
<37>
<38>
<39>
<40>
<41>
<42>
<43>
<44>
<45>
<46>
public W1 ndowProc .data hwnddd0
hlnstdd0
hdcdd0
;IpVersIonInformationOSVERSIONINFO<?>
wcl WNDCLASSEX <?> message MSG <?> ps PAINTSTRUCT <?> szClassName db1 Приложение Win32', 0
szTitleName db1 Каркасное приложение Win32 на ассемблере', О
MesWi ndowdb'Привет! Как звук в наушниках.
Не нравится? - Отключите!!!1
<47>
<48>
<49>
<50>
<51>
<52>
<53>
<54>
<55>
MesWi ndowLen= $-MesWindow.
playFIleCreate db'create.wav', 0
playFIlePaintdb'paint.wav', 0
playFileDestroy db'destroy.wav', 0
.code
startproc near
точка входа в программу: начало стартового кода вызовы расположенных ниже функций
<56>
<57>
<58>
<59>
<60>
можно при необходимости раскомментировать,
но они не являются обязательными в данной программе
вызов BOOL GetVersionEx(LPOSVERSIONINFO IpVersionlnformation) push offset IpVersionlnformation
callGetVersionExA
далее можно вставить код
<61>
для анализа информации о версии Windows
вызов LPTSTR GetCommandLine(VOID) - получить указатель
<62>
<63>
на командную строку
call GetCommandLiпеА ;b регистре еах адрес
вызов LPVOID GetEnvironmentStrings (VOID) - получить указатель
<64>
<65>
на блок с переменными окружения
call GetEnvironmentStringsA ;в регистре еах адрес
вызов VOID GetStartupInfo(LPSTARTUPINFO IpStartupInfo) ;указатель на структуру STARTUPINFO
Каркасное Windows-приложение на ассемблере
381
<66>
push
offset IpStartupInfo
<67>
call
GetStartupInfoA
<68>
вызов HMODULE GetModuleHandleA (LPCTSTR IpModuleName)
<69>
push
NULL;0->GetModuleHandle
<70>
call
GetModuleHandleA;получить значение базового адреса,
<71>
mov hlnst, eax;по которому загружен модуль.
<72>
;далее hlnst будет использоваться в качестве
;дескриптора данного приложения
<73>
;конец стартового кода
<74>
Wi nMai n:
<75>
определить
класс окна
;AT0M RegisterClassEx(CONST WNDCLASSEX *lpWndClassEx),
<76>
;где *lpWndClassEx - адрес структуры WndClassEx
<77>
;для начала
инициализируем поля структуры WndClassEx
<78>
mov
wcl.cbSi ze, type WNDCLASSEX ;размер структуры ;в wcl.cbSize
wcl.Style. CS HREDRAW+CS VREDRAW
<79>
mov
<80>
mov
wcl.IpfnWndProc, offset WindowProc ;адрес оконной
процедуры
<81>
mov
wcl.cbClsExtra, 0
<82>
mov
wcl.cbWndExtra, 0
<83>
mov
eax, hlnst
<84>
;дескриптор
приложения в поле hlnstance структуры wcl
<85>
mov
wcl .hlnstance, eax
<86>
;готовим вызов
;HICON Loadicon (HINSTANCE hlnstance, LPCTSTR IpIconName)
<87>
push
IDI_APPLICATION стандартный значок
<88>
push
0;NULL
<89>
call
LoadlconA
<90>
mov
wcl.hlcon, eax ;дескриптор значка в поле hlcon
<91>
структуры wcl
;готовим вызов
;HCURSOR LoadCursorA (HINSTANCE hlnstance, LPCTSTR IpCursorName)
<92>
push
IDC_ARROW;стандартный курсор - стрелка
<93>
push
0
<94>
call
LoadCursorA
<95>
mov wcl,
hCursor, eax;дескриптор курсора в поле hCursor
структуры wcl
<96>
;определим
цвет фона окна - белый
<97>
;готовим вызов HGDIOBJ GetStockObject(int fnObject)
<98>
push
WHITE_BRUSH
<99>
call
GetStockObject
<100>
mov
wcl.hbrBackground, eax
<101>
mov
dwordptr wcl. IpszMenuName, 0 ;без главного меню
<102>
mov
dword ptr wcl.lpszClassName, offset szClassName ;имя
;класса
окна
<103>
raov wcl
hlconSm, 0
<104>
регистрируем класс окна - готовим вызов
;RegisterClassExA (&wndclass)
<105>
push
offset wcl
<10б>
call
RegisterClassExA
<107>
test
ax, ax ;проверить на успех регистрации класса окна
<108>
jz
end_cycl__msg; неудача
<109>
создаем окно:
<110>
;готовим вызов
;HWND CreateWindowExA(DWORD dwExStyle,LPCTSTR IpClassName,
<111>
;LPCTSTR IpWi ndowName, DWORD dwStyle, int x, int y, int nWidth, ;int nHeight, HWND hWndParent, HMENU hMenu, HANDLE hlnstance,
<112>
;LPVOID IpParam).
<113>
push
0;lpParam
<114>
push
hlnst ;hlnstance
<115>
push
NULL;menu
<116>
push
NULL;parent hwnd
<117>
push
CW USEDEFAULT ;высота окна
<118>
push
CW_USEDEFAULT;ширина окнапродолжение身
Листинг 16.4 (продолжение)
59> <120> <121> <122> <123> <124> <125> <126>
<127> <128>
<129> <130> <131> <132> <133> <134>
<135> <136〉 <137> <138> <139> <140> <141> <142>
<143〉
<144> <145> <146> <147> <148> <149> <150> <151> <152> <153> <154> <155> <156> <157> <158> <159> <160> <161> <162> <163>
<164> <165> <166> <167> <168> <169> <170> <171> <172> <173> <174> <175> <17б> <177>
pushCW_USEDEFAULT;координата у левого верхнего угла окна
pushCW_USEDEFAULT;координата х левого верхнего угла
push WS~OVERLAPPEDWINDOW ;стиль окна
pushoffset szTitleName;строказаголовкаокна
push offset szClassName ; имя класса окна
pushNULL
callCreateWindowExA
movhwnd, eax;hwnd - дескриптор окна
;показать окно:
:готовим вызов BOOL ShowWi ndow( HWND hWnd, int nCmdShow )
pushSW—SHOWNORMAL
pushhwnd
callShowWindow
перерисовываем содержимое окна ;готовим вызов BOOL UpdateWindow( HWND hWnd )
pushhwnd
callUpdateWindow
:запускаем цикл сообщений:
;готовим вызов BOOL GetMessageA( LPMSG IpMsg, HWND hWnd, ;UINT wMsgFilterMin, UINT wMsgFi IterMax ) cycl_msg:.
push0
push0
pushNULL
pushoffset message
callGetMessageA
cmpax, 0
jeend_cycl_msg
:трансляция ввода с клавиатуры ;готовим вызов BOOL TranslateMessage( CONST MSG *lpMsg )
pushoffset message
callTranslateMessage
;отправим сообщение оконной процедуре
;готовим вызов LONG Di spatchMessage( CONST MSG *lpmsg)
pushoffset message
callDispatchMessageA
jmpcycl_msg
end_cycl_msg:
;выход из приложения
;готовим вызов VOID ExitProcess ( UINT uExitCode )
pushNULL
callExitProcess
startendp
;
WindowProcproc
arg @@hwnd:DWORD, @@mes:DWORD, @@wparam:DWORD, @@lparam:DWORD uses ebx, edi, esi ;эти регистры обязательно должны сохраняться local@@hdc:DWORD
cmp @@mes, WM_DESTROY
je wnidestroy
cmp @@mes, WM_CREATE
je wmcreate ~
cmp @@mes, WM_PAINT
je wmpai nt
jmp default
wmcreate:
;обозначим создание окна звуковым эффектом ;готовим вызов функции
;BOOL PlaySound(LPCSTR pszSound, HMODULE hmod, DWORD fdwSound)
<178>
<179>
<180>
pushSND_5YNC+SND_FILENAME
pushNULL
pushoffset playFileCreate
<ш>
<182>
<183>
<184>
<185>
<186>
<187>
<188>
callPlaySoundA
moveax, 0 ;возвращаемое значение - 0
jmpexi t_wndproc
wmpaint:-
pushSND_SYNC+SND_FILENAME
pushNULL
pushoffset playFilePaint
callPlaySoundA
<189>
<190>
<191>
<192>
<193>
<194>
;получим контекст устройства
;HDC BeginPaint( HWNDLPPAINTSTRUCT IpPaint )
push offset ps
push@@hwnd
callBeginPaint
mov@@hdc, eax
;выведем строку текста в окно
;BOOL TextOut( HDC hdc, int nXStart. int nYStart,
<195>
<196>
<197>
<198>
<199>
<200>
<201>
<202>
;LPCTSTR IpStrlng, int cbString )
pushMesWindowLen
pushoffset MesWindow
push100
push10
push@@hdc
callTextOutA
-освободить контекст
;BOOL EndPaint( HWND hWnd, CONST PAINTSTRUCT *lpPaint )
<203>
<204>
<205>
<206>
<207>
<208>
<209>
<210>
<211>
<212>
<213>
<214>
<215>
<216>
<217>
<218>
<219>
<220>
<221>
<222>
<223>
<224>
<225>
<226>
<227>
<228>
<229>
<230>
<231>
<232>
<233>
pushoffset ps
push@@hdc
callEndPaint
moveax, 0 ;возвращаемое значение - 0
jmpexi t_wndproc
wmdestroy:~
pushSND SYNC+SNDFILENAME
pushNULL
pushoffset playFileDestroy
callPlaySoundA
;послать сообщение WM_QUIT
;готовим вызов VOID PostQui tMessage( int nExitCode )
push0
callPostQuitMessage
moveax, 0 возвращаемое значение - 0
jmpexit_wndproc
default:-
;обработка по умолчанию ;готовим вызов LRESULT DefWindowProc( HWND hWnd, UINT Msg, ;WPARAM wParam, LPARAM IParam )
push@@lparam
push@@wparam
push@@mes
push@@hwnd
callDefWindowProcA
jmpexit_wndproc
• • • exit_wndproc:
ret
WindowProc endp end start
Каркасное Windows-приложение на ассемблере содержит один сегмент данных .data и один сегмент кода .code, Сегмент стека в исходных текстах Windows-приложений непосредственно описывать не нужно. Windows выделяет для стека объем памяти, размер которого задан программистом в файле с расширением .def. Текст листинга 16.4 довольно большой. Поэтому для обсуждения разобьем его коммен'
тариями на характерные фрагменты, каждый из которых затем поясним с необходимой степенью детализации.
Строка 3. Все символические имена в программе на ассемблере по умолчанию являются глобальными. Задание директивы LOCALS включает в трансляторе механизм контроля областей видимости имен и позволяет использовать в программе локальные имена (см. главу 10).
Строка 4. Директива .MODEL задает модель сегментации (flat) и стиль генерации (см. главу 15) кода при входе в процедуры программы и выходе из них (stdcall). Модель памяти flat обозначает плоскую модель памяти. В соответствии с этой моделью компилятор создает программу, которая содержит один 32-разрядный сегмент для данных и кода программы. Код загрузочного модуля, генерируемый с параметром flat, будет работать на процессорах i386 и выше. По этой причине директиве .MODEL должна предшествовать одна из директив: .386, .486 или .586. Указание этой модели памяти заставляет компоновщик создать исполняемый файл с расширением .ехе. В программе с плоской моделью памяти используется адресация программного кода и данных типа near. Это же касается и атрибута расстояния в директиве PROC, который также имеет тип near. Параметр stdcall определяет порядок передачи параметров через стек так, как это принято в языке C/C++, то есть справа налево. Этот параметр задает генерацию кода эпилога, который очищает стек по завершении работы процедуры (в стиле языка Pascal) от аргументов, переданных в эту процедуру при вызове. Такая передача параметров очень удобна при разработке Windows-приложений: программист может помещать параметры в стек в прямом порядке, и ему не нужно заботиться об очистке стека после окончания работы процедуры.
Строка 7. Директива INCLUDE включает в программу файл windowA.inc. Для чего нужен этот файл? Вы, наверняка, имеете некоторый опыт разработки Windows- приложений на C/C++ и знаете, что функции Win32 API в качестве передаваемых им параметров используют множество констант и данных определенной структуры. В пакете компилятора C/C++ эти данные расположены в заголовочных файлах, совокупность которых можно рассматривать как дерево с корнем в файле windows.h. Для того чтобы эти описания стали доступны приложению на C/C++, в его начало включается директива #include <windows.h> (см. листинг 16.1). Windows- приложение на ассемблере также использует вызовы функций Win32 API, поэтому в нем должны быть выполнены аналогичные действия по определению необходимых констант и структур. Но просто взять из пакета C/C++ и затем использовать в программе на ассемблере файл windows.h и связанные с ним файлы напрямую нельзя. Причина банальна: транслятор ассемблера не понимает синтаксиса C/C++. Что делать? Пакет TASM предоставляет в распоряжение программиста утилиты h2ash.exe и h2ash32.exe, которые должны помочь ему конвертировать содержимое включаемых файлов на языке C/C++ во включаемые файлы ассемблера. Однако, исходя из опыта, не стоит питать особых надежд на эффективность их работы. Поэтому проще формировать ассемблерный включаемый файл с описанием констант и структур Windows самостоятельно. Кстати, пакет TASM 5.0 предоставляет в распоряжение программиста вариант такого включаемого файла win32.inc. Его можно найти среди прилагаемых к книге файлов в каталоге к данной главе, но
пользоваться им нужно с осторожностью, так как его содержимое не совсем актуально. Например, файлсодержит описание структуры WNDCLASS, но в нем
отсутствует расширенный вариант этой структуры WNDCLASSEX, который требуется в расширенном вариантеКак решить проблему акту
альности описаний? Проще всего сделать это, используя включаемые файлы последних намомент времени версий компилятора C/C++ (сейчас это,
например, VC++ 6.0 и 7.0). Это один из основных источников подобной информации, актуальность которой к тому же гарантирована разработчиком компилятора, являющегося одновременно и создателем операционной системы Windows. Поэтому программирование на ассемблере для Windows предполагает, что вы хорошо умеете ориентироваться во включаемых файлах компилятора C/C++. Полностью пытаться конвертировать эти файлы не имеет смысла. Более правильно извлекать из них по мере необходимости описание нужныхприводить их
в соответствие требованиямассемблера и после этого записывать в некоторый свой файл, который будет играть роль файлаДля Windows-
приложений данной главы вам предлагается вариант такого включаемого файла —который включается в текст приложения директивой include
Файл windowA.inc имеется средиприлагаемых к книге. Его
можно взять за основу для дальнейшей работы и наращивать по мере необходимости.
Строки 9-33. Функции Win32 API, используемые в программе, должны быть объявлены внешними с помощью директивы EXTRN. Это необходимо для того, чтобы компилятор мог сгенерировать правильный код, так как тела функций Win32 API содержатся в библиотеках DLL системы Windows.
Имейте в виду, что в различных источниках встречаются разныека
залось бы, одной и той же функции. Необходимо правильно понимать их. Например, уже упомянутая функцияможет иметь названия RegisterClassExA
или RegisterClassExW. На самом деле две последние функции являются более современными вариантами RegisterClass. Для разрешения подобных коллизий приходится обращаться к первоисточникам — документации с описанием функций Win32 API (помните, что она также может оказаться устаревшей). Самый лучший источник такой информации — включаемые файлы компилятора C/C++. Например, описание функциисодержится в файле winuser.h. В листинге 16.5 при-
строки из этого файла.
ЛистингФрагмент файла winuser.h (VC++ 6.0)
<1>ATOM WINAPI
<2> WINUSERAPI ATOM WINAPI
<3>UNICODE
<4> #define
<S> #else
<6>
<7>//
<8> #if(WINVER >= 0x0400)
9> WINUSERAPI ATOM WINAPIWNDCLAS5EXA
10> WINUSERAPI ATOM WINAPIWNDCLASSEXW *);
11>UNICODE
12>Regi sterClassEx
продолжение
13256
Листинг 16.5 (продолжение)
13> #else
14>#define RegisterClassEx RegisterClassExA
15>tfendif// 'UNICODE
Последние версии операционных систем Windows поддерживают две системы кодировки символов: однобайтную (ANSI) и двухбайтную (UNICODE). Поддержка кодировки UNICODE была введена Microsoft, чтобы облегчить локализацию программных продуктов на неанглоязычном рынке. Операционная система Windows NT поддерживает только кодировку UNICODE. Операционная система Windows 95/98 имеет довольно плохо скрытое наследие MS-DOS и не поддерживает в своей внутренней работе кодировку UNICODE (она поддерживается лишь на уровне функций Win32 API). Для работы с обеими кодировками программный интерфейс Win32 API имеет два варианта функций. Эти функции различаются последним символом в названии. Если это А, то данная функция работает в кодировке ANSI, если W, то функция работает в кодировке UNICODE. Следующий момент, требующий пояснения, — наличие суффикса Ех в названиях функций. Объяснение этому можно найти в листинге 16.5. Директива условной компиляции (строка 8) проверяет текущую версию операционной системы. Если это Windows 95/98 или NT, то можно использовать расширенные (с суффиксами Ех) варианты функций Win32 API. Они обладают дополнительными возможностями по сравнению со старыми вариантами функций Win32 API. Исчерпывающим источником информации по функциям Win32 API (и не только) является MSDN (Microsoft Developer Network — информационная система поддержки разработчика по продуктам Microsoft), его можно найти в Интернете по адресуhttp://www. microsoft.com/ msdn/.
Строка 35. В соответствии с соглашениями операционной системы Windows, оконная функция приложения должна быть видимой за пределами приложения, в котором она описана. Это связано с тем, что оконная функция вызывается самой операционной системой Windows при поступлении сообщений для данного приложения. По этой причине оконная функция нашего каркасного приложения объявлена общей (public).
Строки 36-50 содержат описание сегмента данных, в котором определяются переменные и экземпляры структур, используемые в каркасном приложении.
Упомянем еще об одном важном техническом моменте программирования для операционной системы Windows — соглашениях об именовании различных программных объектов. Обозначениям в Windows придается большое значение. Это объясняется сложностью разрабатываемых систем, а также тем, что пользователи и разработчики должны понимать друг друга при создании как программных продуктов, так и документации к ним. Без согласованных правил по обозначению тех или иных объектов им не обойтись. Единых требований на этот счет нет. В настоящее время де-факто в качестве системы обозначений принята так называемая форма венгерской записи. Более подробные сведения о ней с указанием первоисточника приведены в файле \Lessonl6\Hungarian Notation.htm, который находится среди файлов, прилагаемых к книге. Не стоит принимать венгерскую форму записи за некую догму. Это всего лишь одна из возможных систем обозначений. При желании вы можете ввести свою собственную систему. Для того чтобы заставить
компилятор ассемблера различать строчные и прописные буквы, необходимо указать ключ /ml в командной строке.
Перед рассмотрением сегмента кода обратите внимание на его начало (строка 51). В нем отсутствуют привычные команды загрузки сегментного регистра данных. Загрузчик Windows самостоятельно загружает сегментные регистры, при этом учитывается требуемая модель памяти (директива model). В первой части главы мы упоминали, что для запуска приложения под управлением Windows необходимо выполнить нескольких шагов, в которых выполняется вызов ряда функций Win32 API. Перечислим их.
Выполнение стартового кода.
Выполнение главной функции (в C/C++ — вызов функции WinMain), которая выполняет следующие действия:
регистрирует класс окна;
создает окно;
отображает окно;
запускает цикл обработки сообщений;
завершает выполнение приложения.
Организация обработки сообщений в оконной процедуре.
А как вызывать функции Win32 API в программе на ассемблере? Вызов этих функций осуществляется аналогично вызову внешних функций (см. главу 15). Передача всех параметров осуществляется через стек. По этой причине важно знать размеры передаваемых величин. Здесь проявляется полезность венгерской системы обозначений. О размере переменной можно судить по ее названию. К тому же в Win32 большинство переменных имеет размер двойного слова (четыре байта). В соответствии с параметром stdcall директивы .MODEL параметры в стек должны помещаться справа налево, то есть первым в стек идет последний параметр функции.
Продолжим рассмотрение каркасного Windows-приложения (см. листинг 16.4).
Стартовый код
Структура нашего каркасного Windows-приложения строится в соответствии с аналогичным приложением на C/C++ (см. листинг 16.1) и его дизассемблированным вариантом (см. листинг 16.3). Разрабатывая приложение на C/C++, мы «прячемся за спину» компилятора, доверяя ему часть работы. Программируя на ассемблере, мы лишаемся этой «широкой спины» и вынуждены всю работу делать сами. Что представляет собой эта работа, видно из листинга 16.3. Первый шаг Windows-приложения заключается в исполнении стартового кода (строки 54-73). Стартовый код представляет собой последовательный вызов функций Win32 API. Вы можете экспериментировать с ними при разработке своих программ (например, для доступа к информации о параметрах командной строки или переменных окружения), но в общем случае использовать большинство из них не обязательно. Чтобы продемонстрировать это, в листинге 16.4 вызов некоторых функций стартового кода закомментирован. В этой программе из всего стартового кода мы оставили лишь вызов функции GetModuleHandteA (который, кстати, тоже можно за-
комментировать без потери работоспособности программы). Она предназначена для идентификации исполняемого файла в адресном пространстве процесса. Здесь нужно кое-что пояснить.
В литературе по платформе Win32 часто встречаются такие понятия, как процесс и поток. Процесс (приложение) представляет собой экземпляр программы, загруженной в память для выполнения. Процесс инертен, он просто владеет пространством памяти в 4 Гбайт. В этом пространстве содержатся код и данные, другие ресурсы, загружаемые в адресное пространство процесса. В качестве ресурсов могут быть, в свою очередь, исполняемые файлы или библиотеки DLL. Для того чтобы процесс исполнялся, в нем должен быть создан поток, который, собственно, и отвечает за исполнение кода, содержащегося в адресном пространстве процесса.
Зачем такие сложности? Дело в том, что Win32 по сравнению со своими предшественниками кроме процессной многозадачности поддерживает еще и потоковую многозадачность, при которой в рамках одного процесса параллельно запускаются несколько потоков. Таким образом, в рамках одной программы виртуально выполняются несколько фрагментов кода. В принципе, имея несколько процессе- ров, возможно реальное распараллеливание вычислительного процесса в рамках одного приложения.
Из-за того что в адресное пространство процесса можно загрузить несколько файлов, для работы с ними требуется некий механизм их однозначной идентификации. При загрузке исполняемого файла (или библиотеки DLL) в адресное пространство процесса ему присваивается уникальный номер. Этот номер передается операционной системой в качестве первого параметра hlnst (Handle Instance — описатель экземпляра) функции WinMain в программе на языке C/C++. Это значение используется впоследствии при вызове многих функций Win32 API, загружающих те или иные ресурсы для данной программы. Что касается значения, присваиваемого hlnst, то оно равно базовому адресу в адресном пространстве процесса, по которому загружен данный файл с расширением .ехе. Выяснить его значение можноспомощьюфункцииGetModuleHandle. Привызовеэтойфункциивкачестве ее единственного параметра передается адрес ASCIIZ-строки с именем исполняемого файла (библиотеки DLL), базовый адрес загрузки (значение hlnst) которого мы хотим получить. Если вызватьфункциюGetModuleHandle, передав ейзначение NULL, то мы получим значение hlnst для текущей программы, что, кстати, и делает стартовый код (см. листинг 16.3) в программе листинга 16.1. Важно отметить, что эта идентификация актуальна только в рамках одного процесса.
Как правило, в системе существует по крайней мере несколько процессов, владеющих своим четырехгигабайтным адресным пространством и имеющих исполняемые файлы с одинаковыми идентификаторами. Причина в том, что в Win32 адресные пространства процессов разделены, и каждый из этих процессов полагает, что он в системе один.
Фрагмент кода с вызовом GetModuleHandleA выглядит так:
.data
hi nst dd 0
.code
push0
callGetModuleHandleA
movhinst, eax
После этого кода вызывается главная функция приложения — WinMain (см. листинг 16.3).
Главная функция
Основная задача главной функции (строки 74-162) оконного Windows-приложения состоит в правильной инициализации программы и корректном ее завершении. Правильная инициализация приложения предполагает выполнение ряда предопределенных шагов. Обсудим их подробнее.
Регистрация класса окна
Регистрация класса окна выполняется в строках 75-108. Под классом окна понимается совокупность присущих ему характеристик, таких как стиль его границ, формы указателя мыши, значков, цвет фона, наличие меню, адрес оконной процедуры, обрабатывающей сообщения этого окна. Класс окна можно впоследствии использовать для создания окон приложения функцией CreateWindow. Характеристики окна описываются с помощью специальной структуры WNDCLASS (или ее расширенного варианта WNDCLASSEX в Win32).
В сегменте данных в строке 41 листинга 16.4 определен экземпляр структуры WNDCLASSEX - wcl. Первое поле структуры WNDCLASSEX cbSize должно содержать длину структуры. Команда в строке 80 загружает в это поле соответствующее значение. В поле style можно определять стиль границ окна и его поведение при перерисовке. Значение стиля является целочисленным и формируется из констант. Каждая константа означает некоторую предопределенную характеристику. Включаемый файл winuser.h компилятора VC++ содержит символические названия этих констант. Эти же константы, но уже в соответствии с требованиями синтаксиса ассемблера, описаны в файле windowsA.inc, В строке 81 листинга 16.4 комбинация констант CS_HREDRAW и CS_VREDRAW определяет необходимость полной перерисовки окна при изменении его вертикального или горизонтального размера.
В строке 80 в поле LpfnWndProcзаписывается адрес оконной функции. С помощью этойфункции все окна, созданные позднее функциейCreateWindow на основе класса, для которого выполняется регистрация, будут обрабатывать посланные им сообщения.
ПоляcbClsExtra иcbWndExtra служатдля указания количества байтов, дополнительно резервируемых в структуре класса окна WNDPROC и структуре параметров окна, которая поддерживается внутри самой системы Windows. Обычно эти поля инициализированы нулевыми значениями.
Строка 85 формирует в ноле hlnstance дескриптор приложения, полученный ранее функцией GetModuleHandleA.
В строках 86-95 в поля hlcon и hCursor загружаются дескрипторы значка и указателя мыши. После запуска приложения значок будет отображаться на панели задач Windows и в левом верхнем углу окна приложения, а указатель мыши появится в области окна. Значки и указатели мыши представляют собой ресурсы и находятся в отдельных файлах. Windows предоставляет в распоряжение программиста ряд стандартных изображений указателей мыши и значков. В файле winuser.h содержатся символические имена констант, обозначающих стандартные указатели мыши и значки. Обратите внимание на первый параметр функций LoadCursorA
и LoadlconA — hlnst Это дескриптор приложения, содержащий базовый адрес ресурса значка или указателя мыши, загруженного в процесс. Если используются их стандартные изображения, то параметр hlnst равен NULL
Команды в строках 96-99 формируют поле hbrBackground, которое должно содержать значение дескриптора кисти. Кисть представляет собой ресурс в виде шаблона пикселов, которым закрашивается некоторый объект, в данном случае — фон окна приложения некоторого класса. Для получения такого дескриптора необходимо использовать функцию GetStockObject. В качестве параметра ей передается имя нужной кисти. В файле wingdi.h содержатся символические имена констант, определяющих стандартные кисти.
В строке 101 в поле LpszMenuName записывается указатель на ASCIIZ-строку с именем меню. Если меню не используется (как в нашем случае), то в поле записывается значение NULL
Поле hlconSm в строке 103 можно рассматривать как альтернативу полю hlcon. В него помещается дескриптор значка, который будет связан с данным классом окна. Если поле hlconSm нулевое, то система будет использовать значок, определенный полем hlcon.
И последнее действие при описании класса окна — присвоение данному классу уникального имени. Это имя описано в виде ASCIIZ-строки в поле szClassName сегмента данных, и указатель на него формируется в поле LpszClassName (строка 102).
После инициализации структуры необходимо зарегистрировать класс окна в системе. Это действие выполняется в строках 105-108 с помощью функции RegisterClassExA, которой в качестве параметра передается указатель на структуру WNDCLASSEX.
Необходимо заметить, что после того как класс окна зарегистрирован, структура WNDCLASSEX больше не нужна. У вас появляется возможность сэкономить немного памяти. Это можно сделать, используя предоставляемый ассемблером тип данных объединения либо инициализируя поля структуры в стеке с последующим их выталкиванием. Здесь есть широкое поле для экспериментов. Дерзайте!
Создание окна
После того как класс окна описан и зарегистрирован в системе, приложение на его основе может создать множество различных окон. Создание окна (строки 109-126) выполняется функцией Win32 API CreateWindowEx. Для этого ей нужно передать множество параметров. Назначение их мы рассмотрим далее. В качестве результата функция возвращает уникальный дескриптор окнаhWnd, который необходимо сохранить (строки 37,125,126).
У читателя, видимо, возникает вопрос, почему для создания окна необходимы два шага: сначала определение класса окна, а лишь затем непосредственно его создание. Даже если не рассматривать этот вопрос в контексте концепции объектноориентированного программирования, двухэтапный процесс создания окна очень удобен для практической работы. В качестве наглядного примера приведем кнопки редактора MS Word. На самомделе это маленькие окна, созданные на базе одного класса. Они используют одну оконную функцию, которая обрабатывает сообщения, посланные этим окнам. Какой именно кнопке послано сообщение, оконная
функция определяет по полю hwnd в структуре сообщения (см. далее). Наличие одного класса для всех кнопок гарантирует их одинаковое поведение. В то же время, каждая из кнопок может отличаться внешним видом. Но это уже дополнительные свойства конкретного окна-кнопки, которые задаются при создании экземпляра окна параметрами функции CreateWindowEx.
Рассмотрим на примере Windows-приложения (см. листинг 16.4) задание значений параметров функции CreateWindowEx (строки 113-124).
Строка 113. Параметр IpParam используется при создании окна для передачи данных или указателя на них в оконную функцию. Делается это следующим образом. Все параметры, передаваемые функции CreateWindowEx, сохраняются в создаваемой Windows внутренней структуре CREATESTRUCT. Поля этой структуры идентичны параметрам функции CreateWindowEx. Указатель на структуру CREATESTRUCT передается оконной функции при обработке сообщения WM_CREATE. Сам указатель находится в поле IParam сообщения. Значение параметра IpParam функции Create- WindowEx находится в поле IpCreateParams структуры CREATESTRUCT.
Строка 114. Параметр hlnst — дескриптор приложения, создающего окно.
Строка 115. Параметр hMenu — дескриптор главного меню окна. Так как в данном варианте Windows-приложения меню у окна отсутствует, то параметр hMenu имеет нулевое значение.
Строка 116. Параметр hWndParent — дескриптор родительского окна. Между двумя окнами Windows-приложения можно устанавливать родовые отношения. Дочернее окно всегда должно появляться в области родительского окна. Так как у нас подобных отношений нет, то значение передаваемого параметра нулевое.
Строки 117-120. Параметры, загружаемые в стек этими строками, задают начальные координаты и размеры окна приложения на экране. Константа CW_USE- DEFAULT (80000000b), определенная в файле winuser.h, позволяет «попросить» Windows использовать значения этих параметров по умолчанию.
Строка 121. Параметр dwStyle определяет стиль окна приложения. Значение этого параметра задается константой или комбинацией констант. Символические имена этих констант описаны во включаемом файле winuser.h (Visual C++ 6.0). В нашем приложении используется значение WS_OVERLAPPEDWINDOW (OOOOOOOOh), которое задает стиль обычного перекрывающегося окна с рамкой, имеющего системное меню, кнопки свертывания, развертывания и закрытия окна в правом верхнем углу.
Строки 122 и 123. Параметры szTitleName и szClassName — их значения являются указателями на ASCIIZ-строки (строки 44-45) с именем класса окна и текстом, помещаемым в заголовок окна.
Строка 124. Параметр dwExStyle позволяет задать дополнительные стили окна. В Win32 API существует две реализации функции создания окна: стандартная CreateWindowA и расширенная CreateWindowExA. Для их вызова используются одинаковые параметры, за исключением последнего — dwExStyle. При использовании функции CreateWindowA параметр в стек помещать не требуется, при использовании CreateWindowExA параметрdwExStyle должен быть помещен в стек последним. Дополнительные стили можно задействовать вместе со стилями, задаваемыми па- раметромdwStyle.
Строка 125. Вызов функции CreateWindowExA. В качестве результата функция возвращает дескриптор окна hWnd. Он имеет уникальное значение и является одним из важнейших описателей объектов приложения. Он передается как параметр многим функциям Win32 API и как значение полей в некоторых структурах. Одновременно в приложении может быть создано и совместно существовать несколько окон, поэтому с помощью дескриптора hWnd Windows однозначно идентифицирует то окно, для работы с которым вызывается та или иная функция Win32 API. Другой важный результат работы функции CreateWindowExA — посылка асинхронного сообщения WM_CREATE в оконную функцию приложения. В нашей программе обработка этого сообщения заключается в вызове функции PlaySoundA, которая воспроизводит звуковой файл. Более подробно о сообщениях и их обработке поговорим далее.
Отображение окна
В случае успешного выполнения функции CreateWindowExA требуемое окно будет создано, но пока это произойдет лишь внутри самой системы Windows — на экране это новое окно пока еще не отобразится. Для того чтобы созданное окно появилось на экране, необходимо применить еще одну функцию Win32 API — Show- WindowA. В качестве параметров этой функции передаются дескриптор hWnd окна, которое необходимо отобразить на экране, и константа, задающая начальный вид окна на экране. В зависимости от значения последнего параметра окно отображается в стандартном виде, развернутым на весь экран или свернутым в значок. Описание символических констант, задающих начальный вид окна на экране, содержится во включаемом файле winuser.h. Строки 127-131 нашего Windows-приложения задают отображение окна в стандартном виде.
Если окно создано, то в него нужно что-то выводить — текст или графику. Здесь есть несколько тонких моментов, выяснение которых позволит нам «подняться сразу на несколько ступеней» в понимании принципов работы Windows. Поэтому задержимся немного. Тем более что в литературе этим моментам не всегда уделяется достаточно внимания — в лучшем случае десяток строк, большая часть которых уходит на обсуждение функций и их параметров, а не на разъяснение сути происходящих при этом процессов. Ход наших рассуждений будем подкреплять практическими наблюдениями за работой приложения. Для этого можно использовать утилиты, которые позволят нам наблюдать за сообщениями, циркулирующими в системе, и вызовами функций API. В качестве таких программных средств можно использовать утилиты, входящие в комплект компиляторов для языка C/C++. Например, в состав Visual C++ входит утилита Spy++ (шпион), которая позволяет наблюдать за приложениями, находящимися в данный момент в системе. Неплохие результаты для Windows-приложений дает дизассемблер W32Dasm, информацию о котором можно найти по адресу http://www.expage.com/page/ w32dasm.
Если у вас нет ничего подобного под рукой, отчаиваться не стоит. Можно провести необходимые исследования опытным путем, просто меняя логику работы приложения. Суть этой методики состоит в комбинировании последовательности выполнения функций Win32 API, воспроизводящих звуковые файлы и выводя-
щих текст на экран. При этом необходимо перемещать те или иные фрагменты программы друг относительно друга. Но это еще не все. Само воспроизведение относительно длительных звуковых файлов выполняется в определенном режиме. Обратите внимание на значение параметра fdwSound, который мы передаем функции PlaySoundA. В нашем случае это комбинация двух констант, одна из которых (SND_SYNC) означает, что функция не должна возвращать управление до тех пор, пока не закончится воспроизведение звукового файла. Если комбинировать вывод текстового сообщения и воспроизведение звукового файла, то это дает некоторую задержку, в ходе которой глаз может воспринять момент появления текстового сообщения на экране.
Используем изложенную методику для изучения проблемы вывода информации (не обязательно текста) на экран. В нашей программе мы будем экспериментировать с выводом некоторого сообщения на экран, взяв за основу строки 189205. Выполним с ними несколько манипуляций и обсудим их результаты.
Вырежем строки 189-205 из оконной процедуры WindowProc и вставим их в промежутке между функциями CreateWindowExA и ShowWindowA. Перетранслируем заново исходный текст приложения, получим новый вариант исполняемого модуля и запустим его. Поведение нашей программы будет следующим: воспроизводится звуковой файл create.wav (что означает обработку асинхронного сообщения WM_CREATE оконной функцией), появляется пустое окно (отработала функция ShowWindowA),воспроизводитсязвуковойфайлpaint.wav(этоозначает,чтовокон- ную функцию было передано сообщение WM_PAINT). На экране вы ничего не увидите, кроме пустого окна. Почему окно оказалось пустым несмотря на то, что мы в него поместили текстовое сообщение функцией TextOut? О причинах сложившейся ситуации можно привести некоторые соображения.
Работа функции ShowWindowA заключается лишь в отображении созданного функцией CreateWindowExA окна заданного класса и последующем заполнении фона этого окна цветом кисти, указанной в соответствующем поле структуры WNDCLASS.
Ж Вывод в область окна становится возможным лишь после отображения окна на экране.
И В системе Windows циркулируют два типа сообщений: синхронные и асинхронные. Их различие — в приоритетах обработки. Функция ShowWindowA помещает в очередь сообщений приложения синхронное сообщение WM_PAINT. Все синхронные сообщения попадают в очередь сообщений приложения и обрабатываются в порядке очередности. Это и послужило причиной того, что текст, выведенный в окно функцией TextOut, не был отображен в окне немедленно. В отличие от предыдущих двух доводов, данный вывод сделать довольно трудно, не имея дополнительных средств исследования, о которых мы говорили ранее. Косвенно это можно выяснить, закомментировав в программе вызов функции UpdateWindowA, основным назначением которой является посылка асинхронного сообщения WM—PAINT в оконную функцию. Асинхронные сообщения не ставятся в общую очередь приложения и сразу передаются в оконную функцию. Функция UpdateWindowA как раз и предназначена для таких ситуаций, в которых необходимо обновить содержимое окна. К этому вопросу, а так-
же к синхронным и асинхронным сообщениям мы еще вернемся, поэтому примите пока все сказанное здесь на веру.
Переместите теперь строки 189-205 (при этом именно переместите их, а не просто скопируйте) из функции WindowProc, разместив их сразу за вызовом функции ShowWindowA (перед UpdateWindowA). Перетранслируйте заново исходный текст приложения, получите новый вариант исполняемого модуля и запустите его. Поведение программы несколько изменилось. Файл create.wav воспроизведен, как обычно — после создания окна. Далее последовательно появились окно приложения и долгожданное сообщение в его области. И лишь после этого был воспроизведен звуковой файл paint.wav. Поведение приложения для этого варианта размещения фрагментов кода приводит к новым выводам.
и Функции Win32 API, выводящие что-либо в окно, должны размещаться после функций, реализующих действия по отображению окна.
後 Приложение должно самостоятельно следить за актуальностью содержимого своего окна.
後; Весь вывод на экран должен производиться в оконной функции.
Подтвердим правильность этих выводов следующими рассуждениями и экспериментами. Сверните и вновь разверните окно приложения. Вы увидите, что область окна вновь стала пустой, но при этом был воспроизведен звуковой файл create.wav. Исходя из того, что строки кода, воспроизводящие этот файл, находятся в оконной функции в том месте, где обрабатывается сообщение WM—PAINT, приходим к выводу — в очереди сообщений вновь оказалось это сообщение, и именно оно было обработано оконной функцией. Как сообщение WM.PAINT оказалось в очереди, если наше приложение на этот раз его туда не помещало, а сообщения WM_PAINT от ShowWindowA и UpdateWindowA к этому моменту уже обработаны и управление передано циклу обработки сообщений? Ответ напрашивается сам собой: это делает сама система Windows, именно она рассылает в очереди сообщений всех приложений, окна которых отображены на экране, сообщение WM_PAINT. Но при этом Windows не берет на себя обязанность хранить содержимое этих окон. За актуальность содержимого окна должно отвечать само приложение. Сообщение WM_PAINT служит для приложения сигналом обновить либо заново восстановить содержимое своего окна. Следовательно, если мы хотим, чтобы результаты предыдущих выводов на экран были актуальны, необходимо после получения сообщения WM.PAINT перерисовать содержимое окна приложения. Последнее рассуждение доказывает тезис о том, что весь вывод на экран должен производиться в оконной функции как реакция на поступление сообщения WM_PAINT.
Эти рассуждения показывают роль сообщений (и не только WM_PAINT), циркулирующих в системе. Теперь, понимая всю их важность, можно приступить к рассмотрению следующей важной части оконного Windows-приложения. При этом мы продолжим манипулировать фрагментами нашего программного кода (на этот раз звуковыми) для пояснения некоторых характерных моментов.
Цикл обработки сообщений
Сообщение в Win32 представляет собой объект особой структуры, формируемый Windows. Формирование и доставка этого объекта в нужное место в системе по-
зволяют управлять работой как самой системы Windows, так и загруженных Windows-приложений. Инициировать формирование сообщения могут несколько источников: пользователь, само приложение, система Windows, другие приложения. Именно наличие механизма сообщений позволяет Windows реализовать многозадачность, которая при работе на одном процессоре является, конечно же, псевдомультизадачностью. Windows поддерживает очередь сообщений для каждого приложения. Запуск приложения автоматически подразумевает формирование для него собственной очереди сообщений, даже если это приложение и не будет ею пользоваться. Последнее маловероятно, так как в этом случае у приложения не окажется связи с внешним миром и оно превратится в «вещь в себе».
Формат всех сообщений Windows одинаков и описывается структурой, шаблон которой содержится в файле winuser.h:
/*
* Message structure
*/
typedef struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM IParam;
DWORD time;
POINT pt;
} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;
В этой структуре все типы данных вам знакомы, за исключением одного — POINT. Этот тип данных описан во включаемом файле windef.h и представляет собой структуру вида
typedef struct tagPOINT
{ LONG x;
LONG y;} POINT, *PPOINT, NEAR *NPPOINT. FAR *LPPOINT;
На основе этого описания в файл windowsA.h помещено эквивалентное описание этой структуры в соответствии с синтаксисом ассемблера:
POINTstruc
xULONG О
у ULONG 0 ends
MSG struc
meshwnd HWi ND 0
mes UINT?
wParam UINT?
IParam UINT?
timedd 0
POINTstruc{}
ends
Поле meshwnd структуры MSG содержит значение дескриптора окна, которому предназначено сообщение. Этотот самый дескриптор, который возвращается функцией CreateWindowExA и, соответственно, однозначно идентифицирует окно в системе. Не забывайте, что приложение обычно имеет несколько окон, поэтому значение в поле meshwnd помогает приложению идентифицировать нужное окно.
В поле mes Windows помещает 32-разрядную константу — идентификатор сообщения, однозначно идентифицирующий тип сообщения. Для удобства все эти константы имеют символические имена, начинающиеся с префикса WM (Window Message). Посмотрите, каким образом они определены в файле winuser.h. Во включа-
емом файле для программ на ассемблере win32.inc (и нашем варианте этого файла — windowsA.h) тоже содержатся некоторые из этих констант. Если в ходе работы вам понадобятся отсутствующие константы, возьмите их из включаемых файлов компилятора C/C++, исправив описание в соответствии с синтаксисом ассемблера. В программе на языке C/C++ эти константы используются в оконной функции оператором switch для принятия решения о том, какая из его ветвей будет исполняться. В оконной функции каркасного Windows-приложения на ассемблере (строки 163-232) этот оператор моделируется командами условного и безусловного переходов (строки 168-174), а также командой cmp, в качестве второго операнда которой и выступает константа, обозначающая определенный тип сообщения.
Поля IParam и wParam предназначены для того, чтобы система Windows могла разместить в них дополнительную информацию о сообщении, необходимую для его правильной обработки. Эти поля, например, используются при обработке сообщений о выборе пунктов меню или о нажатии клавиш.
В поле time Windows записывает информацию о времени, когда сообщение было помещено в очередь сообщений.
И наконец, поле POINT содержит координаты указателя мыши в момент помещения сообщения в очередь.
Итак, представим, что в системе произошло какое-то событие, например, некоторому приложению необходимо перерисовать свое окно, в результате Чего система Windows сформировала сообщение WM_PAINT. Данное сообщение попадает в очередь сообщений приложения, создавшего окно. Для того чтобы приложение могло обработать это (или любое другое) сообщение, ему необходимо сначала его обнаружить в очереди сообщений. С этой целью после отображения окна на экране программа «входит» в специальный цикл, называемый циклом обработки сообщений (строки 139-156). Выйти из этого цикла можно только по приходу сообщения WM_QUIT. В этом случае функция GetMessageA возвращает нулевое значение, и команда условного перехода в строке 146 передает управление на конец цикла обработки сообщений. В случае прихода других сообщений функция GetMessageA возвращает ненулевое значение, в результате чего и осуществляется вход непосредственно в тело цикла обработки сообщения. Функции GetMessageA передается несколько параметров (строки 140-143):
维 message — указатель на экземпляр структуры MSG (строка 42). Во время работы функцияGetMessageA извлекает сообщениеиз очереди сообщенийприложения и на основе информации в нем инициализирует поля экземпляра структуры message. Таким образом, приложение получает полный доступ ко всей информации в сообщении, сформированном Windows;
№ hWnd — в поле передается дескриптор окна, сообщения для которого должны будут выбираться функцией GetMessageA. Параметр позволяет создать своеобразный фильтр, заставляющий функциюGetMessageA выбирать из очереди и передавать в цикл обработки сообщений сообщения лишь для определенного окна данного приложения. Если hWnd = NULL, то GetMessageA будет выбирать из очереди сообщения для всех окон;
戀 wMsgFilterMin и wMsgFilterMax — значения данных параметров также позволяют создавать фильтр для выбираемых функцией GetMessageA сообщений. Они задают диапазон номеров сообщений (поле mes структуры MSG), которые будут выбираться из очереди функцией GetMessageA и передаваться в цикл обработки сообщений. Параметр wMsgFilterMin задает минимальное значение параметра mes, а параметр wMsgFilterMax, соответственно, максимальное значение.
Функция GetMessageA выполняет следующие действия.
Постоянно просматривает очередь сообщений.
Выбирает сообщения, удовлетворяющие заданным в функции параметрам.
Заносит информацию о сообщении в экземпляр структуры MSG (строка 42).
Передает управление в цикл обработки сообщений.
Цикл обработки сообщений (строки 147-155) состоит всего из двух функций: TranslateMessage и DispatchMessageA. Эти функции имеют единственный параметр — указатель на экземпляр структуры MSG, предварительно заполненный информацией о сообщении функцией GetMessageA.
Функция TranslateMessage предназначена для обнаружения сообщений от клавиатуры для данного приложения. Если приложение самостоятельно не обрабатывает ввод с клавиатуры, то эти сообщения передаются для обработки обратно Windows.
Функция DispatchMessageA предназначена для передачи сообщения оконной функции. Такая передачапроизводитсяненапрямую, таккак самаDispatchMessageA ничего не знает о месторасположении оконной функции, а косвенно — посредством системы Windows. Это делается следующим образом:
Функция DispatchMessageA возвращает сообщение операционной системе.
Windows, используя описание класса окна, передает сообщение нужной оконной функции приложения.
После обработки сообщения оконной функцией управление возвращается операционной системе.
Windows передает управление функции DispatchMessageA.
DispatchMessageAзавершает свое выполнение.
Так как вызов функции DispatchMessageA является последним в цикле, то управление опять передается функции GetMessageA, которая выбирает очередное сообщение из очереди сообщений и, если оно удовлетворяет параметрам, заданным при вызове функции, выполняет тело цикла. Цикл обработки сообщений выполняется до тех пор, пока не приходит сообщение WM_QUIT. Получение этого сообщения — единственное условие, при котором программа может выйти из цикла обработки сообщений.
Завершение выполнения приложения
Выход из цикла обработки сообщений означает одно — необходимо завершить программу. В программе на C/C++ для этого непосредственно за циклом обработки сообщений помещается оператор return (см. листинг 16.1): return IpMsg.wParam;
В качестве операнда в операторе return используется значение поля wParam экземпляра структуры MSG — IpMsg. Значение этого поля формируется значением соответствующего поля последнего сообщения, выбранного функцией GetMessageA из очереди. Нетрудно догадаться, что этим сообщением было WM_QUIT.
Листинг 16.3 позволяет посмотреть, каким образом процесс завершения Windows-приложения реализован компилятором языка C/C++:
004012С6call _WinMain@16
004012CBpush еах
004012ССcall _exit
00401380_exit proc near; CODE XREF: start+A9_p
00401389call sub_4013C0;_doexit
O0401391_exit endp
O04013C0 ;__doexi t
004013D1call ds:GetCurrentProcess
004013D7pusheax
004013D8call ds:TerminateProcess
00401458 pushesi
00401459 call ds:ExitProcess
00401462retn
Из полного варианта листинга 16.3 видно, что процедура WinMain возвращает в регистре еах значение wParam сообщения WM_QUIT. Затем вызывается локальная процедура _exit, предназначенная для выполнения определенных действий по завершению приложения. Процедура_exit, в свою очередь, вызывает другую локальную процедуру — _doexit. Ее текст представляет наибольший интерес для нас, так как в нем мы видим те функции Win32 API, которые непосредственно выполняют работу по удалению приложения из системы Windows, — это три функции: Get- CurrentProcess, TerminateProcess и ExitProcess. Для завершения работы приложения достаточно использовать только функцию ExitProcess, что и сделано в разработанной нами программе (строки 158-161 листинга 16.4).
На этом рассмотрение работы главной функции стандартного Windows-приложения можно считать оконченным. Видимо, вы обратили внимание на то, что до сих пор вся работа шла в интересах Windows: инициализировались определенные структуры данных, вызывались строго определенные функции и т. д. А где же полезная работа приложения? Выполнением этой работы занимается оконная функция. Если быть более точным, то она выступает координатором этой работы. Далее разберемся с тем, как реализовать оконную функцию Windows-приложения на языке ассемблера.
Обработка сообщений в оконной функции
Оконная функция призвана организовать адекватную реакцию со стороны Windows-приложения на действия пользователя и поддерживать в актуальном состоянии то окно приложения, сообщения которого она обрабатывает. Приложение может иметь несколько оконных функций. Их количество определяется количеством классов окон, зарегистрированных в системе функцией RegisterCLass(Ex). Если
вы помните, функции RegisterClass(Ex) посредством экземпляра структуры WNDCLASS передается указатель на определенную оконную функцию Windows-приложения. Данная функция до конца работы приложения связана с экземплярами окон, которые, в свою очередь, создаются другой функцией API — CreateWindowEX.
Когда для окна Windows-приложения появляется сообщение, операционная система Windows производит вызов соответствующей оконной функции. Ранее мы для упрощения говорили, что единственный источник появления сообщений — очередь сообщений приложения, но это не совсем так. Дело в том, что сообщения, в зависимости от источника их появления в оконной функции, могут быть двух типов: синхронные и асинхронные. К синхронным сообщениям относятся те сообщения, которые помещаются в очередь сообщений приложения и терпеливо ждут момента, когда они будут выбраны функцией GetMessage. После этого поступившие сообщения попадают в оконную функцию, где и производится их обработка. Асинхронные сообщения подобно пожарной машине попадают в оконную функцию в экстренном порядке, минуя все очереди. Асинхронные сообщения, в частности, инициируются некоторыми функциями Win32 API, такими как CreateWin- dow(Ex) или UpdateWIndow. Координацию синхронных и асинхронных сообщений осуществляет Windows. Если рассматривать синхронное сообщение, то его извлечение производится функцией GetMessage с последующей передачей обратно в Windows функцией DispatchMessage. Асинхронное сообщение, независимо от источника, который инициирует его появление, сначала попадает в Windows и затем — в нужную оконную функцию.
Для чего реализуется именно такая схема, неужели функции DispatchMessage нельзя сразу передать сообщение в оконную функцию? Если бы это было так, в системе появилось бы единственное приложение-монополист, которое захватило бы все процессорное время своим циклом обработки сообщений. В схеме, реализованной в Windows, обработка сообщений приложением проводится в два этапа: на первом этапе приложение выбирает сообщение из очереди и отправляет его обратно во внутренние структуры Windows; на втором этапе Windows вызывает нужную оконную функцию приложения, передавая ей параметры сообщения. Преимущество этой схемы в том, что Windows самостоятельно решает все вопросы организации эффективной работы приложений.
Таким образом, при поступлении сообщения Windows вызывает оконную функцию и передает ей ряд параметров. Все они берутся из соответствующих полей сообщения. В нотации языка C/C++ заголовок оконной функции описан следующим образом (см. листинг 16.1):
LRESULT CALLBACK WindowProc (HWND hWnd, UINT message, WPARAM wParam,
LPARAH IParam)
Здесь hWnd — дескриптор окна, которому предназначено сообщение; message — идентификатор сообщения, характеризующий тип сообщения; wParam и IParam — дополнительные параметры, являющиеся копиями соответствующих полей структуры поступившего сообщения.
Оставшиеся два поля структуры MSG: time и POINT — используются довольно редко, и при необходимости их значения можно извлечь непосредственно из экземпляра структуры сообщения.
В нашем примере Windows-приложения строки 164-167 обозначают начало оконной функции. Параметры этой функции Windows помещает в стек. Чтобы можно было работать с ними, используя символические имена, после заголовка функции поместим директиву ARG (см. главу 15).
Windows требует, чтобы оконная функция сохраняла значения регистров EBI, EDI и ESI. Причина — оконная функциядолжна быть рекурсивной. Например, воз- можпа ситуация, когда несколько классов окон применяют одну и ту же оконную функцию для обработки сообщений, поступающих в созданные на базе этих классов окна. Имеет смысл сохранять не только названные регистры, используемые самой системой Windows, но и другие (за исключением ЕАХ), если они задействованы в оконной функции. Исходя из требования рекурсивности, все переменные, используемые в оконной функции, должны быть локальными. Чтобы удовлетворить требование сохранения регистров, лучше применить (строка 166 листинга 16.4) соответствующее средство транслятора ассемблера — директиву USES (см. также главы 10 и 15). При этом транслятор вставляет в начало и конец оконной функции соответствующую последовательность команд ассемблера PUSH. Вы можете убедиться в этом, посмотрев файл листинга (файл .1st) программы из листинга 16.4 после ее трансляции.
Центральным местом оконной функции является синтаксическая конструкция, в задачу которой входит распознавание поступившего сообщения по его типу (параметр message) и передача управления на ту ветвь кода оконной функции, которая продолжает далее работу с параметрами сообщения. В языке C/C++ (см. листинг 16.1) для этого используется оператор switch (переключатель). В языке ассемблера такого средства нет, поэтому его приходиться моделировать (см. главу 11). В листинге 16.4 строки 168-174 показывают, как это можно сделать с помощью команды СМР, атакже команд условного(JE)и безусловного(JMP)переходов.
Соответствие символических имен константам, обозначающим тип сообщений Windows, приведено во включаемом файле windowsA.h. Совершенно необязательно анализировать типы всех возможных сообщений, параметры которых передаются в оконную функцию. Структуру, подобную строкам 168-174,и вообще структуру всей оконной функции можно сравнить с ситом, а сообщения — с зернами разной величины. Отверстия в сите непростые — каждое из них пропускает зерно своего размера. Зерна (сообщения) в сите (оконной функции) не задерживаются, попав в него, они через одно из отверстий обязательно уходят. Уходят куда? Это опять-таки определяется особой структурой нашего сита. Представьте, что к каждому отверстию припаяна трубка, которая определяет, куда данное зернышко попадет для дальнейшей обработки. Таким образом, каждому сообщению, попадающему в оконную функцию, соответствует ветвь в коде процедуры, которая начинает обработку этого сообщения. В ходе этой обработки, возможно, понадобится вызов других функций или применение синтаксических конструкций, подобных описанному ситу, для дальнейшей более тонкой селекции сообщений, но уже одного типа (по полям IParam или wParam). Для наглядности аналогию структуры оконной функции с ситом можно упрощенно представить в виде схемы (рис. 16.1).
Представленная схема показывает, что оконная функция, обрабатывающая сообщения, имеет одну точку входа и множество точек выхода. Выход осуществляется из той ветви оконной функции, где обрабатывалось сообщение. Сообщения,
Рис. 16.1.Абстрактное представление структуры оконной функции Windows
для которых не предусмотрена отдельная обработка, должны обрабатываться функцией DefWindowProc (строки 219-227). Эта процедура по отношению к переданным ей сообщениям предпринимает действия по умолчанию. В принципе, ей можно передавать на обработку все сообщения, что в контексте рассмотренной ранее абстракции означает сито, содержащее одно отверстие, в которое проваливаются все поступающие сообщения.
По завершении работы оконная функция формирует значение в регистре ЕАХ. Если сообщение обрабатывалось в оконной функции, то в ЕАХ необходимо поместить нулевое значение. Если обработка осуществлялась по умолчанию, то есть функцией DefWindowProc, то в ЕАХ уже сформировано возвращаемое значение, и именно его нужно возвратить в качестве результата работы оконной функции.
В нашем примере оконная функция обрабатывает три сообщения: создание окна WM_CREATE, перерисовку области окна WM_PAINT, закрытие окна WM_DESTROY.
Более подробно с условиями возникновения и обработкой этих и других сообщений можно познакомиться в литературе по Windows, где эти вопросы освещены более полно.
Средства TASM для разработки Windows-приложений
Ранее мы подробно разобрались с тем, что собой представляет простое Windows- приложение, написанное на языке ассемблера. Излагая материал, мы упоминали имена файлов, которые нужны для получения работоспособного исполняемого модуля программы. Для устранения возможных неясностей соберем и систематизируем эту информацию.
При разработке Windows-приложений на языке ассемблера с помощью Win32 API вам нужен один из пакетов ассемблера — не обойтись без пакета TASM версии 5.0. Современные 32-разрядные операционные системы Windows используют формат РЕ исполняемого файла. В состав пакета TASM 5.0 входят два компилятора ассемблера — 16- и 32-разрядный. Они имеют имена исполняемых файлов, соответственно, tasm.exe и tasm32.exe. То же касается и редакторов связей — tlink.exe и tlink32.exe. Получить файл формата РЕ можно только при совместном использовании файлов tasm32.exe и tlink32.exe. Для удобства работы, подобно тому, как мы это делали для 16-разрядных программ в главе 6, скопируйте все файлы 32-раз- рядной версии в отдельный каталог и назовите его, например, Work32. Сюда же необходимо поместить файлы windowsA.inc и import32.lib, находящиеся среди файлов, прилагаемых к книге.
Для создания программы нужны еще два файла: файл определений компоновщика и файл описания ресурсов. Файл описания ресурсов будет рассмотрен в следующем разделе. Что касается файла определений компоновщика, то его содержимое довольно подробно описывается в различных источниках, и его роль ничем не отличается от роли аналогичного файла при разработке Windows-приложений на других языках. Назначение файла определений компоновщика состоит в том, чтобы предоставить редактору связей информацию о способе загрузки программы. Несмотря на то что в архитектуре Win32 нет особого смысла использовать данный файл, редактор tlink32.exe требует указания этого файла среди файлов, подаваемых ему в качестве входных. Вы можете без проблем задействовать готовый вариант этого файла, находящийся среди файлов, прилагаемых к книге.
Перечислим необходимые для разработки Windows-приложения файлы.
Файл с исходным текстом программы (.asm). Формируется программистом.
Включаемый файл с описаниями структур данных и констант Win32 (.inc или .ash). Файл формируется программистом по мере расширения используемых им средств Win32. Источником информации для этого файла служат включаемые файлы (.h) из пакета компилятора C/C++, например VC++ версии 4.0 и выше.
я Файл с библиотекой импорта import32.lib. Этот файл требуется компоновщику для разрешения внешних ссылок на функции Win32 API. Вы можете создать этот файл сами. Такая необходимость может возникнуть, если вам понадобятся функции из библиотек DLL, информация о которых отсутствует в существующем варианте файла import32.Lib. Для этого существует специальная утилита implib.exe, поставляемая в пакете TASM 5.O. Командная строка для ее запуска имеет вид
implib имя_файла_11 bсписок_с!11_библ иотек
Получить информацию о местонахождении конкретной функции Win32 API довольно просто. Многие справочные руководства по Windows при описании конкретной функции приводят и информацию о библиотеке DLL, где эта функция содержится.
後 Файл с описанием ресурсов, используемых в приложении.
Другие файлы. Например, в рассмотренной нами программе каркасного приложения (см. листинг 16.4) используются звуковые файлы (.wav).
т Файлы tasm32, tlink32.exe и, возможно, некоторые другие вспомогательные файлы из пакета TASM 5.O. Следитеза сообщениями. В том случае,
если какого-либо файла будет недоставать, его нужно просто найти в каталоге пакета TASM 5.0 и скопировать в свой рабочий каталог. Непосредственно в каталогеработать не рекомендуется, иначе он моментально превратится
в слабоструктурируемое нагромождение файлов.
II Компилятор ресурсов brc32.exe или brcc32.exe. Компиляторы взяты из пакета C/C++ фирмы Borland.если вы работаете с пакетом VC++, то вам может
понадобиться компилятор ресурсов, входящий в этот пакет. Он называется rc.exe.
т Файл makefile и утилита make.exe. Эти файлы призваны облегчить процесс сборки приложения в единый исполняемый модуль.
Приведенный список файлов, необходимых для сборки Windows-приложения, довольно велик. Ранее процесс получения исполняемого файла у нас был простым и вполне управлялся из командной строки (безнапример, make-
файлов). Более сложные приложения требуют учета взаимосвязей между несколькими файлами. Данные файлы, в свою очередь, создаются или обрабатываются разными программными средствами, которые иногда требуют задания режимов работыпараметрами. Запоминать их и постоянно вводить вруч
ную тяжело, и такая работа вряд ли может быть признана эффективной. Для облегчения процесса получения исполняемого файла используйте возможности, предоставляемые make-файлами.
Make-файл для программиста — существенное облегчение в его работе. Тщательно разработав один раз make-файл для создания исполняемого файла своего приложения, вы впоследствии избавите себя от рутинной работы по формированию необходимых для этого командных строк. Второй положительный эффект от использования make-файлов — упрощается работа автора по описанию процесса получения исполняемого модуля. Кстати, в роли автора можете оказаться и вы, когда разработаете свою программу и вместо длинного описания процесса ее сборки предоставитедополнив его необходимыми коммен
тариями. Информацию о правилах написания make-файлов можно найти среди прилагаемых к книге файлов в каталоге к главе 6. Make-файл для сборки приложения (см. листингпредставлен в листинге 16.6.
Листинг 16.6. Пример make-файла для создания приложения
<1> NAME =
<2> OBJ5 =
<3> DEF =
<4>
<5>
<6>
7>$(OBJS) $(DEF)
8> tnnk32 /Tpe /aa /c $(LINKDEBUG) $(OBJS), $(NAME) , , $(IMPORT) , $(DEF) <9>
10>, ,
Поясним наиболее значимые элементы приведенного файла. Мы уже упоминали, что трансляция исходного файла производится программой tasm32.exe. При
вызове ей передается ряд ключей (строки 1-6). Для формирования отладочной информации вы можете указывать значения макрооператоровTASMDEBUG=/zi(стро- ка 4) и LINKDEBUG = /v (строка 5). Кроме этих необязательных ключей в строке 10 присутствует обязательный ключ /ml, который требует, чтобы транслятор ассемблера различал строчные и прописные буквы в идентификаторах программы. Как вы смогли убедиться, это очень удобно при написании программ для Windows. Строка 8 содержит вызов компоновщика tlink32.exe. Описания других ключей для этой программыприведенывглаве6иприложенииВ(http://www.piter.com/download).
При запуске программы make.exe не забывайте о нехитром, но полезном приеме с перенаправлением вывода экранных сообщений в файл с помощью символа «>»: MAKE.EXE -DDEBUG > р
В файле р текущего каталога читайте диагностические сообщения о процессе построения приложения.
Углубленное программирование на ассемблере для Win32
Реализация описанного ранее процесса разработки простого Windows-приложения на языке ассемблера может отнять довольно много сил и времени у неподготовленного человека. Hot скорее всего, это не будет пустой тратой драгоценных для любого программиста жизненных сил. Цель материала, изложенного именно в таком виде, — демонстрация того, что разработка Windows-приложения на языке ассемблера — не такое уж нереальное дело. Напротив, у него даже есть свои достоинства. Нужно отметить, что объем учебного материала, необходимого для описания процесса разработки каркасного приложения для Windows, не зависит от языка, на котором предполагается вести программирование, так как основное внимание уделяется не столько средствам языка, сколько описанию требований к функционированию приложения со стороны Windows. Каркасное приложение является простейшей программой для Windows, которая в лучшем случае выводит строку текста в окно приложения. С точки зрения сложности, не имеет смысла даже проводить ее сравнение с аналогичной программой для MS-DOS, выводящей строку на экран. Логику работы и способы реализации такой программы для MS-DOS в худшем случае можно объяснить минут за десять. Для объяснения логики работы каркасного Windows-приложения неподготовленному слушателю придётся прочитать целую лекцию, может быть, и не одну. И это простейшая программа. А где же предел? Какими минимальными знаниями и умениями должен обладать программист, чтобы утверждать, что он является, если, конечно, можно так выразиться, профессиональным Windows-программистом. Не претендуя на безусловную истину, попытаемся перечислить некоторые проблемы, которые программист должен научиться решать в первую очередь.
緻 Нужно понять общие принципы построения программы, работа которой управляется сообщениями.
II Нужно научиться выводить текст и графику в область окна приложения. Основная проблема здесь состоит в умении эффективно использовать совокуп-
ность средств Win32 API. Сам процесс формирования изображения в окне Windows напоминает процесс формирования изображения в видеобуфере, как это делалось в MS-DOS. Оба эти варианта вывода изображения можно сравнить с рисованием цветными мелками на школьной доске. Для того чтобы обновить содержимое окна, его необходимо либо полностью вывести заново, либо сначала удалить ненужные фрагменты, сформировать на их месте новые и затем вывести в определенное место в окне. Эта проблема называется проблемой перерисовки изображения, и она тесно связана с тем, насколько эффективно решается следующая проблема.
Нужно организовать адекватную обработку сообщений. Эффективность и правильность работы программы напрямую зависит от того, насколько правильно в ней организована обработка сообщений. В самом начале процесса обучения написанию программ для Windows вы столкнетесь с необходимостью обработки такого сообщения, как WM_PAINT. Проблема здесь заключается в том, что Windows не сохраняет содержимое окна или части окна при его свертывании или скрытии под другим окном. Следить за содержимым своих окон должно само приложение, а точнее, соответствующая оконная функция. Более подробно о решении этой проблемы мы узнаем при рассмотрении вопроса перерисовки изображения.
Нужно научиться создавать интерфейсную часть приложения. Интерфейс приложения — это его визитная карточка. Первым, на что обращает внимание пользователь, тем более если он непрофессионал, является именно этот элемент работы приложения. Более того, движущей силой развития самой системы Windows является стремление к реализации идеи идеального интерфейса. На сегодняшний день эту роль играет оконный интерфейс. Что будет завтра, пока не ясно, так как оконный интерфейс действительно решает многие проблемы и до исчерпания его потребительского ресурса, наверное, еще далеко. Основа оконного интерфейса — окно, в котором имеются две области: управляющая, с ее помощью осуществляется управление работой окна, и пользовательская, которая обычно занимает большую часть окна, и именно в ней пользователь формирует некоторое изображение. Управляющая область окна приложения состоит из более элементарных интерфейсных компонентов: меню, окон диалога, кнопок, панелей и т. д. Создание и организация работы со многими из этих компонентов поддерживается Windows с помощью функций Win32 API.
Нужно научиться обрабатывать пользовательский ввод.
Каждый пункт этого списка представляет лишь вершину некоторой иерархии более частных проблем и может быть довольно глубоко детализирован вглубь. Конечно, в рамках нашего изложения сделать это не представляется возможным, да и вряд ли нужно. Подобные вопросы хорошо изложены в литературе. Материал данной главы подобран так, чтобы показать, как отражается на процессе разработки Windows-приложения выбор ассемблера в качестве основного языка программирования. Поэтому основное внимание мы уделяем не тому, как реализовать те или иные элементы пользовательского интерфейса, а технологии сборки работо-
способного приложения с помощью пакета TASM (для MASM отличие только в инструментарии). Исходя из этого далее покажем, как использовать ресурсы в Windows-приложениях, написанных на ассемблере.
Ресурсы Windows-приложений на языке ассемблера
Для включения ресурсов в Windows-приложения, написанные на ассемблере, задействуется та же самая технология, что и для программ на языках C/C++. Ресурс — это специальный объект, используемый программой, но не определяемый в ее теле. К ресурсам относятся следующие элементы пользовательского интерфейса: значки, меню, окна диалога, растровые изображения.
Определение ресурсов производится в текстовом файле с расширением .гс, в котором для описания каждого типа ресурса используются специальные операторы. Подготовку этого файла можно вести двумя способами: ручным и автоматизированным.
Ручной способ предполагает:
т что разработчик ресурса хорошо знает операторы, необходимые для описания конкретного ресурса;
綫 что ввод текста ресурсного файла выполняется с помощью редактора, который не добавляет в текст элементы форматирования, например редактора Блокнот (notepad.exe), входящего в состав программного обеспечения Windows.
Автоматический способ создания ресурсного файла предполагает использование специальной программы — редактора ресурсов, который позволяет визуализировать процесс создания ресурса. Конечные результаты работы этой программы могут быть двух видов: в виде текстового файла с расширением .ГС, который впоследствии можно подвергнуть ручному редактированию, либо в виде двоичного файла, уже пригодного к включению в исполняемый файл приложения.
Будем предполагать, что описание ресурсов в текстовом виде получено и находится в файле ресурсов с расширением .ГС. Далее это описание должно быть преобразовано в вид, пригодный для включения в общий исполняемый файл приложения. Для этого необходимо выполнить перечисленные далее шаги.
*
Откомпилировать ресурсный файл. На этом шаге выполняется преобразование текстового представления ресурсного файла с расширением .ГС в двоичное представление с расширением .res. Для этого в пакете TASM есть специальная программа brc32.exe — компилятор ресурсов.
Включить ресурсы в исполняемый файл приложения. Это действие выполняет компоновщик tlink32.exe, которому в качестве последнего параметра должно быть передано имя ресурсного файла (•0).
Далее на конкретных примерах мы рассмотрим порядок применения этих программных средств для включения некоторых типов ресурсов в Windows-приложения.
Меню в Windows-приложениях
Меню в системе Windows являются, пожалуй, самым распространенным элементом пользовательского интерфейса. Мы не будем особенно вдаваться в детали раз-
работки приложения с меню, так как это уже делалось при рассмотрении каркасного приложения. Содержание этого процесса стандартное, поэтому мы остановимся только на специфических моментах его реализации при разработке приложения на ассемблере. Для того чтобы включить меню в приложение, необходимо реализовать следующую последовательность шагов.
Разработка сценария меню. Перед тем как приступить к процессу включения меню в конкретное приложение, разработаем его логическую схему. Этот шаг необходим для того, чтобы уже на стадии проектирования обеспечить эргономические свойства приложения. Ведь меню — это один из немногих элементов интерфейса, с которым пользователь вашего приложения будет постоянно иметь дело. Поэтому схема меню должна иметь наглядную иерархическую структуру с логически увязанными между собой пунктами, что поможет пользователю эффективно задействовать все возможности приложения. Для того чтобы вести предметный разговор, поставим себе задачу разработать для окна нашего приложения главное меню. При этом мы исследуем возможности вывода в окно приложения текста и графики, а также покажем способы решения общих проблем, связанных с разработкой приложения. Наше меню будет довольно простым и состоять из трех пунктов: Текст, Графика и 0 приложении, — причем первые два пункта будут открывать доступ к подменю. Иерархическая структура меню представлена на рис. 16.2.
ТекстГрафикаО приложении -
' — DrawText[ — ПримитивыОтрезок
Прямоугольник
TextOut
■ — ЭффектыПавлин
Кружева
Рис. 16.2. Иерархическая структура меню
Описание схемы меню в файле ресурсов. Для выполнения этого этапа требуются специальные операторы. В нашем случае файл ресурсов будет выглядеть следующим образом:
//Текст файла menu. гс
#i nclude"nienu. h"
MYMENU MENU DISCARDABLE
{
POPUP "&Текст"
{
MENUITEM "&DrawText", IDM_DRAWTEXT
MENUITEM ^TextOut",IDM^TEXTOUT
}
POPUP "&Графика"
P{OPUP "&Примитивы"
{MENUITEM "&0трезок",EDM—LENGTH
MENUITEM "&Прямоугольник1', IDM_RECTANGLE
POPUP "&Эффекты"
MENUITEM "&Павлин", IDM_PEACOCK MENUITEM "&Кружева",IDM_LACES } }
MENUITEM "&About", IDM_ABOUT
}
Составление текста включаемого файла, необходимого для компиляции ресурсного файла. В нашем случае файл называется menu.h и выглядит следующим образом:
#define MYMENU 999
#define IDM_DRAWTEXT100
#define IDM^TEXTOUT 101
#define IDM_LENGTH 102
#define 工DM二RECTANGLE103
#defi ne IDM_PEACOCK 104
#define IDM_LACES105
#define IDM_ABOUT106
В этом файле идентификаторам пунктов меню назначаются константы, которые впоследствии будут передаваться в оконную процедуру в младшем слове параметра wParam сообщения WM_COMMAND. Заметьте, что имени самого меню также назначена константа. К этому моменту мы вернемся чуть позже.
Компиляция ресурсного файла. Для этого используется утилита brc32.exe: brc32 [ключи ...] menu.rc
Если утилита заканчивает свою работу нормально, то создается файл с расширением .res (menu.res). В случае, если утилита обнаруживает ошибки в исходном ресурсном файле, она выдает на экран соответствующие диагностические сообщения. Для удобства работы с ними их можно записать в файл, перенаправив вывод сообщений с экрана в файл, используя оператор командной строки
brc32 [ключи ...] имя_файла.rc > Имя_Файла_Диаг мостики
например, так:
brc32 menu.rc > р
Подключение меню на стадии регистрации того окна приложения, для работы с которым оно будет использоваться. Для этого существуют два основных способа:
D первый способ — поместить в поле LpszMenuName экземпляра структуры WNDCLASS (строка 101 листинга 16.4) указатель на поле, содержащее имя меню:
.data
menu db "MYMENU"
.code
mov dword ptr wcl.IpszMenuName, offset menu
D второй способ — назначить ресурсу меню символическую константу, которая расположена в поле имени оператора MENU файла ресурсов:
56 MENUDISCARDABLE ;оператор из файла ресурсов
Чтобы использовать упомянутую на последнем шаге символическую константу, необходимо вставить в файлы, из которых собирается приложение, дополнительные строки:
趣 Файл ресурсов:
MYMENU MENUDISCARDABLE
緩 Файл menu.h:
tfdefine MYMENU56
si Файл menu.inc:
MYMENU equ 56
И Строка 101 из листинга 16.4 должна выглядеть так:
mov ciword ptr wcl.IpszMenuName, MYMENU
Илитак:
mov dword ptr wcl.IpszMenuName, 56
Вторым способом мы фактически смоделировали макрос MAKEINTRESOURCE, известный Windows-программистам, пишущим приложения на языке C/C++.
После внесения всех изменений в верхней части окна появится строка меню. Далее необходимо в оконную функцию включить команды, реализующие реакцию на выбор пунктов этого меню. Эта информация передается в младшем слове поля wParam сообщения WM_COMMAND. В листинге 16.7 приведен измененный текст каркасного приложения, дополненный меню.
Листинг16.7.Пример приложения с использованием меню
<1>;Пример приложения для Win32 с использованием меню
<2> .386
<3> locals ;разрешает применение локальных меток в программе
<4>.model flat, STDCALL ;модель памяти flat,
<5> :STDCALL - передача параметров в стиле С (справа налево),
<6> ;вызываемая процедура чистит за собой стек
<7>include wi ndowA.i nc;включаемый файл с описаниями базовых структур
;и констант Win32
<8> include menu.inc ;включаемый файл с определением имен
;идентификатор меню
<9> ;0бъявление внешними используемых в данной программе
;функций Win32 (ASCII):
<10>extrnGetModuleHandleA:PROC
<...>
<37>
<38>
extrnGetClientRect:PROC
;объявление оконной функции объектом, ;видимым за пределами данного кода
<39>
<40>
<41>
<42>
<43>
<44>
<45>
<46>
<47>
<48>
<49>
<50>
public WindowProc
.data
hwnddd 0
hlnstdd 0
:IpVersion Informati on OSVERSIONINFO <?>
wcl WNDCLASSEX <?>
message MSG <?>
ps PAINTSTRUCT <?>
IpRect RECT<?>
szClassName db'Приложение Win32 ', 0
szTitleName db 'Каркасное приложение Win32 на ассемблере', 0
MesWi ndowdb'Привет! Ну как вам процесс разработки приложения
на ассемблере?'
<51>
MesWi ndowLen=$-MesWindowпродолжение夕
410
ГлаваСоздание Windows-приложений на ассемблере
Листинг (продолжение)
<52>
<53>
<54>
<55>
<56>
<57>
<58>
<59>
<60>
<61>
<62>
<63>
<64>
<65>
<бб>
<67>
<68>
<69>
<70>
<71>
<72>
<73>
<74>
<75>
<76>
<77>
<78>
<79>
<80>
<81>
<82>
<83>
<84>
<85>
<86>
<87>
<88>
<89>
<90>
<91>
<92>
<93>
<94>
<95>
<96>
<97>
<98>
<99>
<100>
<101>
<102>
<103>
<104>
db0
db0
db0
.code
startproc near
;точка входа в программу: стартового кода
55-72 листинга 16.4
;конец стартового кода
WinMain:
;строкилистинга 16.4
dwordptr
;строки 102-149 листинга 16.4
startendp
; -- Wiroc - - - -- -
proc
arg
uses ebx,ebx ;эти регистры обязательно должны сохраняться
local @@hdc:DWORD
cmp @@mes, WM_DESTROY j e wmdestroy*
cmp
je
cmpWM_PAINT
je
cmp
je
default
wmcreate:
строки 176-212 листинга 16.4
послать сообщение
готовим вызов VOIDnExitCode)
push 0
call
moveax, 0значение - 0
jmpexit_wndproc
wmcommand:
процедуры обработки сообщений от меню
(DWORDDWORD
push
push
call
jmpexit_wndproc
default:
по умолчанию
вызовMsg
;WPARAM wParam, LPARAM IParam):
<105>
<106>
<107>
<108>
<109>
<110>
<111>
<112>
<113>
push
push
push
push@@hwnd
call
jmp
exi t_wndproc :..
ret
Углубленное программирование на ассемблере для Win32411
<114>
<115》
<116>
<117>
<119>
<120>
<121>
<122>
<123>
<124>
<12S>
<126>
<127>
<128>
<129>
<130>
<131>
<132>
<133>
<134>
<135>
<136>
<137>
<138>
<139>
<140>
<141>
<142>
WindowProc endp - обработка сообщений от меню MenuProcproc arg@@hwnd:DWORD, @@wparam:DWORD
usesebx
local@@hdc:DWORD
mov ebx, @@wparam ; в bx идентификатор меню cmp bx, IDM_DRAWTEXT je @@1dmdrawtext cmp bx, IDM_TEXTOUT je @@1dmtextout cmp bx, IDM—LENGTH je @@idmlength cmp bx, IDM_RECTANGLE je @@idmrectangle cmp bx, IDM_PEACOCK je @@idmpeacock cmp bx, IDM_LACES je @@idmlaces cmp bx, IDM_ABOUT je @@idmabout jmp @@exi t @@idmdrawtext:
;получаем контекст устройства HDC GetDC(HWND hWnd) push @@hwnd call GetDC mov@@hdc, eax
;получаем размер рабочей области
;BOOL GetClientRect(HWND hWnd, LPRECT IpRect)
<143>
<144>
<145>
<146>
pushoffset IpRect
push@@hwnd
callGetCUentRect
выводим строку текста в окно int DrawText(HDC hDC, LPCTSTR IpString, int nCount ,
<147>
<148>
<149>
<150>
<151>
<152>
<153>
<154>
<1S5>
<156>
<157>
<158>
<159>
<160>
<161>
<162>
<163>
<164>
<165>
<166>
<167>
<168>
<169>
<170>
<171>
<172>
<173>
<174>
<175>
<176>
LPRECT IpRect, UINT uFormat)
pushDT„SINGLELINE+DT_BOTTOM
pushoffsetIpRect
push-1
pushoffset@@TXT_DRAWTEXT
push@@hdc
callDrawTextA
освобождаем контекст int ReleaseDC(HWND hWnd, HDC hdc)
push@@hdc
push@@hwnd
callReleaseDC
jmp@@exit
@@idmtextout:
push@@hwnd
callGetDC;получаемконтекст устройства
mov@@hdc,eax
;выводим строку текста в окно BOOL TextOut(HDC hd j int nXStart, ;int nYStart, LPCTSTR IpString, int cbString)
pushlenTXT_TEXTOUT
pushoffset-@@TXT_TEXTOUT
push150
push10
push@@hdc
callTextOutA
push@@hdc
push@@hwnd
callReleaseDCосвобождаем контекст устройства
jmp@@exi t
@@i dmlength:
pushMB^ICONINFORMATION+MB_OKпродолжи 抄
Листинг 16.7 f продолжение)
<177>
push
offset szTitleName
<178>
push
offset @@TXT_LENGTH
<179>
push
@@hwnd
<180>
call
MessageBoxA
<181>
jmp
@@exit
<182>
@@idmrectangle:
<183>
push
MB_ICONINFORMATION+MB_OK
<184>
push
offset szTitleName
<185>
push
offset @@TXT_RECTANGLE
<186>
push
@@hwnd
<187>
call
MessageBoxA
<188>
jmp
@@exit
<189>
@@idmpeacock:
<190>
push
MB_ICONINFORMATION+MB_OK
<191>
push
offset szTitleName
<192>
push
offset @@TXT_PEACOCK
<193>
push
@@hwnd
<194>
call
MessageBoxA
<195>
jmp
@@exi t
<196>
@@idmlaces:
<197>
push
M.B_ICONINFORMATION+MB_OK
<198>
push
offset szTitleName
<199>
push
offset @@TXT_LACES
<200>
push
@@hwnd
<201>
call
MessageBoxA
<202>
jmp
@@exi t
<203>
@@idmabout:
<204>
push
MB_ICONINFORMATION+MB_OK
<205>
push
offset szTitleName
<206>
push
offset @@TXT_ABOUT
<207>
push
@@hwnd
<208>
call
MessageBoxA
<209>
jmp
@@exi t
<210>
■ • • •
<211>
@@exi t:
<212>
mov eax,
, 0
<213>
ret
<214>
@@TXT ABOUT
db 'IDMABOUT', 0
<215>
@@txT LACES
db 'IDM~LACES', 0
<216>
@@TXT PEACOCKdb'I DIVPEACOCK', 0
<217>
@@TXT RECTANGLE db ■ idfTrectangle', 0
<218>
@@TXT LENGTHdb 'IDMLENGTH' 0
<219>
@@TXT TEXTOUTdb ""'Текст выведен функцией TEXTOUT'
<220>
lenTXT_TEXTOUT=$-@@TXT TEXTOUT
<221>
@@TXT_DRAWTEXT db 'Текст выведен функцией DRAWTEXT \ 0
<222>
MenuProcendp
<223>
end start
Строки 98-100 листинга 16.7 показывают, что для обработки сообщения WM_ COMMAND вызывается процедура MenuProc. Подход, при котором программа на ассемблере структурируется на более мелкие части с использованием механизма процедур, при программировании для Windows особенно полезен и должен быть преобладающим. Для большей наглядности мы не задействуем макрокоманды. Применение макрокоманд полезно и даже необходимо, так как они позволяют получать компактный и структурированный код.
В заключение этого раздела обратитесь к файлам, прилагаемым к книге, где в каталоге данной главы вы найдете все исходные тексты, необходимые для сборки и запуска приложения (см. листинг 16.7). Собрав с помощью утилиты make.exe исполняемый файл, вы увидите, что реакция программы на выбор большинства
пунктов меню стандартная и заключается в выводе сообщения с использованием функции Win32 API MessageBox. Определена реакция лишь на два пункта меню Текст — пункты DrawText и TextOut. При их выборе на экран выводится текст. Тем самым демонстрируются различные способы вывода текста в окно приложения. Далее в материале этой и последующих глав мы определим реакцию и на остальные пункты меню.
Давайте теперь рассмотрим еще одну ключевую проблему программирования для Windows — перерисовку изображения. Для того чтобы понять ее смысл и важность, в любом порядке выберите пункты меню Текст ► DrawText и Текст ► TextOut На экране появятся строки текста. Далее сверните и разверните окно приложения. Строки текста исчезнут, оставив только строку, которая выводится как реакция на сообщение WM_PAINT. Причина этой ситуации в том, что Windows не хранит содержимое окна, и заботиться о его восстановлении после различных действий с окном должно приложение, создавшее это окно. Windows лишь посылает приложению сообщение WM_PAINT в случаях, когда с окном были произведены некоторые действия, например, свертывание-развертывание окна, изменение его размеров и т. д. Приложение, получив сообщение WM_PAINT, в качестве реакции на него должно обновить в необходимой степени содержимое своего окна. Более подробно эта проблема и пути ее решения рассмотрены в любом хорошем учебнике по программированию для Windows. Отметим лишь, что в конечном итоге все сводится к необходимости организовать вывод в окно приложения как реакцию на сообщение WM_PAINT. Существует несколько подходов к решению проблемы перерисовки. Мы рассмотрим наиболее общий и широко используемый.
Перерисовка изображения
Рассмотрим следующую абстракцию, цель которой — показать, что за содержимое окон на экране отвечают оконные процедуры тех приложений, которым эти окна принадлежат. Роль Windows в этом процессе минимальна и состоит в том, что при определенных действиях с окном оконной процедуре, отвечающей за связь с этим окном, посылается сообщение WM_PAINT. Получив это сообщение, оконная процедура должна уметь заново перерисовать содержимое всего окна или его части. Для общего случая это, если задуматься, не такая уж простая задача.
Если у вас возникают трудности с пониманием этого момента работы приложений в Windows, попробуйте развить абстракцию со школьной доской. Представьте себе школьный класс, в котором процесс обучения выглядит очень необычно. Вместо обычной школьной доски на стене есть некоторая ограниченная область (экран монитора). У каждого ученика (исполняемого файла) есть в портфеле своя доска (окно приложения), которая может (или не может, в зависимости от конструкции) менять свой размер. На всех учеников один комплект цветных мелков (контекст устройства), обладание которым разрешает ученику что-то рисовать на своей доске. Ученик объясняется с присутствующими графическими образами, выводя их на свою доску. Но прежде доску нужно повесить на стену в пределах ограниченной области. Для этого ученик, как ему и положено, подымает руку. Учитель (Windows) обязательно помогает ученику выйти к ограниченной области на стене (запускает на выполнение задачу) и повесить свою доску. Действия учителя и ученика в ходе этого процесса соответствуют определенному алгоритму и дей-
ствиям каркасного приложения. В результате могут быть закрыты частично или полностью доски учеников, вышедших к доске ранее и не ушедших пока на свои места в классе. При этом информация в скрытых частях расположенных ниже досок других учеников уничтожается в пределах ограниченной области.
Итак, доска ученика с помощью учителя повешена в пределах области на стене, и для того чтобы рисовать на ней, ученик должен попросить у учителя комплект мелков (см. функцию API GetDC или BeginPaint в листинге 16.4). Если ученик его получает, то он может начинать процесс рисования на своей доске. Как только ученик заканчивает этот процесс, он должен вернуть учителю комплект мелков (функция EndPaint). Далее ученик может закончить ответ и, забрав свою доску, занять свое место в классе (приложение завершило работу и «ушло» на диск). Если ученик делает это, то, забирая свою доску, он открывает доски других учеников, и... на этих досках обнаруживаются черные дыры, причем местоположение этих дыр соответствует местам, которые были скрыты доской их ушедшего товарища. Чтобы сгладить этот конфуз, учитель срочно оповещает об этом (Windows посылает сообщение WM_PAINT) учеников, у которых было испорчено содержимое досок. Каждый из этих учеников должен, попросив у учителя комплект мелков, перерисовать содержимое своей доски.
Данная абстракция довольно точно отражает логику работы Windows, причем не только в обозначенном нами контексте. Вы можете дополнить и развить ее в нужную вам сторону.
Как решается проблема перерисовки изображения практически? Для этого существует несколько способов, самый общий из которых заключается в использовании виртуального окна. Суть перерисовки изображения на основе виртуального окна заключается в использовании приложением некоторой области памяти для направления в него всего вывода программы. Реальный вывод в окно приложения осуществляется только как реакция на получение сообщения WM_PAINT. He забывайте, что программа с помощью функции InvalidateRect() может сама себе послать сообщение WM—PAINT, когда ей потребуется вывести новый фрагмент изображения в окно приложения. Понятие виртуального окна настолько важно для организации работы Windows, что Win32 API содержит ряд функций, поддерживающих работу с этим окном: CreateCompatibleDC(), SelectObject(), GetStockObject(), BitBLt(), CreateCompatibleBitmapO и PatBtt(). Рассмотрим порядок их использования в реальном приложении.
Работа с виртуальным окном в программе организуется в два этапа: создание виртуального окна и организация непосредственной работы с ним. Создать виртуальное окно целесообразно при обработке сообщения WM/REATE, то есть в момент создания окна приложения. Работать с этим окном можно в любое время, когда требуется вывод в окно.
Для наглядности обсуждения приведем фрагмент программы на языке C/C++: //фрагмент оконной процедуры из программы на языке C/C++
case WM_CREATE:
//определить размеры экрана
maxX = GetSystemMetrics (SM_CXSCREEN);
maxY =
//строим растровое изображение, совместимое с окном
hdc = GetDC (hwnd);
memdc = CreateCompatibleDC (hdc);
hbit =(hdc, maxX, maxY);
SelectObject (memdc, hbi t);
//закрашиваем окно серым цветом hbrush =
SelectObject (memdc, hbrush);~
PatBlt (memdc, 0, 0, maxX, maxY, PATCOPY);
ReleaseDC (hwnd, hdc);
case WM_PAINT: //перерисовываем окно
hdc = BeginPai nt (hwnd, &paintstruct); //получаем дескриптор реального окна //копируем растровое изображение из памяти на экран
BitBlt (hdc, 0, 0, maxX, maxY, memdc, 0, 0, SRCCOPY);
EndPaint (hwnd, *дескриптор
//выводим в окно где-то в программе
TextOut(memdc, X, Y, str, strlen(str)); //выводим строку
//для немедленного вывода в окно вызываем функцию InvalidateRect: InvalidateRect (hwnd, NULL,;
Физически виртуальное окно представляет собой растровое изображение, хранящееся в памяти. Работа с этой областью памяти организуется так же, как и с окном приложения на экране монитора. Это означает, что для работы с ним необходимо создать контекст устройства памяти, совместимый с контекстом окна. Это действие реализуется двумя функциями: с помощью функции GetDC() приложение получает контекст окна; функция CreateCompatibleDC() создает совместимый с контекстом окна контекст памяти memdc. После этого функцией CreateCompati- bleBitmap() создается совместимое с реальным окном на экране растровое изображение. Его размеры должны соответствовать размеру окна, для работы с которым оно строится. Поэтому предварительно с помощью функции GetSystemMetrics() должны быть получены размеры окна и переданы как параметры в функцию CreateCompatibleBitmapO, которая возвращает дескриптор на созданное растровое изображение. После этого функция SelectObject() выбирает созданное растровое изображение в контекст памяти, который, в свою очередь, является совместимым с контекстом окна. Благодаря такой цепочке связей обращение к растровому изображению в памяти производится аналогично обращению к реальному окну. На практике это означает, что во всех функциях, выводящих изображение в окно, на месте параметра, соответствующего контексту устройства, необходимо указывать контекст устройства памяти. Например, функция TextOut() будет вызываться следующим образом: pushlenTXT—TEXTOUT
pushoffset ~@@TXT_TEXTOUT
push150
push10
push@@memdc
callTextOutA
В пятой строке этого фрагмента функции TextOutQ передается не контекст окна, а контекст виртуального окна, что и приводит к выводу не в реальное окно, а в виртуальное, являющееся растровым изображением. Как мы уже отметили, в программе есть только одно место, где производится вывод в реальное окно, — это фрагмент программы, обрабатывающий сообщение WM_PAINT. В случае виртуального
окна здесь располагается функция BitBLt(), которая копирует содержимое растрового изображения из контекста памяти в контекст реального окна. Таким образом постоянно обеспечивается актуальное содержимое окна приложения. В листинге 16.8 приведены фрагменты текста программы на языке ассемблера, демонстрирующей практическую реализацию способа перерисовки окна приложения с использованием виртуального окна. Полный текст программы находится среди файлов, прилагаемых к книге, в каталоге ..\prgi6_3 данной главы.
Листинг 16.8. Фрагменты приложения prg16_3.asm
фрагменты приложения (prgl6_3.asm) для Win32 с использованием меню
;и виртуального окна для перерисовки содержимого окна; .386
locals;разрешает применение локальных меток (с префиксом (P@) в программе .model flat, 5TDCALL:модель памяти flat,
;STDCALL - передача параметров в стиле С (справа налево), ;вызываемая процедура чистит за собой стек
include windowA.1nc ;включаемый файл с описаниями базовых структур
;и констант Win32
include menu.1nc ;включаемый файл с определением имен пунктов
;меню
;0бъявпение внешними используемых в данной программе
;функций Win32 (ASCII):
extrnGetModuleHandleA:PROC
extrn GetDCrPROC
extrn Begi nPai nt :PROC
extrnEndPaint:PROC
extrn MessageBoxA:PROC
extrn DrawTextA:PROC
extrn GetClientRect:PROC
extrn GetSystemMetrics:PROC
extrn CreateCompati bieDC:PROC
extrn CreateCompati bleBi tmap:PROC
extrn SelectObject:PROC
extrn GetStockObject:PROC
extrn PatBlt:PROC
extrn BitBXt:PROC
extrn InvalidateRect:PROC
extrn DeleteDC:PROC
.data
memdcdd0;!!!это глобальная переменная
maxXdd@;!!!это глобальная переменная
maxYdd;!!!это глобальная переменная
IpRect RECT<?>
. code
startproc near
;точка входа в программу:
W1nMai n:
startendp
;
■Wi ndowProc proc
arg @@hwnd:DWORD, @@mes:DWORD, @@wparam:DWORD, @@lparam:DWORD
uses ebx, edi, esi, ebx ;эти регистры обязательно должны сохраняться
local@@hdc:DWORD, @@hb「ush:DWORD, @@hbit:DWORD
cmp @@mes. WM_DESTROY
je wmdestroy
cmp @@me$, WM_CREATE
je wmcreate
cmp @@mes, WM—PAINT je wmpai nt ~
crap @@mes, WM—COMMAND je wmcommand~ jmp default
wmcreate:
;создание растрового изображения, совместимого с окном приложения
;получим размер экрана в пикселах int GetSystemMetrics(int nlndex) pushSM_CXSCREEN
callGetSystemMetrics
movmaxX, eax
pushSM_CYSCREEN
callGetSystemMetrics
movmaxY,eax
;лолучить контекст устройства окна на экране @@hdc=GetDC(@@hwnd) push@@hwnd
callGetDC
mov@@hdc,eax
;получить совместимый контекст устройства памяти
;memdc=CreateCompatibleDC(@@hdc)
push@@hdc
call CreateCompatibleDC
movmemdc, eax :! ! ! memdc - глобальная переменная
;получить дескриптор растрового изображения в памяти
; @@hbi t=CreateCompatibleBi tmap(@@hdc , @@maxX, @@maxY)
pushmaxY
pushmaxX
push@@hdc
callCreateCompati bleBi tmap
mov@@hbi t,eax
;выбираем растр в контекст памяти SelectObject(memdc, @@hbit) push@@hbi t
pushmemdc
call SelectObject
;выполним первичное заполнение растра серым цветом
;получим дескриптор серой кисти hbrush=GetStockObject(GRAY_BRUSH) pushGRAY_BRUSH一
call GetStockObject mov@@hbrush, eax
;выбираем кисть в контекст памяти SelectObject(memdc, @@hbrush) push@@hbrush
pushmemdc
callSelectObject
;заполняем выбранной кистью виртуальное окно
;BOOL PatBlt(HDC hdc, int nXLeft. int nYLeft, int nWidth,
;i nt nHeight, DWORD dwRop)
pushPATCOPY
pushmaxY
pushmaxX
pushNULL
pushNULL
pushmemdc
callPatBlt
;освободим контекст устройства ReleaseDC(@@hwnd, @@hdc)
push@@hdc
push@@hwnd
callReleaseDC
;обозначим создание окна звуковым эффектом
;готовим вызов функции BOOL PlaySound(LPCSTR pszSound, ;HMODULE hmod, DWORD fdwSound) pushSND—SYNC+SND一FILENAME
14 Зак. 256
1nlicтин「16.8(Т7родап*ен勿e丿
push
NULL
push
offset playFileCreate
call
PlaySoundA
;возвращаем
значение О
mov
eax, 0
jmp
exi t_wndproc
wmpai nt:
;получим контекст устройства HDC BeginPai nt(HWND hwnd,
;LPPAINTSTRUCT IpPaint);
push
offsetps
push
@@hwnd
call
BeginPaint
mov
@@hdc, eax
;обозначим
перерисовку окна звуковым эффектом
push
SND_SYNC+SND_FILENAME
push
NULL
push
offset playFilePai nt
call
PlaySoundA
;выведем строку текста в окно BOOLTextOut(HDChdc,intnXStart, ;i nt nYStart, LPCTSTRlpStri ng,i ntcbStri ng);
push
MesWindowLen
push
offset MesWi ndow
push
100
push
10
push
memdc
call
TextOutA
вывод виртуального окна в реальное окно
BOOL BitBlt(HDC hdcDest, int nXDest, int nYDest, int nWidth, int nHeight, HDC hdcSrc, int nXSrc, int nYSrc, DWORD dwRop)
push
SRCCOPY
push
NULL
push
NULL
push
memdc
push
maxY
push
maxX
push
NULL
push
NULL
push
@@hdc
call
BitBlt
освободить
контекст BOOL EndPaint(HWND hWnd,
;CONST
PAINTSTRUCT *lpPaint);
push
offset ps
push
@@hwnd
call
EndPaint
mov
eax, 0 :возвращаемое значение - 0 exit_wndproc
jmp
wmdestroy:
wmcommand:
;вызов процедуры обработки сообщений от меню
;MenuProc (DWORD @@hwnd, DWORD @@wparam)
push
@@wparam
push
@@hwnd
call
MenuProc
jmp
exi t_wndproc
default:
exi t_wndproc:
ret
Wi ndowProc endp
MenuProc---
;обработка сообщений от меню
MenuProcproc
arg @@hwnd:DWORD, @@wparam:DWORD uses ebx local
mov ebx, @@wparam ;b Ьх идентификатор меню
jmp @@exi t @@idmclrawtext:
;получим размер рабочей области BOOL GetClientRect(HWND hWnd. ;LPRECT IpRect);
pushoffset IpRect
push@@hwnd
call GetClientRect
;выведем строку текста в окно int DrawText(HOC hDC,
;LPCTSTR IpString, int nCount, length, ;LPRECT IpRect, UINT uFormat);
push
DT_SINGLELINE+DT_BOTTOM
push
offset IpRect
push
-1
push
offset @@TXT_DRAWTEXT
push
memdc
call
DrawTextA
;генерация сообщения WM_PAINT для вывода строки на экран
;BOOL InvalidateRect(HWND hWnd, CONST RECT *lpRect, BOOL bErase)
push1
pushNULL
push@@hwnd
callInvalidateRect
jmp@@exi t
@@idmtextout:
;выведем строку текста в окно BOOL TextOut(HDC hdc, int nXStart,
;int nYStart, LPCTSTR IpString, int cbString)
pushlenTXT_TEXTOUT
pushoffset ~@@TXT_TEXTOUT
push150
push10
pushmemdc
callTextOutA
;генерация сообщения WM—PAINT для вывода строки на экран
push0
pushNULL
push@@hwnd
callInvalidateRect
jmp@@exi t
@@idmlength:J
jmp@@exi t
@@idmrectangle:
jmp@@exit
@@idmpeacock:
jmp
@@exi t
@@idmlaces:
jmp
@@exi t
@@idmabout:
jmp
@@exit
@@exit:
mov
eax, 0
ret
@@TXT_AB0UT db 'IDM_AB0UT', 0
Листинг 16.8 (продолжение)
@@TXT_LACES db ' IDM_LACES ' , 0
@@TXT_PEACOCK db '1DM_PEACOCK', Э
@@TXT_RECTANGLE db UDM二 RECTANGLE,, 0
@@TXT二LENGTHdb ' IDM_LENGTH' , 0
@@TXT_TEXTOUT db ~ Текст выведен функцией TEXTOUT' lenTXT_TEXTOUT=$-@@TXT_TEXTOUT
@@TXT_DRAWTEXT db 'Текст выведен функцией DRAWTEXT', 0 MenuPTocendp end start
Возможно, результаты работы программы из листинга 16.8 вам покажутся не очень красивыми, но такая цель и не ставилась. Назначение этой программы — исследовательское. Из-за задержек, вызванных воспроизведением звуковых файлов, хорошо виден момент перерисовки окна. Такую технологию можно использовать для более глубокого исследования механизмов работы Windows.
Окна диалога в Windows-приложениях
Окна диалога являются важными и популярными элементами пользовательского интерфейса Windows. Редкое оконное приложение обходится без окон этого типа. Физически окно диалога представляет собой специфическое окно, работа с которым поддерживается на уровне интерфейса Win32 API Windows. Основное назначение этого окна — помочь пользователю сформировать информацию, необходимую для управления работой приложения. Наиболее наглядные примеры окон этого типа — окна диалога в текстовом редакторе. С их помощью можно задать параметры шрифта, страницы или печати. Очень важно, что разработка таких окон не требует программирования. Для описания окон диалога система Windows поддерживает специальный тип ресурса. Более подробно о том, как в программе должны выглядеть окна диалога, из каких элементов они состоят, а также о деталях управления этими окнами вы можете почитать в других источниках. Наша цель — показать, каким образом работа с окнами диалога организуется программой, написанной на языке ассемблера.
С точки зрения технологии, организация работы с окнами диалога, реализуемая программой на ассемблере, ничем не отличается от того, как это делается на любом другом языке. Чтобы создать окно диалога на языке ассемблера, необходимо выполнить следующие шаги.
Описать окно диалога в файле ресурсов.
Разработать диалоговую функцию, которая будет обрабатывать сообщения, предназначенные для определенного в файле ресурсов окна диалога.
Активизировать окно диалога в приложении.
Для того чтобы разговор был предметным, поставим себе цель разработать конкретную программу. В последней программе (см. листинг 16.8) мы предусмотрели возможность ее расширения. Сейчас для этого настало время. Дополним программу из листинга 16.8 фрагментами, обеспечивающими работу подменю Примитивы в меню Графика (см. рис. 16.2). Это подменю обеспечивает доступ к пунктам меню Отрезок и Прямоугольник. Пусть они инициируют открытие окон диалога, предназначенных для настройки параметров простейших фигур (отрезка и прямоуголь-
ника) и прорисовки их в окне приложения в соответствии с заданными параметрами. Параллельно мы разберемся с некоторыми общими принципами работы с графикой, понимая которые, вы сможете реализовать более сложные алгоритмы.
Описание окна диалога в файле ресурсов
Как было отмечено, окно диалога представляет собой специальный объект, призванный облегчить взаимодействие пользователя с выполняющейся программой и настроить ее на определенные условия функционирования. Окно диалога состоит из элементарных объектов, называемых элементами управления. Система Windows поддерживает несколько типов таких объектов. В рассматриваемом далее примере мы будем использовать только небольшую их часть.
Для описания внешнего вида окна диалога и его интерфейса с приложением используется специальный тип ресурса — DIALOG. В отличие от ресурса меню, который можно создать вручную, ресурс окна диалога лучше создавать с помощью соответствующих программных средств — редакторов ресурсов. Основная причина здесь в том, что при ручном определении размеров и взаимного расположения элементов управления трудно представить, как окно будет выглядеть на экране. Сформулируем некоторые общие принципы, которые позволят вам без труда выполнять эту часть работы.
Пакет ассемблера не имеет своего редактора ресурсов, поэтому вам придется поискать его на стороне. Это не обязательно должен быть автономный редактор ресурсов — вполне подойдет редактор, встроенный в интегрированную среду разработки какого-нибудь языка высокого уровня. Главное, чтобы он создавал файл описания ресурса с расширением ,rc. В файле описания наряду со строками, описывающими ресурс, будут строки, необходимые для работы интегрированной среды с ресурсами. Их нужно просто удалить. Строки, содержащие непосредственное описание ресурса, включите в общий файл описания ресурсов приложения.
Ресурсы окон диалога показанного далее приложения создавались с помощью редактора ресурсов VC ++ версии 6.0. Ресурс каждого окна диалога готовился отдельно, а затем добавлялся в файл ресурсов приложения ргд1б.4.гс. В этом же файле, кстати, уже находился ранее разработанный ресурс с описанием меню приложения. Параллельно с созданием файла ресурсов приложения необходимо создать (или модифицировать) включаемый файл описаний, содержащий символьные константы для пунктов меню и окон диалога. В нашем примере файл ресурсов имеет описание, представленное в листинге 16.9.
Листинг 16.9. Файл ресурсов prg16_4.rc
1> #i nclude "Prgl6_4.h"
2> #1nclude <wi ndows.h>
3> IDI_ICON1 ICONDISCARDABLE "iconl.ico"
4> MYMENU MENU DISCARDABLE
5> {
...> см. описание меню в пункте "Меню в Wi ndows-приложениях"
25>}
26>///////////////////////////////////////////////////////////////////
27> II
28>// Dialog для отрезка
29>//
30>IDD_DIALOG1 DIALOG DISCARDABLE 0, 0, 186, 95
422
Глава 16. Создание Windows-приложений на ассемблере
Листинг 16.9 (продолжение)
<31>
STYLE DS MODALFRAME | WS POPUP | WS CAPTION | WS SYSMENU
<32>
CAPTION "Отрезок"
<33>
FONT 8, "MS Sans Serif"
<34>
BEGIN
<35>
DEFPUSHBUTTON "ОК", IDOK, 35, 72, 50, 14
<36>
PUSHBUTTON "Cancel", IDCANCEL, 118, 72, 50, 14
<37>
LTEXT "Задайте координаты концов отрезка:", IDC STATIC,
22,
6, 134, 8
<38>
EDITTEXTIDC EDIT1, 34, 37, 20, 12, ES AUTOHSCROLL
<39>
LTEXT"Xstart", IDC STATIC, 5, 40, 19, 8
<40>
LTEXT"Ystart". IDC STATIC, 5, 54, 19, 8
<41>
LTEXT "XendH, IDC STATIC, 91, 39, 18. 8
<42>
LTEXT"Yend", IDC STATIC, 91, 52, 18, 8
<43>
EDITTEXTIDC EDIT2, 34, 52, 20, 12, ES AUTOHSCROLL
<44>
EDITTEXTIDC EDIT3, 118, 36, 20, 12, ES AUTOHSCROLL
<45>
EDITTEXTIDC EDIT4, 118, 52, 20, 12, ES AUTOHSCROLL
<46>
END
<47>
/////////////////////III//////////////// III////////////////////////
<48>
//
<49>
II Dialog для прямоугольника
<50>
//
<51>
IDD DIALOG2 DIALOG DISCARDABLE 0, 0, 186, 95
<52>
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
<53>
CAPTION "Прямоугольник"
<54>
FONT 8, "MS Sans Serif"
<S5>
BEGIN
<56>
DEFPUSHBUTTON "OK". IDOK, 35, 72 , 50, 14
<57>
PUSHBUTTON "Cancel". IDCANCEL, 118, 72 , 50, 14
<58>
LTEXT "Задайте координаты углов прямоугольника:",
IDC STATIC, 22, 6, 174, 8
<59>
EDITTEXTIDC EDIT1, 34, 37, 20, 12, ES AUTOHSCROLL
<60>
LTEXT"X*', IDC STATIC, 22, 39, 8, 8
<61>
LTEXT"Y", IDC STATIC, 21, 55, 8, 8
<62>
LTEXT"X", IDC STATIC, 104, 39, 8, 8
<63>
LTEXT"Y", IDC STATIC, 104, 54, 8, 8
<64>
EDITTEXTIDC EDIT2, 34, 52, 20, 12, ES AUTOHSCROLL
<65>
EDITTEXTIDC EDIT3. 118, 36, 20, 12, ES AUTOHSCROLL
<66>
EDITTEXTIDC EDIT4, 118, 52, 20, 12, ES AUTOHSCROLL
<67>
LTEXT"Left Top:", IDC STATIC, 4, 27, 32, 8
<68>
LTEXT "Right Bottom:n, IDC STATIC, 65, 27, 46, 8
<69>
END
<70>
III/////////////////////////////////////////////////III II III III////
<71>
<72>
II Dialog для пункта меню About
<73>
//
<74>
AboutBox DIALOG 20, 20. 160, 80
<75>
STYLE W5 POPUP | W5 DLGFRAME
<76>
{
<77>
CTEXT"TASM32”~1, 0. 12, 160, 8
<78>
ICOW'IDI ICON1" -1, 8, 8, 0, 0
<79>
CTEXT"Win32 Demo Program"-1, 0, 36, 160, 8
<80>
CTEXT"(с) Юров Виктор, 2003" -1, 0, 48, 160, 8
<81>
DEFPUSHBUTTON"OK'-IDOK, 64, 60, 32, 14, WS GROUP
<82>
}
В файле определены ресурсы трех типов: значок (ICON), меню (MENU) и окно диалога (DIALOG). Их можно создать по отдельности в редакторе ресурсов, а затем с помощью текстового редактора объединить в один файл. При этом вы не должны забывать и о сопутствующим их включаемым файлам с расширением .h. Их также удобно объединить в единый файл так, как это сделано для нашего примера (листинг 16.10).
Листинг16.10.Включаемыйфайлсидентификаторамиэлементовресурсов prg16_4.h
#define IDM_DRAWTEXT100
#defineIDM二TEXTOUT101
#defineIDM二LENGTH102
#defIneIDM_RECTANGLE103
#defineIDM_PEACOCK104
#defineIDM_LACES105
#defi neIDM_ABOUT106
#defI neIDC_EDIT11000
#defineIDC_EDIT21001
#defineIDC_EDIT31002
#defineIDC_EDIT41003
#define IDC_STATIC -1
На основании включаемого файла должен быть составлен эквивалентный включаемый файл prgl6_4.inc (листинг 16.11).Директиву include prg!6_4.inc необходимо помещать в начале исходного текста приложения. В принципе, этого можно и не делать, но тогда нельзя будет использовать символические имена констант, определенные в файле prgl6_4.h. Вместо них в соответствующих местах программы придется указывать их численные значения.
Листинг16.11.Включаемый файлс идентификаторами элементов ресурсов prg16_4.inc
I DM DRAWTEXT
equ
100
I丽TEXTOUT
equ
101
IDM LENGTH
equ
102
IDM RECTANGLE
equ
103
IDM PEACOCK
equ
104
IDM LACES
equ
105
IDM ABOUT
equ
106
IDC EDIT1
equ
1000
IDC EDIT2
equ
1001
IDC EDIT3
equ
1002
IDC EDIT4
equ
1003
IDC STATIC
equ
-1
После того как файлы созданы, необходимо выполнить компиляцию файла ресурсов и получить его двоичный эквивалент prgl6_4.res. Файл ресурсов рассматриваемого нами приложения, в отличие от файла ресурсов prgl6_3,rc, имеет особенности. Эти особенности связаны с тем, что при описании ресурса окна диалога используются символические имена констант, определенные в файле windows.h. Дальнейшие действия зависят от того, каким компилятором языков C/C++ вы располагаете, хотя последовательность этих действий во всех случаях будет приблизительно одинаковой. Рассмотрим эту последовательность на примере компилятора Visual C++ версии 6.0, который использовался для компиляции файла ресурсов приложения prg!6_4:
Скопировать исполняемый файл компилятора ресурсов . .\Msdev\bin\rc.exe в свой рабочий каталог・・\.・\W0RK32L
Предполагается, что вы следуете рекомендациям и ведете всю текущую работу в рабочем каталоге, к примеру .•\..\WORK. В нем находятся необходимые файлы из пакета TASM и все файлы, относящиеся к текущему разрабатываемому приложению. Теперь у вас будет два каталога: для 16-разряд- ных приложений .\.\WORKи для 32-разрядных приложений ,.\..\WORK32.
Поместить в файл autoexec.bat строку: callTasmVars.bat. Файл TasmVars.bat предназначен для установки переменных окружения. Значения переменных определяют путь для поиска включаемых, исполняемых и других файлов. После модификации файла autoexec.bat необходимо перезагрузить компьютер, с тем чтобы изменения вступили в силу.
Запустить компилятор ресурсов, указав ему в качестве параметра имя созданного нами ранее ресурсного файла:
гс.exe prgl6_4.ГС
Если указанные действия выполнены корректно, вы получите файл prgl6_4.res. То, насколько удачно вам удалось определить ресурсы для вашего приложения, можно проверить только на этапе его выполнения. Если что-то вас не устраивает, то описанный ранее процесс придется повторить, внеся необходимые коррективы.
Диалоговая процедура
В процессе работы с окном диалога пользователь выполняет некоторые действия, о которых с помощью сообщений становится известно приложению. В приложении для каждого окна диалога должна существовать своя процедура, предназначенная для обработки сообщений этого окна. Эта процедура называется диалоговой процедурой. Даже самое примитивное окно диалога содержит элемент, сообщение от которого поступает в диалоговую процедуру. Обычно это кнопка OKили Cancel. На языках C/C++ соответствующая диалоговая процедура выглядит так, как показано в листинге 16.12.
Листинг16.12.Диалоговая процедура на языках C/C++
BOOL CALLBACK Di alogProc (HWND hdwnd, UINT message, WPARAM wParam, LPARAM IParam)
{
switch(message)
{
case WM_INITDIALOG:
return 1;
case WM_COMMAND:
switch (LOWORD (wParam))
{
case IDOK: //или IDCANCEL
EndDialog(hdwnd, 0); return 1;
}
}
return 0;
}
В приложении prgl6.4.asm, частично представленном в листинге 16.13, имеется фрагмент (строки 523-549), являющийся эквивалентом программы из листинга 16.12 на языке ассемблера. Это процедура AboutDialog, поддерживающая работу окна диалога AboutBox, описанного в файле ресурсов (см. листинг 16.9). Заметьте, что если оконная процедура самостоятельно обрабатывает сообщение, то она должна возвратить 1 (return 1), если нет, то 0 (return 0).
Листинг 16.13. Фрагменты приложения prg16_4.asm
<1> ;P「gl6_4.asm
<2> ;Пример приложения для Win32 с использованием меню,
<3> ;окон диалогов, решением проблемы перерисовки содержимого окна
<4> ;и демонстрацией некоторых принципов работы с графикой
;Внимание! ! ! Координаты вводить из четырех знаков с ведущими нулями: ;например, для ввода числа 12 вводить 0012, для 123 вводить 0123 и т. д. <5> .486
<6> locals;разрешает применение локальных меток (с префиксом @@) в программе
<7> .model flat, STDCALL ;модель памяти flat,
<8> ;STDCALL - передача параметров в стиле С (справа налево),
<9> ;вызываемая процедура чистит за собой стек
<10>include windowA.Iпс ;включаемый файл с описаниями базовых структур
;и констант Win32
<11> include P「gl6_4.inc ;включаемый файл с определением имен
;пунктов меню и окна диалога
<12>;Объявление внешними используемых в данной программе
;функций Win32 (ASCII):
<13>extrnGetModuleHandleA:PROC
<...> см. исходный текст программы
<55>extrnGetDlgltemTextA:PROC
;видимыми за пределами данного кодаpublic WindowProcpublic Di alogProclpublic DialogProc2.dataXstart dd 0, 0Xend dd 0, 0Ystart dd 0, 0Yenddd 0, 0hwnd dd 0hlnst dd 0memdcdd 0!!!это глобальнаяпеременнаяmaxXdd 0;!! ! это глобальнаяпеременнаяmaxY dd 0;!! ! это глобальнаяпеременнаяjlpVersionlnforniation OSVERSIONINFO <?>wcl WNDCLASSEX <?> message MSG <?> ps PAINTSTRUCT <?> IpRect RECT<?>pt POINT<?>szClassName db 'Приложение W1 n32 * ,0szTi tleName db 'Каркасное приложение Win32 на ассемблере',0MesWindowdb 'Привет! Ну как вампроцесс разработки приложенияна ассемблере?' MesWindowLen=$-MesWindow;звуковые файлы playFileCreatedb'create.wav', 0playFIlePaintdb'paint.wav*, 0playFileDestroydb'destroy.wav',0;имена ресурсов:Ipmenu db "MYMENU", 0w db "IDD_DIALOG1", 0W db "IDDZDIAL0G2", 0 lpdlg3 db "AboutBox". 0 ;переменныедлямакроса show_eaxeedxdd0eecxdd0, 0templatedb '0123456789ABCDEF'MesMsgBoxdb'Отладка (содержимоеeax): ' , 0;видимыми за пределами данного кодаpublic WindowProcpublic Di alogProclpublic DialogProc2.dataXstart dd 0, 0Xend dd 0, 0Ystart dd 0, 0Yenddd 0, 0hwnd dd 0hlnst dd 0memdcdd 0!!!это глобальнаяпеременнаяmaxXdd 0;!! ! это глобальнаяпеременнаяmaxY dd 0;!! ! это глобальнаяпеременнаяjlpVersionlnforniation OSVERSIONINFO <?>wcl WNDCLASSEX <?> message MSG <?> ps PAINTSTRUCT <?> IpRect RECT<?>pt POINT<?>szClassName db 'Приложение W1 n32 * ,0szTi tleName db 'Каркасное приложение Win32 на ассемблере',0MesWindowdb 'Привет! Ну как вампроцесс разработки приложенияна ассемблере?' MesWindowLen=$-MesWindow;звуковые файлы playFileCreatedb'create.wav', 0playFIlePaintdb'paint.wav*, 0playFileDestroydb'destroy.wav',0;имена ресурсов:Ipmenu db "MYMENU", 0w db "IDD_DIALOG1", 0W db "IDDZDIAL0G2", 0 lpdlg3 db "AboutBox". 0 ;переменныедлямакроса show_eaxeedxdd0eecxdd0, 0templatedb '0123456789ABCDEF'MesMsgBoxdb'Отладка (содержимоеeax): ' , 0;описание макрокоманд include show eax.inc;описание макрокоманд include show eax.inc<57><58><59><60><61><62><63><64><65><66><67><68><69><70><71><72><73><74><75><76><77><78><79><80><81><82><83><84><85><86><87><88><89><90><91><92><93><94><95><57><58><59><60><61><62><63><64><65><66><67><68><69><70><71><72><73><74><75><76><77><78><79><80><81><82><83><84><85><86><87><88><89><90><91><92><93><94><95><5б> ;объявление оконной и диалоговых функций объектами,
;видимыми за пределами данного кода
public WindowProc
public Di alogProcl
public DialogProc2
.data
Xstart dd 0, 0
Xend dd 0, 0
Ystart dd 0, 0
Yenddd 0, 0
hwnd dd 0
hlnst dd 0
memdcdd 0!!!это глобальная
переменная
maxXdd 0;!! ! это глобальная
переменная
maxY dd 0;!! ! это глобальная
переменная
jlpVersionlnforniation OSVERSIONINFO <?>
wcl WNDCLASSEX <?> message MSG <?> ps PAINTSTRUCT <?> IpRect RECT<?>
pt POINT<?>
szClassName db 'Приложение W1 n32 * ,
0
szTi tleName db 'Каркасное приложение Win32 на ассемблере',
0
MesWindowdb 'Привет! Ну как вам
процесс разработки приложения
на ассемблере?' MesWindowLen=$-MesWindow
;звуковые файлы playFileCreatedb'create.wav', 0
playFIlePaintdb'paint.wav*, 0
playFileDestroydb'destroy.wav',
0
;имена ресурсов:
Ipmenu db "MYMENU", 0
w db "IDD_DIALOG1", 0
W db "IDDZDIAL0G2", 0 lpdlg3 db "AboutBox". 0 ;переменныедлямакроса show_eax
eedxdd0
eecxdd0, 0
templatedb '0123456789ABCDEF'
MesMsgBoxdb'Отладка (содержимое
eax): ' , 0
;видимыми за пределами данного кода
public WindowProc
public Di alogProcl
public DialogProc2
.data
Xstart dd 0, 0
Xend dd 0, 0
Ystart dd 0, 0
Yenddd 0, 0
hwnd dd 0
hlnst dd 0
memdcdd 0!!!это глобальная
переменная
maxXdd 0;!! ! это глобальная
переменная
maxY dd 0;!! ! это глобальная
переменная
jlpVersionlnforniation OSVERSIONINFO <?>
wcl WNDCLASSEX <?> message MSG <?> ps PAINTSTRUCT <?> IpRect RECT<?>
pt POINT<?>
szClassName db 'Приложение W1 n32 * ,
0
szTi tleName db 'Каркасное приложение Win32 на ассемблере',
0
MesWindowdb 'Привет! Ну как вам
процесс разработки приложения
на ассемблере?' MesWindowLen=$-MesWindow
;звуковые файлы playFileCreatedb'create.wav', 0
playFIlePaintdb'paint.wav*, 0
playFileDestroydb'destroy.wav',
0
;имена ресурсов:
Ipmenu db "MYMENU", 0
w db "IDD_DIALOG1", 0
W db "IDDZDIAL0G2", 0 lpdlg3 db "AboutBox". 0 ;переменныедлямакроса show_eax
eedxdd0
eecxdd0, 0
templatedb '0123456789ABCDEF'
MesMsgBoxdb'Отладка (содержимое
eax): ' , 0
;описание макрокоманд include show eax.inc
;описание макрокоманд include show eax.inc
<57>
<58>
<59>
<60>
<61>
<62>
<63>
<64>
<65>
<66>
<67>
<68>
<69>
<70>
<71>
<72>
<73>
<74>
<75>
<76>
<77>
<78>
<79>
<80>
<81>
<82>
<83>
<84>
<85>
<86>
<87>
<88>
<89>
<90>
<91>
<92>
<93>
<94>
<95>
<57>
<58>
<59>
<60>
<61>
<62>
<63>
<64>
<65>
<66>
<67>
<68>
<69>
<70>
<71>
<72>
<73>
<74>
<75>
<76>
<77>
<78>
<79>
<80>
<81>
<82>
<83>
<84>
<85>
<86>
<87>
<88>
<89>
<90>
<91>
<92>
<93>
<94>
<95>
Листинг 16.13 (продолжение)
<96> sim4 to_EAXbiп macro sim4:req
<97>
local
ml
<98>
push
eax
<99>
push
ebx
<100>
push
ecx
<101>
mov
ebx, 1
<102>
mov
eax, si ni4
<103>
bswap
eax
<104>
mov
sim4, 0
<105>
push
eax
<10б>
mov
ecx, 4
<107>
ml:
and
eax, 0fh
<108>
imul
eax, ebx
<109>
imul
ebx, 10
<110>
add
sim4, eax
<111>
pop
eax
<112>
shr
eax, 8
<113>
push
eax
<114>
loop
ml
<115>
pop
eax
<116>
pop
ecx
<117>
pop
ebx
<118>
pop
eax
<119>
endm
120> .code
121> startproc near
122>точка входа в программу:
123>начало стартового кода
124>
125>конец стартового кода
12б> WinMain:
<127> определить класс окна ATOM:Regi sterClassEx(CONST WNDCLASSEX
<128>;*lpWndClassEx) , где *lpWndClassEx - адрес структуры WndClassEx
<129>
<130>
call
RegisterClassExA
<131>
test
ax, ax;проверить на успех регистрации класса окна
end_cycl_msg;неудача
<132>
jz
<133>
создаем окно:
<134>
<135>
call
CreateWi ndowExA
<13б>
mov
hwnd, eax;hwnd - дескриптор окна
<137>
показать окно:
<138>
<139>
call
ShowWindow
<140>
перерисовываем содержимое окна
<14i>
<142>
call
UpdateWi ndow
<143>
запускаем
цикл сообщений:
<144>
<145>
cycljnsg:
<146>
<147>
call
GetMessageA
<148>
cmp
ax, 0
end_cycl_msg
<149>
<150>
трансляция ввода с клавиатуры
<151>
<152>
call
TranslateMessage
<153>
<154>
отправляем
сообщение оконной процедуре
<155>callDispatchMessageA
<156>jmpcycl msg
<157> end_cycl_msg:-
Углубленное программирование на ассемблере для Win32427
<158>
<159>
<160>
<161>
<162>
<163>
<164>
<165>
<166>
<167>
<168>
<169>
<170>
<171>
<172>
<173>
<174>
<175>
<176>
<177>
<178>
<179>
<180>
<181>
<182>
<183>
<184>
<185>
<186>
<187>
<188>
<189>
<190>
<191>
<192>
<193>
<194>
<195>
<196>
<197>
<198>
<199>
<200>
<201>
<202>
<203>
<204>
<205>
<206>
<207>
<208>
<209>
<210>
<211>
<212>
<213>
<214>
<215>
<21б>
<217>
<218>
<219>
<220>
<221>
из приложения ;...
callExitProcess
start ;-
WindowProc proc arg uses ebx,ebxрегистры обязательно должны сохраняться
local
cmp @@mes, WM_DESTROY je
WM_CREATE
je cmp je cmp je default wmcreate:
создание растрового изображения, совместимого с окном приложения
обозначаем создание окна звуковым эффектом
callPlaySoundA
возвращаем значение 0 eax, jmp wmpalnt:-
перерисовку окна звуковым эффектом
виртуального окна в реальное окно
wmdestroy:
;удалить виртуальное окно DeleteDC(memdc)
; послать сообщение
wmcommand:
процедуры обработки сообщений от меню
;MenuProc (DWORDDWORD @@wparam)
push push call MenuProc jmp default: обработка по умолчанию
jmp
exi t_wndproc: ret
WindowProc endp ; -MenuProc
сообщений от меню
MenuProcproc arg useseax, ebx mov ebx,;в Ьх идентификатор меню
cmp bx, je @@idmdrawtext cmp bx, jeл
продолжение
Листинг
16.13 (продолжение)
<222>
cmp bx, IDM_LENGTH
<223>
je @@idmlength
<224>
cmp bx, IDM_RECTANGLE
<225>
je @@i dmrectangle
<226>
cmp bx, IDM_PEACOCK
<227>
je @@idmpeacock
<228>
cmp bx, IDM_LACES
<229>
je@@idmlaces
<230>
cmp bx, IDM_ABOUT
<231>
je @@idmab6ut
<232>
jmp @@exit
<233>
@@idmdrawtext:
<234>
;получим размер рабочей области BOOL GetClientRect (HWND hWnd,
;LPRECT IpRect);
<235>
pushoffset IpRect
<236>
push @@hwnd
<237>
callGetClientRect
<238>
выводим строку текста в окно int DrawText(HDC hDC,
LPCTSTR IpString, int nCount,
<239>
LPRECT IpRect, UINT uFormat) ;
<240>
pushDT_SINGLELINE+DT_BOTTOM
<241>
pushoffset IpRect
<242>
push-1
<243>
pushoffset @@TXT_DRAWTEXT
<244>
pushmemdc-
<245>
callDrawTextA
<246>
;генерация сообщения WM_PAINT для вывода строки на
экран
<247>
push 1
<248>
push NULL
<249>
push @@hwnd
<250>
callInvalidateRect
<251>
jmp@@exi t
<252>
@@i dmtextout:
<253>
;выводим строку текста в окно BOOL TextOut(HDC hdc,
int nXStart
<254>
;intnYStart, LPCTSTR IpString, int cbString) ;
<255>
I
<256>
callTextOutA
<257>
;генерация сообщения WM—PAINT для вывода строки на
экран
<258>
push 0
<259>
push NULL
<260>
push@@hwnd
<261>
call InvalidateRect
<262>
jmp@@exi t
<263>
@@i dmlength:
<264>
;вызываем окно диалога int DialogBoxParam(HINSTANCE
hlnstance,
;LPCTSTR IpTemplateName,
<265>
;HWND hWndParent, DLGPROC IpDialogFunc , LPARAM dwlni tParam)
<266>
push 0
<267>
pushoffset DialogProcl
<268>
push@@hwnd
<269>
pushoffset Ipdlgl
<27Q>
push hlnst
<271>
callDi alogBoxParamA
<272>
[установить текущую точку BOOL MoveToEx(HDC hdc, int X, int Y,
;LPPOINT IpPoi nt)
<273>
pushNULL
<274>
push Ystart
<275>
pushXstart
<27б>
pushmemdc
<277>
callMoveToEx
<278>
;вывод линии BOOL LineTo(HDC hdc, int nXEnd, int nYEnd)
<279>
pushYend
Углубленное программирование на ассемблере для Win32429
<280>
<281>
<282>
<283>
<284>
<285>
<286>
<287>
<288>
<289>
<290>
<291>
<292>
<293>
<294>
<295>
<296>
<297>
<298>
<299>
<300>
<301>
<302>
<303>
<304>
<305>
<306>
<307>
<308>
<309>
<310>
<311>
<312>
<313>
<314>
<315>
<316>
<317>
<318>
<319>
<320>
<321>
<322>
<323>
<324>
<325>
<326>
<327>
<328>
<329>
<330>
<331>
<332>
<333>
<334>
<335>
<336>
<337>
<338>
<339>
<340>
<341>
<342>
<343>
pushXend■
pushmemdc
callLineTo
;генерация сообщения WM_PAINT для вывода строки на экран push0
pushNULL
push@@hwnd
callInvalidateRect
jmp@@exi t
@@i dmrectangle:
;вызываем окно диалога push0
pushoffset DialogProc2
push@@hwnd
pushoffset 1Pdlg2
pushhlnst
callDi alogBoxParamA
;вывод прямоугольника BOOL Rectangle(HDC hdc, int nLeftRect, ;1 nt nTopRect, int nRightRect, int nBottomRect)
pushYstart
popeax
pusheax
show_eax pushXstart
popeax
pusheax
show_eax pushYend
popeax
pusheax
show_eax pushXend
popeax
pusheax
show eax pushmemdc
callRectangle
;генерация сообщения WM_PAINT для вывода строки на экран push0
pushNULL
push@@hwnd
callInvalidateRect
jmp@@exi t
@@idmpeacock:
pushMB_ICONINFORMATION+MB_OK
pushoffset szTi tleName ~
pushoffset @@TXT_PEACOCK
push@@hwnd-
callMessageBoxA
jmp@@exi t
@@idmlaces:
pushMB_ICONINFORMATION+MB_OK
pushoffsetszTitleName
pushoffset@@TXT_LACES
push@@hwnd-
callMessageBoxA
jmp@@exi t
@@idmabout: ;вызываем окно диалога push0
pushoffsetAboutDi alog
push@@hwnd
pushoffsetlpdlg3
pushhlnst
1продолжение 田
Листинг 16.13(продолжение)
<344>
call
Di alogBoxParamA
<345>
jmp
@@exit
<34б>
@@exi t:
<347>
<348>
mov eax,
0
<349>
ret
<350>
@@TXT ABOUT
db'IDM ABOUT', 0
<351>
@@TXT LACES
db'IDM LACES', 0
<352>
@@TXT_PEACOCK
db'IDM PEACOCK',
01
<353>
@@TXT TEXTOUTdb'Текст выведен
функцией TEXTOUT
<354>
lenTXT TEXTOUT=$-@@TXT TEXTOUT
<355>
@@TXT DRAWTEXT db'Текст выведен
функцией DRAWTEXT', 0
<356>
MenuProcendp
<357>
Пi л 1 Pгпг 1
DialogProcl
VltlLU^FiUUX ..
<358>
proc
<359>
arg @@hdlg:DWORD, @@message:DWORD,
@@wparam:DWORD, @@lparam:DWORD
<360>
uses eax, ebx, edi, esi
<361>
mov eax,
@@message
<362>
cmp ax,
WM INITDIALOG
<363>
je @@wminitdialog
<364>
cmp ax,
WM COMMAND
<365>
jne @@exit_false
<366>
mov ebx,
@@wparam;B bx идентификатор элемента управления
<367>
cmp bx,
IDOK
<368>
i e @@idok
<369>
cmp bx,
IDCANCEL
<370>
je @@i dcancel
<371>
jmp @@exi t false
<372>
@@wminitdialog:
<373>
jmp @@exit true
<374>
@@idok:
<375>
;читаем Xstart UINT GetDlgItemText(HWND hDlg, int nlDDlgltem,
<376>
;LPTSTR IpString, int nMaxCount);
<377>
push
5
<378>
push
offset Xstart
<379>
push
IDC_EDIT1
<380>
push
@@hdlg
<381>
call
GetDlgltemTextA
<382>
push
MB_ICONINFORMATION+MB_
OK
<...>
<387>
sim4 to
EAXbin Xstart
<388>
;читаем Ystart
<389>
push5
<390>
push
offset Ystart
<391>
push
IDC_EDIT2
<392>
push
@@hdlg
<393>
call
GetDlgltemTextA
<...>
<399>
sim4 to
EAXbin Ystart
<400>
;читаем Xend
<401>
push
5
<402>
push
offset Xend
<403>
push
IDC_EDIT3
<404>
push
@@hdlg
<405>
call
GetDlgltemTextA
<...>
<411>
sim4 to
EAXbin Xend
<412>
;читаем Yend
<413>
push
5
<414>
push
offset Yend
<415>
push
IDC_EDIT4
<416>
push
@@hdlg
<417> < . . . > <423> <424><425> <426> <427> <428> <429> <430> <431> <432> <433> <434> <435> <436> <437> <438> <439> <440> <441><442><443> <444> <445> <446> <447> <448> <449> <450> <451> <452> <453> <454> <455> <456> <457> <458> <459> <460> <461> <462> <463><464> <...> <470> <471> <472> <473> <474> <475> <476> <...> <482> <483> <484> <485> <486> <487> <488> <...> <494> <495> <496><417> < . . . > <423> <424><425> <426> <427> <428> <429> <430> <431> <432> <433> <434> <435> <436> <437> <438> <439> <440> <441><442><443> <444> <445> <446> <447> <448> <449> <450> <451> <452> <453> <454> <455> <456> <457> <458> <459> <460> <461> <462> <463><464> <...> <470> <471> <472> <473> <474> <475> <476> <...> <482> <483> <484> <485> <486> <487> <488> <...> <494> <495> <496>call GetDlgltemTextAcall GetDlgltemTextAsim4_to_EAXbi n Yend
<417> < . . . > <423> <424>
<425> <426> <427> <428> <429> <430> <431> <432> <433> <434> <435> <436> <437> <438> <439> <440> <441>
<442>
<443> <444> <445> <446> <447> <448> <449> <450> <451> <452> <453> <454> <455> <456> <457> <458> <459> <460> <461> <462> <463>
<464> <...> <470> <471> <472> <473> <474> <475> <476> <...> <482> <483> <484> <485> <486> <487> <488> <...> <494> <495> <496>
<417> < . . . > <423> <424>
<425> <426> <427> <428> <429> <430> <431> <432> <433> <434> <435> <436> <437> <438> <439> <440> <441>
<442>
<443> <444> <445> <446> <447> <448> <449> <450> <451> <452> <453> <454> <455> <456> <457> <458> <459> <460> <461> <462> <463>
<464> <...> <470> <471> <472> <473> <474> <475> <476> <...> <482> <483> <484> <485> <486> <487> <488> <...> <494> <495> <496>
call GetDlgltemTextA
call GetDlgltemTextA
push0
push@@hdlg
callEndDi alog
jmp@@exit_true
@@idcancel:
pushNULL
push@@hdlg
callEndDialog
jmp@@exi t_true
@@exit_false:
mov eax, 0
ret
@@exi t_true:
mov eax, 1
ret
Di alogProcl endp ;
DialogProc2 proc
arg @@hdlg:DWORD, @@message:DWORD, @@wparam:DWORD, @@lparam:DWORD uses eax, ebx, edi, esi
mov eax, @@message
cmp ax, WM_INITDIALOG
je @@wmini tdialog
cmp ax, WM_COMMAND jne @@exit_false
mov ebx, @@wparam;B bx идентификатор элемента управления cmp bx, IDOK je @@idok
cmp bx, IDCANCEL
je @@idcancel
jmp @@exi t_false
@@wmi ni tdi alogT
jmp @@exi Jtrue
@@idok:I
;читаем Xstart UI NT GetDlgltemText(HWND hDlg, int nlDDlgltem, ;LPTSTR IpString, int nMaxCount);
push5
pushoffset Xstart
pushIDC_EDIT1
push@@h『lg
push push push push call5offset YstartIDC_EDIT2@@hdlgGetDlgltemTextAsim4_to_EAXbin Ystart читаем Xendpush5pushoffset XendpushIDC_EDIT3push@@hdlgcallGetDlgltemTextAsim4_to_EAXbin Xendpush push push push call5offset YstartIDC_EDIT2@@hdlgGetDlgltemTextAsim4_to_EAXbin Ystart читаем Xendpush5pushoffset XendpushIDC_EDIT3push@@hdlgcallGetDlgltemTextAsim4_to_EAXbin Xendsim4 to EAXbin Xstartsim4 to EAXbin Xstart;читаем Ystart;читаем Ystart;читаем Yendpush 5;читаем Yendpush 5callGetDlgltemTextA
push push push push call
5
offset Ystart
IDC_EDIT2
@@hdlg
GetDlgltemTextA
sim4_to_EAXbin Ystart читаем Xend
push5
pushoffset Xend
pushIDC_EDIT3
push@@hdlg
callGetDlgltemTextA
sim4_
to
_EAXbin Xend
push push push push call
5
offset Ystart
IDC_EDIT2
@@hdlg
GetDlgltemTextA
sim4_to_EAXbin Ystart читаем Xend
push5
pushoffset Xend
pushIDC_EDIT3
push@@hdlg
callGetDlgltemTextA
sim4_
to
_EAXbin Xend
sim4 to EAXbin Xstart
sim4 to EAXbin Xstart
;читаем Ystart
;читаем Ystart
;читаем Yend
push 5
;читаем Yend
push 5
Листинг 16.13(продолжение)
<497>
push
offset Yend
<498>
push
I DC EDIT4.
<499>
push
@@hdlg .
<500>
call
GetDlgltemTextA
<...> <506>
sim4 to
EAXbi n Yend
<507>
push
NULL
<508>
push
@@hdlg
<509>
call
EndDialog
<510>
jmp
@@exit true
<511>
@@idcancel:
<512>
push
NULL
<513>
push
@@hdlg
<514>
call
EndDialog
<515>
jmp
@@exit true
<516>
@@exit_false:
<517>
mov
eax, 0
<518>
ret
<519>
@@exit_true
<520>
mov eax
,: 1
<521>
ret
<522>
DialogProc2
endp
<523>
-
<524>
AboutDialog proc
<525>
arg @@hdlg:I
WORD, @@message:DWORD, @@wparam:DWORD, @@lparam:DWORD
<526>
uses eax, ebx, edi, esi
<527>
mov eax
, @@message
<528>
cmp ax,
WM_INITDIALOG
<529>
je @@wmi ni tdi alog
<530>
cmp ax,
WM COMMAND
<531>
jne @@exit false
<532>
mov'ebx,
@@wparam;e bx идентификатор элемента управления
<533>
cmp bx,
I DOK
<534>
je @@i dok
<535>
jmp @@exit false
<536>
@@wminitdialog:,
<537>
jmp @@exit true
<538>
@@i dok:
<539>
push
NULL
<540>
push
@@hdlg
<541>
call
EndDialog
<542>
jmp
@@exit true
<543>
@@exi t false:
<544>
mov eax
, 0
<545>
ret
<546>
@@exit true:
<547>
mov eax
, 1
<548>
ret
<549>
AboutDialog
endp
<550>
end start
Окна диалога, имеющие большое количество элементов управления, должны, соответственно, иметь диалоговые процедуры, обрабатывающие сообщения от этих элементов. В нашем примере определены два окна диалога, внешне очень похожие друг на друга. С их помощью пользователь может задавать координаты начала и конца отрезка и углов прямоугольника. На рис. 16.3 приведен вид окна приложения с окном диалога для задания координат прямоугольника.
При задании координат пользователь сам должен контролировать правильность ввода данных, так как алгоритм программы не предусматривает их проверки. Пра-
蹩*^宏攵&躋咏出箓镂U期^%蹩*^宏攵&躋咏出箓镂U期^%涉ss>M™?sr?i^sxB涉ss>M™?sr?i^sxBYYYY麻2Ш!Ш驾麻2Ш!Ш驾CancelCancel■i■i|0300|0250||0300|0250|]ОО1О|100201]ОО1О|100201выведенвыведенLelt_Top: XLelt_Top: X_Right_BotSm:X_Right_BotSm:X修沪ХМ4Ц|Т"(13и«11修沪ХМ4Ц|Т"(13и«11楚经密巒經辞'^灰■縱腐■вам楚经密巒經辞'^灰■縱腐■вам
蹩*^宏攵&躋咏出箓镂U期^%
蹩*^宏攵&躋咏出箓镂U期^%
涉ss>M™?sr?i^sxB
涉ss>M™?sr?i^sxB
Y
Y
Y
Y
麻2Ш!Ш驾
麻2Ш!Ш驾
Cancel
Cancel
■i
■i
|0300
|0250|
|0300
|0250|
]ОО1О|
100201
]ОО1О|
100201
выведен
выведен
Lelt_Top: X
Lelt_Top: X
_Right_BotSm:
X
_Right_BotSm:
X
修沪ХМ4Ц|Т"(13и«11
修沪ХМ4Ц|Т"(13и«11
楚经密巒經辞'^灰■縱腐■
вам
楚经密巒經辞'^灰■縱腐■
вам
Текст выведен функцией
Рис.Окно диалога для задания координат прямоугольника
значения, вводимые в каждое из полей ввода, должны содержать все четыре десятичные цифры с включением, при необходимости, ведущих нулей
Для обработки сообщений от этих окон диалога программа(см. листинг 16.13) содержит две диалоговые процедуры,и DialogProc2. Процедура(строки 357-439) предназначена для ввода координат отрезка,
процедура DialogProc2 (строки 440-522) — координат прямоугольника. Структура диалоговой процедуры аналогична структуре оконной процедуры. В начале диалоговой процедуры находится код, определяющий тип поступившего сообщения и в зависимости от него передающий управление в определенную точку диалоговой
eax, cmp ax, je cmp ax, jne mov ebx,bx идентификатор элемента управления
cmp bx, je cmp bx, IDCANCEL je jmp @@exit_false
Главное сообщение, поступающее в оконную процедуру, — сообщение Именно оно несет информацию о действиях пользователя с элементами управления окна диалога. Так, в строках 361-371 листинга 16.13, обрабатываются два сообщения:иСообщение
приходит в диалоговую процедуру один раз. Этов процессе инициа
лизации окна диалога перед его появлением в окне приложения. Далее в
вую процедуру поступает последовательность сообщений WM/OMMAND. Параметр wParam такого сообщения содержит идентификатор того элемента управления, над которым пользователь произвел некоторое действие, например, щелкнул на кнопке окили Cancel (см. рис. 16.3). Обратите внимание на то, что при описании элементов различных окон диалога используются одни и те же идентификаторы. Более того, в файле prgl6_4.h этим идентификаторам назначены одни и те же константы. Именно их значения передаются в структуре сообщения для идентификации конкретного элемента окна диалога. Одинаковые значения не вносят никакой путаницы в работу приложения, так как для каждого окна диалога существует своя оконная процедура.
Интерес представляют детали реализации диалоговых процедур DialogProcl и DialogProc2. В них используются две отладочные макрокоманды, sim4_to_ EAXbin и show_eax.
Макрокоманда sim4_to_EAXbin в программе prgl6_4.asm предназначена для преобразования строки из четырех символов десятичных цифр в эквивалентное двоичное число, помещаемое в регистр ЕАХ. В данной программе эта макрокоманда требуется для преобразования символьных строк, считываемых из полей ввода окна диалога. Эти символьные строки логически представляют собой координаты отрезка и прямоугольника. Преобразованные с помощью макрокоманды sim4_ to_EAXbin значения этих координат используются функциями Win32 API MoveToEx (строки 272-277), LineTo (строки 278-282) и Rectangle (строки 297-316). В конце своей работы макрокоманда sim4_to_EAXbinс помощью окна, выводимого функцией MessageBox, сообщает о результатах преобразования. Это отладочный момент работы, и, учитывая учебный характер разрабатываемого приложения, он не убран из его конечной версии.
Макрокоманда show_eax также используется для отладки и является аналогом макрокоманды show, описанной в главе 14. Попытка запустить макрокомандуshow в нашем приложении ни к чему хорошему не приведет, поэтому для динамического контроля за работой программы макрокоманда show_eax может оказаться очень полезной. Кстати, проблема отладки Windows-приложений довольно актуальна, и для ее решения подчас приходится применять самые нетрадиционные подходы, но это уже тема для отдельного разговора.
Активизация окна диалога
Для того чтобы отобразить окно диалога на экране и выполнить с его помощью некоторую работу, используетсяфункцияDialogBoxParamA. Этафункция имеет следующий формат (в нотации C/C++):
int Di alogBoxParam (HINSTANCE hlnstance,//дескриптор приложения
LPCT5TR IpTemplateName, //указатель на строку с заголовком окна
HWND hWndParent,//дескриптор окна
DLGPROC IpDi alogFunc, //указатель на диалоговую процедуру
LPARAM dwlnitParam) //значение, передаваемое в диалоговую функцию //через IParam
В программе prgl6_4.asni (см. листинг 16.13) обращение к этой функции производится трижды при выборе соответствующих пунктов меню (строки271,296и 344 в теле процедуры MenuProc). С ее помощью в диалоговую процедуру передается 32-разрядное значение, которое извлекается посредством параметра IParam.
По окончании работы окно диалога должно быть закрыто функцией EndDialog, имеющей формат
BOOL EndDialog (HWND hDIg, //дескриптор окна диалога
int nResult); //возвращаемое значение
Обычнозакрытиеокна производится щелчком на кнопке ОК или Cancel.
Большой размер исходного текста приложения P「gl6_4.asm может кого-то испугать. Он может быть существенно сокращен за счет различных средств ассемблера, в том числе макрокоманд и процедур. Если с их помощью реорганизовать исходный текст, то он станет гораздо читабельнее и удобнее для использования, почти как аналогичный текст на языке C/C++. Однако в отличие от текста на C/C++ текст на ассемблере — готовая машинная программа, в которую ничего лишнего не добавляется и код которой полностью контролируется. Более того, с этим текстом можно проводить весь комплекс мероприятий по его оптимизации. В нашем примере намеренно ничего не сделано в плане улучшения внешнего вида исходного текста, чтобы не навязывать читателю свои подходы к этому процессу, тем более что это могло бы ввести его в заблуждение. Текст программы в настоящем виде более всего отражает суть процессов, происходящих в системе Windows во время исполнения приложения, и механизм взаимодействия приложения с системой.
Нами остались не разработанными варианты реакции приложения на выбор пользователем пунктов меню Графика ► Эффекты ► Павлин и Графика ► Эффекты ► Кружева. Их реализация требует использования команд сопроцессора и поэтому будет рассмотрена в следующей главе.
В начале главы отмечалось, что Windows поддерживает работу двух типов приложений — оконных, в полной мере использующих все достоинства графического интерфейса, и консольных, работающих в текстовом режиме. Большая часть данной главы была посвящена разработке программ с оконным интерфейсом. Заключительную часть главы посвятим рассмотрению основ программирования консольных приложений.
Программирование консольных Windows-приложений
Язык ассемблера — язык системных программистов, исследователей принципов работы операционных систем, программ и аппаратных средств. Здесь не всегда нужны красивые графические оболочки, а наоборот, велика потребность в удобных средствах для работы с текстовой информацией. Операционная система Windows обеспечивает встроенную поддержку консолей, которые, по определению, являются интерфейсами ввода-вывода для приложений, работающих в текстовом режиме. Понятие «консоль» существует в вычислительной технике давно. В общем случае под «консолью» подразумевают текстовый терминал для управления компьютером. Видимая часть такого терминала — клавиатура (для ввода управляющих воздействий) и монитор (как средство отображения-реакции вычислительной системы). В Windows консоль представляет собой приложение, которое позволяет взаимодействовать с операционной системой посредством ввода текстовых
команд. Такой способ управления компьютером позволяет решать в основном административные задачи. Приложение, с помощью которого поддерживается этот режим, называется консольным. Видимая часть консольных приложений называется окном консольного приложения.
Написание консольных приложений на ассемблере — задача более актуальная, чем написание оконных. Причина простая — малыми затратами нам становятся доступны практически все возможности Win32 API. Программист может запустить одновременно нескольких консольных приложений и при этом работать с мышью и клавиатурой в стиле Windows. Далее мы рассмотрим порядок действий для запуска консольного Windows-приложения и организацию обмена данными с ним.
API Win32 предоставляет два разных уровня работы с консолью — высокий и низкий. Выбор нужного уровня зависит от того, какая степень гибкости и полноты контроля требуется приложению для обеспечения своей работы с консолью. Функции высокого уровня обеспечивают простоту процесса ввода-вывода за счет использования стандартных дескрипторов ввода-вывода, но при этом невозможен доступ к входному и экранным буферам консоли. Функции низкого уровня требуют учета большего количества деталей и написания большего объема кода, но это компенсируется большей гибкостью.
Консоль состоит из одного входного и нескольких экранных буферов. Входной буфер представляет собой очередь, каждая запись которой содержит информацию относительно отдельного входного события консоли. Экранный буфер — двухмерный массив, содержащий символы, выводимые в окно консоли, и данные об их цвете.
Очередь входного буфера содержит информацию о следующих событиях: и нажатии и отпускании клавиш;
後 манипуляциях мышью — движение, нажатие и отпускание кнопок;
II изменении размера активного экранного буфера, состоянии прокрутки.
С каждой консолью связаны две кодовые таблицы — по одной для ввода и вывода. Консоль использует входную кодовую таблицу для трансляции ввода с клавиатуры в соответствующие символьные значения. Аналогичным образом используется кодовая таблица вывода — для трансляции символьных значений, формируемых различными функциями вывода, в символы, отображаемые в окне консоли. Для работы с кодовыми таблицами приложение может задействовать пары функций: SetConsoleCP и GetConsoleCP — для входных кодовых таблиц и SetConsoteOutputCP иGetConsoleOutputCP —для выходных кодовых таблиц. Идентификаторы кодовых таблиц, доступные на данном компьютере, сохраняются в системном реестре следующим ключом:
HKEY_L0CAL_MACHINE\\SYSTEM\\CurrentControl5et\\Control\\Nls\\CodePage
Для поддержки консольных приложений Win32 API содержит более сорока функций, предназначенных для интеграции в среду Windows программ, работающих в текстовом режиме. Данные функции обеспечивают поддержку отмеченных ранее двух уровней доступа к консоли — высокого и низкого. Консольные функции ввода высокого уровня позволяют приложению извлечь данные, полученные при вводе с клавиатуры и сохраненные во входном буфере консоли. Консольные функции вывода высокого уровня позволяют приложению записать данные в уст-
ройство стандартного вывода или в устройство ошибки с тем, чтобы отобразить этот текст в экранном буфере консоли. Функции высокого уровня также поддерживают переназначение стандартных дескрипторов ввода-вывода и управление режимами работы консоли. Консольные функции низкого уровня позволяют приложениям получить детальную информацию о вводе с клавиатуры, событиях нажатия и отпускания кнопок мыши и о манипуляциях пользователя с окном консоли. Все это обеспечивает высокую степень контроля над выводом данных на экран. Высокоуровневый и низкоуровневый консольный ввод-вывод не являются взаимоисключающими, и приложение может использовать любую комбинацию этих функций.
Минимальная программа консольногоприложения
Минимальная программа консольного приложения на ассемблере выглядит так, как показано в листинге 16.14.
Листинг16.14.Фрагменты приложения prg16_5.asm
;prgl6_5.asm.
;Примерминимальной программы консольного Windows-приложения .486
.model flat,STDCALL;модель памяти flat
include Wi ndowConA.i nc
;0бъявление внешними используемых в данной программе функций Win32 (ASCII): extrn AUocConsole: PROC
extrn SetConsoleTi tleA:PROC
extrn ExitProcess:PROC
.data
TitleTextdb 'Win32-console application',0
.code
startproc near;точка входа в программу:
;запрос консоли
callAllocConsole
;проверить успех запроса консоли testeax.eax
jz exitнеудача
;выведем заголовок окна консоли SetConsoleT1 tie:
pushoffset TitleText
callSetConsoleTi tleA
;проверить успех вывода заголовка
testeax,eax
jz exit;неудача
;работаем
exit:;выход из приложения
;готовим вызов VOID ExitProcess(UINT uExitCode)
push0
callExitProcess
startendp
end start
Если убрать комментарии, то кода будет совсем немного. В нем представлены вызовы трех функций: ALLocConsoLe, SetConsoLeTitle, ExitProcess.
Первой функцией консольного приложения должна быть функция запроса консоли AllocConsole:
BOOLAUocConsole(VOID);
Для вызова функции AllocConsole не требуется никаких параметров. В случае успеха функция AllocConsole возвращает ненулевое значение, при неудаче — нуль. Выделенная консоль представляет собой типичное для Windows окно. Процесс в конкретный момент времени может использовать одну консоль. Если ему нужно запустить еще одну консоль, то прежняя должна быть закрыта или освобождена с помощью функции FreeConsole:
BOOL FreeConsole(VOID);
В случае успеха функция FreeConsole возвращает ненулевое значение, при неудаче — нуль.
При завершении процесса выделенная процессу консоль освобождается автоматически. В нашем случае использован именно этот вариант закрытия консоли — функцией ExitProcess:
VOID ExitProcess(UINT uExitCode);
Функции ExitProcess передается код завершения процесса и всех завершаемых цепочек в этом процессе. Проанализировать этот код можно с помощью функций GetExitCodeProcess и GetExitCodeThread. В общем случае в различных ветвях кода может быть несколько точек выхода с вызовом функции ExitProcess. Задавая различные значения кода завершения, можно идентифицировать причину завершения процесса.
Окно консоли может иметь заголовок, для отображения которого предназначена функция SetConsoleTitle:
BOOL SetConsoleTi tle(LPCTSTRIpConsoleTitle);
У функции SetConsoleTitle один параметр — указатель на строку с заголовком консоли, заканчивающуюся нулем.
Организация высокоуровневого консольного ввода-вывода
Для высокоуровневого ввода-вывода приложение может использовать файловые функции ReadFile и Write File, а также функции консольного ввода-вывода ReadConsole и WriteConsole. Эти функции обеспечивают косвенный доступ к входному и экранным буферам пульта. Физически эти функции фильтруют записи входного буфера консоли так, чтобы возвратить ввод как поток символов, игнорируя все другие записи с расширенной информацией о мыши, клавиатуре и изменении размеров окна консоли. Отфильтрованный поток символов отображается в окне консоли, начиная с текущей позиции курсора. Существуют два важных различия в использовании пар функций ReadFile\WriteFile и ReadConsole\WriteConsole.
я Поддержка символов Unicode и ANSI. Консольные функции (ReadConsole\ WriteConsole) поддерживают эти наборы, а файловые (ReadFiLe\WriteFile) — нет.
Функции файлового ввода-вывода могут использоваться для обращения как к файлам, так и к именованным каналам (устройствам, присоединенным к последовательному интерфейсу). Консольные функции ввода-вывода можно использовать только с тремя дескрипторами стандартного ввода-вывода (см. далее описание функций ReadConsoLe\WriteConsole).
Из сказанного ранее следует, что функции высокоуровневого ввода-вывода обеспечивают простой способ обмена (чтения-записи) потоков символов с консолью.
Операция чтения высокого уровня реализуется функцией ReadConsole, которая получает входные символы из буфера ввода консоли и сохраняет их в указанном буфере:
BOOL ReadConsole(HANDLE hConsolelnput, LPVOID IpBuffer,
DWORD nNumberOfCharsToRead, LPDWORD IpNumberOfCharsRead, LPVOID IpReserved);
Параметры этой функции означают следующее:
hConsolelnput — дескрипторвходного потокаконсоли;
滋 IpBuffer — указатель на строку, в которую будет записана вводимая строка символов;
nNumberOfCharsToRead—размербуфера,указанногоIpBuffer;
議 IpNumberOfCharsRead —количестводействительновведенныхсимволов;
IpReserved — этот параметр не используется, поэтому должен задаваться как NULL
Операция записи высокого уровня реализуется функцией WriteConsoLe, которая извлекает символы из указанного буфера и записывает их в экранный буфер, начиная с текущей позиции курсора и продвигая ее по мере записи символов.
BOOL WriteConsole(HANDLE hConsoleOutput, CONST VOID *lpBuffer,
DWORD nNumberOfCharsToWri te, LPDWORD IpNumberOfCharsWri tten, LPVOID IpReserved);
Параметры этой функции означают следующее:
際 hConsoleOutput — дескриптор выходного потока консоли;
森 IpBuffer — указатель на выводимую строку;
nNumberOfCharsToWrite —размербуфера,указанногоIpBuffer;
缀 IpNumberOfCharsWritten — количество действительно выведенных символов;
IpReserved — этот параметр не используется, поэтому должен задаваться как NULL
Для своей работы эти и некоторые другие консольные функции требуют получения стандартных дескрипторов ввода-вывода. Значения этих дескрипторов присваиваются параметрам hConsolelnput и hConsoleOutput. По умолчанию стандартный дескриптор ввода связан с клавиатурой, стандартный дескриптор вывода — с экраном. Получить стандартный дескриптор ввода-вывода можно с помощью функции GetStdHandle.
HANDLE GetStdHandle(DWORD nStdHandle):
На вход функции GetStdHandle должно быть подано одно из следующих значений:
綴 STDJNPUT_HANDLE = -10 — дескриптор стандартного входного потока;
桀 STD^OUTPUT.HANDLE- -11 — дескриптор стандартного выходного потока;
藻 STD_ERROR_HANDLE = -12 — дескриптор стандартного потока ошибок.
Используя функции высокоуровневого ввода-вывода, приложение может управлять цветами текста и фона, которыми должны отображаться символы, записываемые в экранный буфер. Приложению доступны следующие функции высокоуровневого консольного ввода-вывода:
:戀: эхо-контроль вводимых символов на экране из активного экранного буфера;
ввод строки, окончание операции чтения которой происходит при нажатии клавиши Enter;
№ автоматическая обработка некоторых символов, вводимых с клавиатуры: перевода каретки, нажатия клавиши т. д.;
si автоматическая обработка некоторых символов, выводимых на экран: перевода строки и каретки, возврата на один символ и т. д.
Функцияпредназначена для указания позиции, с кото
рой начинается выполнение операций чтения-записи в окно консоли: BOOL
COORD;
Параметрами этой функции являются стандартный дескриптор вывода hConsole- Output, полученный функциейи указатель на структуру COORD с ко
ординатами новой позиции курсора: COORD struc х0
у ends
По умолчанию цветовое оформление окна консоли довольно унылое — черный фон, белый текст. Внести разнообразие во внешний вид окна консоли поможет функция SetConsoleTextAttribute, с помощью которой можно изменить цвета, установленные по умолчанию для текста и фона: BOOLWORD
Первый параметр — без комментариев, второй определяет цвет текста и фона. Второй параметр формируется через операцию логического ИЛИ следующих значений:
т FOREGROUND_BLUE =- синий текст;
=- зеленый текст;
= 0004h - красный текст;
=- текст повышенной яркости;
=- голубой фон;
= 0020h - зеленый фон;
BACKGROUND_RED =- красный фон;
=- фон повышенной яркости.
Для задания белого цвета складываются три цветовыхдля зада
ния черного компоненты не задаются вовсе.
Пример программы консольного ввода-вывода
Для демонстрации функций высокоуровневого ввода-вывода в окно консоли разработаем программу, которая вводит с клавиатуры строку и отображает ее в заголовке окна консоли, а затем выводит эту строку в окне консоли с изменением текущей позиции курсора и цвета текста (листинг
Листинг 16.15. Фрагменты приложения
- программа консольного ввода-вывода ;с изменением атрибутов выводимого текста
.data
. code
start p
roc near;точка входа в программу:
;получим
стандартные дескрипторы ввода-вывода
push
STD_OUTPUT_HANDLE
call
GetStdHandle
mov
dOut,eax;dOut - дескриптор консольного вывода
push
STD」NPUT_HANDLE
call
GetStdHandle
mov
din,eax;din - дескриптор консольного ввода
;введем строку
установим курсор в позицию (2,6)
mov
con.XX, 2
mov
con.yy,6
push
con
push
dOut
call
SetConsoleCursorPosi t ion
cmp
eax,0
jz
exi tесли неуспех
push
0
push
offset NumWri; количество действительно введенных символов
push
80размер буфера TitleText для ввода
push
offset TitleText
push
din
call
ReadConsoleA
cmp
eax,0
jz
exit;если неуспех
;выведем
введенную строку в заголовок окна консоли:、
push
offset TitleText
call
SetConsoleTi tleA
;проверить успех вывода заголовка
test
eax.eax
exit;неудача
;выведем
строку в окно консоли с различных позиций и с разными цветами
[установим курсор в позицию (2,5)
mov
ecx,10;строку выведем 10 раз
mov
bl,10000001b;начальные атрибуты
ml: push
ecx
inc
con.XX
inc
con.yy
push
con
push
dOut
call
SetConsoleCursorPosition
cmp
eax ,0
jz
exit;если неуспех
определим атрибуты выводимых символов:
;будем получать их циклически сдвигом - регистр BL
хог
eax,eax
rol
mov
al,bl
push
eax
push
dOut
call
SetConsoleTextAttribute
cmp
eax,®
jw
exit;если неуспех
;вывести
строку
push
0
push
offset NumWri ;реальное количество выведенных на экран
символов
push
NumWri;длина строки для вывода на экран
push
offset TitleText;a4pec строки для вывода на экран
push
dOut
call
Wri teConsoleAпродолжение毋
Листинг 16.15(продолжение)
cmpеах,0
]zexit;еспи неуспех
popecx
loopml
exit:;выход из приложения
Каждый консольный процесс имеет собственный список функций-обработчиков, которые вызываются системой, когда происходят определенные события, например, при активном окне консоли пользователь нажимает комбинацию клавиш CtrL+C, Ctrl+Break или Ctrl+Close. При запуске консольного приложения список функций-обработчиков содержит только заданную по умолчанию функцию, которая вызывает функцию ExitProcess. Консольный процесс может добавлять или удалять дополнительные функции-обработчики, вызывая функцию SetConsoleCtrL- Handler:
BOOL SetConsoleCtrlHandler(PHANDLER_ROUTINE HandlerRoutine, BOOL Add);
Данная функция имеет два параметра:
it HandlerRoutine — указатель на определенную приложением функцию HandlerRoutine, которая должна быть добавлена или удалена;
綴Add — логическое значение:
1 - функция должна быть добавлена;
О — функцию необходимо удалить.
ФункцияHandlerRoutine — это определенная приложением функция обратного вызова. Консольный процесс использует эту функцию, чтобы обработать нажатия клавишуправления. НасамомделеHandlerRoutine —идентификатор-заполнитель для определенного приложением имени функции:
BOOL WINAPI HandlerRouti ne(DWORD dwCtrlType);
Параметр DwCtrlType определяет тип сигнала управления, получаемого обработчиком. Этот параметр может принимать одно из следующих значений:
К CTRL_C_EVENT = 0 — сигнал, имитирующий нажатие клавиш CtrL+Cf может быть получен из двух источников: с клавиатуры или от функции GenerateConsole- CtrlEvent;
9 CTRL_BREAK_EVENT = 1 — сигнал, имитирующий нажатие клавиш Ctrl+Break, может быть получен из двух источников: с клавиатуры или от функции Generate- ConsoleCtrlEvent;
CTRL_CLOSE_EVENT = 2 — сигнал, который система посылает всем процессам, подключенным к данному консольному приложению, когда пользователь его закрывает (выбирая пункт Close в системном меню окна консоли или щелкая на кнопке завершения задачи в диалоговом окне Менеджера задач);
^ CTRL_LOGOFF_EVENT = 5 — сигнал, который посылается всем консольным процессам, когда пользователь завершает работу в системе (этот сигнал не указывает, какой именно пользователь завершает работу);
ii CTRL_SHUTDOWN_EVENT = б — сигнал, который система посылает всем консольным процессам при подготовке к выключению машины.
Функция HandlerRoutine должна возвратить логическое значение: 1 — если она обрабатывает конкретный сигнал управления; 0 — для обработки полученного события будет использоваться другая функция-обработчик HandlerRoutine из списка функций-обработчиков этого процесса (то есть включенная в этот список раньше данной функции).
Как уже было упомянуто, каждый консольный процесс может определить несколько функций HandlerRoutine, которые связываются в цепочку. Первоначально этот список содержит только заданную по умолчанию функцию-обработчик, вызывающую функцию ExitProcess, что в результате приводит к завершению текущего консольного приложения. Консольный процесс добавляет или удаляет дополнительные функции-обработчики, вызывая функцию SetConsoleCtrlHandler, которая не затрагивает список функций-обработчиков других процессов. Когда консольный процесс принимает любой из сигналов управления (см. выше), то вызывается последняя зарегистрированная функция-обработчик, если она не возвращает 1,то управление передается следующему (предыдущему) зарегистрированному обработчику и так продолжается до тех пор, пока один из обработчиков не возвратит 1. Если ни один из обработчиков этого не сделал, то вызывается обработчик, заданный по умолчанию.
Установка обработчиков для сигналов CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT и CTRL_SHUTDOWN_EVENT дает процессу возможность выполнить специфичные для него действия по корректному завершению приложения. Пользовательская функция HandlerRoutine может быть вызвана для того, чтобы выполнить следующие действия:
w、 вызвать функцию ExitProcess для завершения процесса;
возвратить 0 (ложь) — это означает, что завершение приложения должен выполнить обработчик, заданный по умолчанию;
возвратить 1 — в этом случае никакие другие функции-обработчики не вызываются, и система отображает модальное окно диалога с запросом о необходимости завершения процесса.
В последнем случае система также отображает окно диалога, если процесс не отвечает определенное время (5 секунд для CTRL/LOSE_EVENT и 20 секунд для CTRL_LOGOFF_EVENT и CTRL„SHUTDOWN_EVENT). Процесс может использовать функцию SetProcessShutdownParameters, чтобы запретить системе отображать окно диалога. В этом случае система просто заканчивает процесс, когдаHandlerRoutine возвращает истину или когда истекает определенный период времени.
В листинге 16.16 приведен пример пользовательского обработчика события — ввода комбинации Ctrl+C или Ctrl+Break. За основу взята предыдущая программа.
Листинг 16.16. Фрагменты приложения prg16_7.asm
;prgl6_7.asm - программа, демонстрирующая ;пользовательский обработчик события.
.data
Text_CTRL_C db "Нажаты CTRL+C"
Len_Text_CTRL=$-Text_CTRL_C
Text BREAKdb "Нажаты CTRL+BREAK"
продолжение i^
Листинг 16.16(продолжение)
Len_BREAK=$-Text_BREAK . code
CtrlHandler proc arg @@dwCtrlType:DWORD uses ebx,edi , esi;эти регистры обязательно должны сохраняться
;анализируем тип сигнала управления
cmp @@dwCtrlType,CTRL_C_EVENT je h_CTRL_C_EVENT
cmp @@dwCtrlfype,CTRL_BREAK_EVENT
je h_CTRL_BREAK_EVENT -
jmp h_default h_CTRL__C_EVENT:;при нажатии CTRL+C выводим сообщение:
установим курсор
callSetConsoleCursorPosi tion
вывести строку Text_CTRL_C
callWri teConsoleA
возвращаем признак обработки
mov eax, 1
jmp exit_CtrlHandler h_CTRL_BREAKZEVENT:;при нажатии CTRL+BREAK выводим сообщение:
установим курсор
callSetConsoleCursorPosi tion
вывести строку
callWri teConsoleA
возвращаем признак обработки mov eax,l jmp exi t_CtrlHandler
h_default: mov eax ,0ffffffffh ; возвращаем -1 ;все остальное не обрабатываем
exi t_CtrlHandler:ret
CtrlHandlerendp startproc near;точка входа в программу:
;работаем... ;получим стандартные дескрипторы ввода-вывода
;установим функцию-обработчик сигналов управления
pushTRUE
pushoffset cs: CtrlHandler
callSetConsoleCtrlHandler
cmpeax,0
jzexit;если неуспех
введем строку вбуфер TitleText
установим курсор в позицию (2,6)
callSetConsoleCursorPosition'
callReadConsoleA
выведем push call
введенную строку в заголовок окна консоли: offset TitleText
SetConsoleTi tleA
выведем mov mov
строку в окно консоли с различных позиций и с разными цветами ecx , 19; строку выведем 10 раз
bl,10000001b;начальные атрибуты
ml: pushecx
курсор в позицию
call
атрибуты выводимых символов:
;будем получать их циклически - сдвигом регистра xor eax,eax rol bl,1 mov al,bl
pusheax
push
callSetConsoleTextAttribute
вывести строку TitleText
call
cmp eax,0
jz exit;если неуспех
pop ecx loopml
Относительно этой программы можно сделать два замечания. Первое касается функциикоторая в нашей программе называется CtrlHandler. Как
упоминалось, эта функция является функцией обратного вызова. Она вызывается при возникновении определенных событий неявно — из системы Windows. По структуре и алгоритму работы она аналогична оконной функции, которую мы рассматривали ранее (см. раздел «Каркасное Windows-приложение наПо
этому за всеми подробностями отсылаем читателя к этому материалу. Второе замечание касается порядка отладки приложений, содержащих определяемые пользователем функции (процедуры) обратного вызова. Первое, что нужно сделать в процессе пошагового выполнения программы в отладчике, — выяснить адрес процедуры обратного вызова. В программе из листинга 16.16 это можно сделать, выяснив, какое значение будет помещено в стек при выполнении команд:
функцию-обработчик сигналов управления
push TRUE
pushoffsetCtrlHandler
callSetConsoleCtrlHandler
cmpeax, 0
jzexit ;если неуспех
После этого, сделав активным окно CPU отладчика (выбрав в меню команду View CPU), необходимо установить указатель мыши в окно с командами процессора и щелкнуть правой кнопкой мыши. В появившемся контекстном меню выберите пункт Goto. В результате этих действий отладчик отобразит окно диалога, в котороенеобходимовнестиадреспрограммы-обработчикаCtrlHandler. Послеэтого в верхней части окна команд отобразится первая команда процедуры CtrlHandler. Установите на нее курсор и нажмите клавишу F4. Программа начнет выполняться по своему алгоритму. При нажатии пользователем управляющих комбинаций клавиш, допустимых функциейHandlerRoutine, управление будет передано этой функции, и вы сможете произвести ее отладку.
Низкий уровень консольного ввода-вывода по сравнению с высоким обладает более широкими и гибкими возможностями. Низкоуровневые функции консоль-
ного ввода-вывода обеспечивают прямой доступ к входному и экранным буферам консоли, предоставляя приложению доступ к событиям мыши и клавиатуры, а также к информации об изменении размеров окна консоли. Функции низкоуровневого ввода-вывода позволяют приложению иметь доступ по чтению-записи к указанному числу последовательных символьных ячеек в экранном буфере или к прямоугольному блоку символьных ячеек в указанной позиции экранного буфера.
Итоги
綴 Разработка Windows-приложения на языке ассемблера — вполне реальное и в ряде случаев оправданное дело. Однако несмотря на имеющиеся в TASM и MASM средства, для создания полноценного Windows-приложения требуются дополнительные программные и информационные ресурсы, предоставляемые пакетами языков высокого уровня. Лучше всего для этой цели подходит пакет VC++ версии 6.0 и выше. Основную ценность в нем имеют включаемые файлы, редактор ресурсов, работающий в составе интегрированной среды разработки, и компилятор ресурсов. Интерес могут представлять также различные утилиты, входящие в состав пакета Visual C++, например Spy++.
и Приступать к разработке приложения для системы Windows на ассемблере лучше всего, имея некоторый опыт разработки приложений на языке высокого уровня. Это необходимо для понимания логики работы приложения. Когда понимание логики работы Windows-приложения достигнуто, выбор языка для его реализации приобретает в большей степени техническое значение и определяется постановкой задачи и предполагаемыми условиями ее эксплуатации.
• Благодаря поддержке системой Windows консольных приложений можно малыми силами решать серьезные задачи, в том числе и по администрированию системы. Консольным приложениям доступны практически все возможности, предоставляемые Win32 API, при этом для построения приложений не требуется реализовывать какие-то изощренные схемы.
Глава 17
Архитектура
и программирование сопроцессора
睑Архитектура
挑Форматы данных
斂, Система команд
> Исключения и их обработка
夢 Использование отладчика
В предыдущих главах мы рассматривали команды и алгоритмы обработки целочисленных данных, то есть чисел с фиксированной точкой (см. главы 5 и 8). Для обработки числовых данных в формате с плавающей точкой процессоры IA-32 содержат специальное устройство, которое является важной частью их архитектуры.
Устройства для обработки чисел с плавающей точкой появились в компьютерах давно. С точки зрения архитектуры они выглядели по-разному. Так, в архитектуре ЕС ЭВМ (аналог IBM 360/370) устройство с плавающей точкой было естественной частью этой архитектуры со своим регистровым пространством и подсистемой команд. Архитектура компьютеров на базе процессоров вначале опиралась исключительно на целочисленную арифметику. С ростом мощи, а главное, с осознанием разработчиками процессорной техники того факта, что их устройства могут составить достойную конкуренцию своим «большим» предшественникам, в архитектуре компьютеров на базе процессоров стали появляться устройства для обработки чисел с плавающей точкой. В архитектуре семейства процессоров Intel 80x86 устройство для обработки чисел с плавающей точкой появилось
в составе компьютера на базе процессора i8086/88 и получило название математический сопроцессор (далее просто сопроцессор). Выбор такого названия был обусловлен тем, что, во-первых, это устройство было предназначено для расширения вычислительных возможностей основного процессора, а во-вторых, оно было реализовано в виде отдельной микросхемы, то есть его присутствие было необязательным. Микросхема сопроцессора для процессора i8086/88 имела название i8087. С появлением новых моделей процессоров Intel совершенствовались и сопроцессоры, хотя их программная модель осталась практически неизменной. Как отдельные (а соответственно, необязательные в конкретной комплектации компьютера) устройства сопроцессоры сохранялись вплоть до модели процессора i386 и имели название i287 и 1387 соответственно. Начиная с модели i486 сопроцессор исполняется в одном корпусе с основным процессором и, таким образом, является неотъемлемой частью компьютера.
Для чего нужен сопроцессор, какие возможности добавляет он к тому, что делает основной процессор, кроме обработки еще одного формата данных? Перечислим некоторые из них.
Я Полная поддержка стандартов IEEE-754 и 854 на арифметику с плавающей точкой. Эти стандарты описывают как форматы данных, с которыми должен работать сопроцессор, так и набор реализуемых им функций.
Поддержка численных алгоритмов для вычисления значений тригонометрических функций, логарифмов и т. п. Эта работа сопроцессора выполняется абсолютно прозрачно для программиста, что само по себе очень ценно, так как не требует от него разработки соответствующих подпрограмм.
Обработка десятичных чисел с точностью до 18 разрядов, что позволяет сопроцессору без округления выполнять арифметические операции над целыми десятичными числами со значениями до 1018.
Обработка вещественных чисел из диапазона 3,37 • lOT.[,18 • 10+W2.
Нужно отметить, что в последние годы разработчики компьютерной периферии все активнее освобождают центральный процессор от части вычислений с целью более эффективной реализации специализированных операций. Очень ярко это проявляется на рынке мультимедийного оборудования, где множество разработчиков предлагают видеокарты с чипсетами (наборами микросхем), более эффективно реализующими работу с графикой, чем это делает сам процессор. Несмотря на это дополнение системы целочисленных команд процессора командами сопроцессора предоставляет ряд уникальных свойств и возможностей, пренебрегать которыми было бы опрометчиво.
Архитектура сопроцессора
В главе 2 мы определили место сопроцессора в архитектуре компьютера. Аппаратная реализация сопроцессора нас интересует лишь в видимой для программиста части. Как и в случае с основным процессором, интерес для нас представляет программная модель сопроцессора. С точки зрения программиста, сопроцессор представляет собой совокупность регистров, каждый из которых имеет свое функциональное назначение (рис. 17.1).
Регистр состояния SWRРегистр состояния SWR
Регистр состояния SWR
Регистр состояния SWR
Указатель команды IPR 47
4747Указатель данных DPR о
47
47
Рис.Программная модель сопроцессора
В программной модели сопроцессора можно выделить три группы регистров. Восемь регистровсоставляют основу программной модели сопроцессо
ра — стек сопроцессора. Размерность каждого регистра — 80 битов. Такая организация характерна для устройств, специализирующихся на обработке вычислительных алгоритмов. Вспомните, как представляются математические выражения с использованием обратной польской записи (ПОЛИЗ). Вычисление такого выражения заключается в выборке с вершины стека очередной операции. Если это двухместная операция, то с вершины стека снимаются два операнда, над которыми и производятся действия в соответствии со снятой ранее операцией. Более подробно представление выражения в форме ПОЛИЗ мы рассмотрим далее. Реализация численных алгоритмов на основе стека сопроцессора позволяет получить существенный выигрыш в скорости вычислений.
Три служебных регистра:
регистр состояния сопроцессора SWR (StatusRegister) отражает ин
формацию о текущем состоянии сопроцессора и содержит поля, позволяющие определить, какой регистр является текущей вершиной стека сопроцессора, какие исключения возникли после выполнения последней команды, каковы особенности выполнения последней команды (некий аналог регистра флагов основного процессора) и т. д.;
П управляющий регистр сопроцессора CWR (Control Word Register) управляет режимами работы сопроцессора; с помощью полей в этом регистре можно регулировать точность выполнения численных вычислений, управлять округлением, маскировать исключения;
15256
□ регистр слова тегов TWR (Tags Word Register) используется для контроля за состоянием каждого из регистров R0...R7 (команды сопроцессора используют этот регистр, например, для того, чтобы определить возможность записи значений в указанные регистры).
幾. Два регистра указателей — данных DPR (Data Point Register) и команд IPR (Instruction Point Register) — предназначены для запоминания информации об адресе команды, вызвавшей исключительную ситуацию, и адресе ее операнда. Эти указатели используются при обработке исключительных ситуаций (но не для всех команд).
Все эти регистры являются программно доступными. Однако к одним из них доступ получить довольно легко, для этого в системе команд сопроцессора существуют специальные команды, а к другим его получить сложнее, так как специальных команд для этого нет, поэтому необходимо выполнять дополнительные действия.
Рассмотрим общую логику работы сопроцессора и более подробно охарактеризуем перечисленные регистры.
Регистровый стек сопроцессора организован по принципу кольца. Это означает, что среди всех регистров, составляющих стек, нет такого, который является вершиной стека. Напротив, все регистры стека с функциональной точки зрения абсолютно равноправны. Но, как известно, в стеке всегда должна быть вершина. И она действительно есть, но является плавающей. Контроль текущей вершины осуществляется аппаратно с помощью трехразрядного поля ТОР регистраSWR (рис. 17.2). В поле ТОР фиксируется номер регистра стека 0...7(R0...R7),являющегося текущей вершиной стека.
Регистр состояния SWR
b
сЗ
top
с2
с1
со
es
sf
ре
ue
ое
ze
de
ie
151413 11 10987654321 О
Рис. 17.2, Формат регистра состояния сопроцессора SWR
Команды сопроцессора не оперируют физическими номерами регистров стека RO...R7. Вместо этого они используют логические номера этих регистров ST⑼… "⑴• С помощью логических номеров реализуется относительная адресация регистров стека сопроцессора. Если текущей вершиной стека является физический регистр RO, то после записи очередного значения в стек сопроцессора его текущей вершиной станет физический регистр R7 (рис. 17.3, а). На рис. 17.3, б показан пример, когда текущей вершиной до записи в стек является физический регистр R3, а после записи в стек текущей вершиной становится физический регистр стека R2. То есть по мере записи в стек указатель его вершины движется по направлению к младшим номерам физических регистров (уменьшается на единицу)^ Что касается логических номеров регистров стека ST(O)...ST(1), то, как следует из рисунка, они «плавают» вместе с изменением текущей вершины стека. Таким образом, реализуется принцип кольца.
На первый взгляд, такая организация стека кажется странной. Но, как оказалось, она обладает большой гибкостью. Это хорошо видно на примере передачи
Состояние стека сопроцессора до выполнения операции записи в стек
SWRI]000|1IIRO st(0)6R1 st(1)R2 st(2)R3 st(3)R4 st(4)R5 st(5)R6 st(6)R7 st(7)SWRI]000|1IIRO st(0)6R1 st(1)R2 st(2)R3 st(3)R4 st(4)R5 st(5)R6 st(6)R7 st(7)Вершина стекаФизическиеi номера I регистров стекаполе topсопроцессорафВершина стекаФизическиеi номера I регистров стекаполе topсопроцессораф个Логические номера个Логические номерарегистров стека с□процессорарегистров стека с□процессораВершина стекаФизические номерарегистров стекасопроцессораПоле topSWR 1)011 II1RO st(5)R1 st(6)R2 st(7)R3 st(0)3R4st(1)R5 st(2)R6 st(3)R7 st(4)个Вершина стекаФизические номерарегистров стекасопроцессораПоле topSWR 1)011 II1RO st(5)R1 st(6)R2 st(7)R3 st(0)3R4st(1)R5 st(2)R6 st(3)R7 st(4)个Логические номера регистров стека сопроцессораЛогические номера регистров стека сопроцессора
SWRI
]000|1
I
I
RO st(0)
6
R1 st(1)
R2 st(2)
R3 st(3)
R4 st(4)
R5 st(5)
R6 st(6)
R7 st(7)
SWRI
]000|1
I
I
RO st(0)
6
R1 st(1)
R2 st(2)
R3 st(3)
R4 st(4)
R5 st(5)
R6 st(6)
R7 st(7)
Вершина стека
Физическиеi номера I регистров стекаполе top
сопроцессораф
Вершина стека
Физическиеi номера I регистров стекаполе top
сопроцессораф
个
Логические номера
个
Логические номера
регистров стека с□процессора
регистров стека с□процессора
Вершина стека
Физические номера
регистров стека
сопроцессораПоле top
SWR 1
)011 I
I
1
RO st(5)
R1 st(6)
R2 st(7)
R3 st(0)
3
R4st(1)
R5 st(2)
R6 st(3)
R7 st(4)
个
Вершина стека
Физические номера
регистров стека
сопроцессораПоле top
SWR 1
)011 I
I
1
RO st(5)
R1 st(6)
R2 st(7)
R3 st(0)
3
R4st(1)
R5 st(2)
R6 st(3)
R7 st(4)
个
Логические номера регистров стека сопроцессора
Логические номера регистров стека сопроцессора
Состояние стека сопроцессора после выполнения операции записи в стек
Вершина стекаВершина стека个Логические номера个Логические номераре I гистров стекаC(^процессораре I гистров стекаC(^процессораФизические номерарегистров стекасопроцессораФизические номерарегистров стекасопроцессораRO st(6)R1 st(7)R2 st(0)R3 st(1)R4 st(2)R5 st(3)R6 st(4)R7st(5)RO st(6)R1 st(7)R2 st(0)R3 st(1)R4 st(2)R5 st(3)R6 st(4)R7st(5)Вершина стекаВершина стекаSWRSWRПоле topL|Поле topL|个Логические номера个Логические номерарегистров стекасопроцессорарегистров стекасопроцессора
Вершина стека
Вершина стека
个
Логические номера
个
Логические номера
ре I гистров стека
C(^процессора
ре I гистров стека
C(^процессора
Физические номерарегистров стекасопроцессора
Физические номерарегистров стекасопроцессора
RO st(6)
R1 st(7)
R2 st(0)
R3 st(1)
R4 st(2)
R5 st(3)
R6 st(4)
R7st(5)
RO st(6)
R1 st(7)
R2 st(0)
R3 st(1)
R4 st(2)
R5 st(3)
R6 st(4)
R7st(5)
Вершина стека
Вершина стека
SWR
SWR
Поле top
L|
Поле top
L|
个
Логические номера
个
Логические номера
регистров стекасопроцессора
регистров стекасопроцессора
а6
Рис. 17.3. Физическая и логическая нумерации регистров стека сопроцессора
параметров подпрограмме. Для повышения гибкости подпрограмм (в разработке и использовании) нежелательно привязывать их по передаваемым параметрам к аппаратным ресурсам (физическим номерам регистров сопроцессора). Гораздо удобнее задавать порядок следования передаваемых параметров в виде логических номеров, поскольку такой способ передачи однозначен и не требует от разработчика знания лишних подробностей о вариантах аппаратной реализации сопроцессора. Логическая нумерация регистров сопроцессора, поддерживаемая на уровне системы команд, идеально реализует эту идею. При этом не имеет значения, в какой физический регистр стека сопроцессора были помещены данные перед вызовом
подпрограммы, определяющим является только порядок следования параметров в стеке. По этой причине подпрограмме достаточно знать не место, а только порядок размещения передаваемых параметров в стеке.
Перед тем как приступить к описанию команд и данных, с которыми работает сопроцессор, отметим, каким образом «уживаются» между собой эти два разных вычислительных устройства — процессор и сопроцессор. Каждое из них имеет свои несовместимые друг с другом системы команд и форматы обрабатываемых данных. Несмотря на то что сопроцессор архитектурно представляет собой отдельное вычислительное устройство, он не может существовать отдельно от основного процессора. Ранее уже было отмечено, что первые модели процессоров Intel (i8086, i286, i386) и сопроцессоров (соответственно, i8087, i287, i387) выполнялись как отдельные устройства — сопроцессор при необходимости вставлялся в специальный разъем на системной плате. Начиная с модели i486 сопроцессор и основной процессор производятся в одном корпусе и являются физически неделимыми, то есть архитектурно это по-прежнему два разных устройства, а аппаратно — одно.
Процессор и сопроцессор, являясь двумя самостоятельными вычислительными устройствами, могут работать параллельно. Но этот параллелизм касается только их внутренней работы над исполнением очередной команды. Как реализован этот параллелизм, в чем его суть и особенности? Оба процессора подключены к общей системной шине и имеют доступ к одинаковой информации. Инициирует процесс выборки очередной команды всегда основной процессор. После выборки команда попадает одновременно в оба процессора. Любая команда сопроцессора имеет код операции, первые 5 битов которого имеют значение 11011. Когда код операции начинается этими битами, то основной процессор по дальнейшему содержимому кода операции выясняет, требует ли данная команда обращения к памяти. Если это так, то основной процессор формирует физический адрес операнда и обращается к памяти, после чего содержимое ячейки памяти выставляется на шину данных. Если обращение к памяти не требуется, то основной процессор заканчивает работу над данной командой (не делая попытки ее исполнения!) и приступает к декодированию следующей команды из текущего входного командного потока. Что же касается сопроцессора, то выбранная команда, как уже было отмечено, попадает к нему и к основному процессору одновременно. Сопроцессор, определив по первым пяти битам, что очередная команда принадлежит его системе команд, начинает ее исполнение. Если команда требует операнда в памяти, то сопроцессор обращается к шине данных за чтением содержимого ячейки памяти, которое к этому моменту должно быть предоставлено основным процессором. Из этой схемы взаимодействия следует, что в определенных случаях необходимо согласовывать работу обоих устройств. К примеру, если во входном потоке сразу за командой сопроцессора следует команда основного процессора, использующая результаты работы предыдущей команды, то сопроцессор не успеет выполнить свою команду за то время, пока основной процессор, пропустив сопроцессорную команду, выполняет свою. Очевидно, что логика работы программы нарушается. Возможна и другая ситуация. Если входной поток команд содержит последовательность из нескольких крманд сопроцессора, то, очевидно, что процессор, в отличие от сопроцессора, проверит их очень быстро, чего он не должен делать, так как обеспечивает внешний интерфейс для сопроцессора. Эти и другие более сложные ситуации при-
водят к необходимости синхронизировать между собой работу двух процессоров. В первых моделях процессоров подобная синхронизация проводилась программистом «вручную» путем вставки перед или после каждой команды сопроцессора специальной команды WAIT или FWAIT. Работа данной команды заключалась в приостановке работы основного процессора до тех пор, пока сопроцессор не закончит работу над последней командой. Начиная с модели процессора i486, подобная синхронизация выполняется командами WAIT/FWAIT, которые введены в алгоритм работы большинства команд сопроцессора, и программисту нет необходимость заботиться об этом явно. Но некоторые команды из группы команд управления сопроцессором (см. далее) имеют два варианта реализации — с синхронизацией (ожиданием) и без нее.
Из всего сказанного можно сделать важный вывод: использование сопроцессора является совершенно прозрачным для программиста. В общем случае программисту следует воспринимать сопроцессор как набор дополнительных регистров, для работы с которыми предназначены специальные команды. Для эффективного применения сопроцессора программист должен хорошо разобраться в структуре регистров и логике их использования. Поэтому перед тем как приступить к рассмотрению команд и данных, с которыми работает сопроцессор, приведем описание структуры некоторых регистров сопроцессора.
Регистр состояния SWR
Ранее было отмечено, что регистр SWR отражает текущее состояние сопроцессора после выполнения последней команды. Далее перечислены поля, из которых структурно состоит регистр SWR (см. рис. 17.2).
- Шесть флагов исключительных ситуаций.
Бит SF (Stack Fault) — ошибка работы стека сопроцессора. Бит устанавливается в единицу, если возникает одна из трех исключительных ситуаций (см. ранее) — РЕ, UE или IE. В частности, его установка информирует о попытке записи в заполненный стек или, напротив, попытке чтения из пустого стека. После того как вы проанализировали этот бит, его нужно снова установить в ноль вместе с битами РЕ, UE или IE (если они были установлены);
Бит ES (Error Summary) сигнализирует о суммарной ошибке в работе сопроцессора. Бит устанавливается в единицу, если возникает любая из шести исключительных ситуаций, о которых будет рассказано далее.
戀 Четыре бита CO...C3 (Condition Code) представляют собой код условия. Назначение этих битов аналогично флагам в регистре EFLAGS основного процессора — они отражают результат выполнения последней команды сопроцессора. В приложении А для некоторых команд сопроцессора приведена интерпретация битов СО...СЗ.
緣 Трехразрядное поле ТОР содержит указатель регистра текущей вершины стека.
Почти половину регистра SWR занимают биты (флаги) регистрации исключительных ситуаций. Исключительная ситуация — особый тип прерываний. Необходимо заметить [8], что прерывания, поддерживаемые процессором Intel, по месту их возникновения классифицируются на внешние и внутренние. Внутренние
прерывания возникают в ходе работы текущей программы и делятся на синхронные (по команде int) и асинхронные, называемые исключениями, или особыми случаями. Таким образом, исключения — это разновидность прерываний, с помощью которых процессор информирует программу о некоторых особенностях ее реального исполнения. Сопроцессор также обладает способностью возбуждения подобных прерываний при возникновении определенных ситуаций (не обязательно ошибочных). Все возможные исключения сведены к шести типам, каждому из которых соответствует один бит в регистре SWR. Программисту совсем не обязательно писать обработчик для реакции на ситуацию, приведшую к некоторому исключению. Сопроцессор умеет самостоятельно реагировать на многие из них. Это так называемая обработка исключений по умолчанию. Для того чтобы запретить сопроцессору обработку определенного типа исключения по умолчанию, необходимо это исключение замаскировать. Такое действие выполняется путем установки в единицу нужного бита в управляющем регистре сопроцессора CWR (рис. 17.4). Приведем типы исключений, фиксируемые с помощью регистра SWR:
幾 IE (Invalide operation Error) — недействительная операция;
DE (Denormalized operand Error) — денормализованный операнд;
緣 ZE (divide by Zero Error) — ошибка деления на нуль;
幾 OE (Overflow Error) — ошибка переполнения (возникает в случае выхода порядка числа за максимально допустимый диапазон);
綴UE (Underflow Error) — ошибка антипереполнения (возникает, когда результат слишком мал);
缴 РЕ (Precision Error) — ошибка точности (устанавливается, когда сопроцессору приходится округлять результат из-за того, что его точное представление невозможно; так, сопроцессору, как и читателю, никогда не удастся точно разделить 10 на 3).
При возникновении любого из этих шести типов исключений устанавливается в единицу соответствующий бит в регистре SWR вне зависимости от того, было ли замаскировано это исключение в регистре CWR или нет. Более подробно об исключениях, в частности, об условиях их возникновения, рассказывается в разделе «Исключения сопроцессора и их обработка».
Регистр управления CWR
Регистр управления сопроцессором CWR определяет особенности обработки численных данных (рис. 17.4). Он состоит:
緻 из шести масок исключений;
о поля управления точностью PC (Precision Control);
緻 поля управления округлением RC (Rounding Control).
Регистр управления CWR
ГС
PC
р
U
о
Z
d
i
1513 1110 9876543210
Рис. 17.4. Формат регистра управления сопроцессором CWR
Шесть масок предназначены для маскирования исключительных ситуаций, возникновение которых фиксируется с помощью шести битов регистра SWR. Если какие-то биты исключений в регистре CWR установлены в единицу, это означает, что соответствующие исключения будут обрабатываться самим сопроцессором. Если для какого-либо исключения в соответствующем бите масок исключений регистра CWR содержится нулевое значение, то при возникновении исключения этого типа будет возбуждено прерывание 16 (10h). Операционная система должна содержать (или программист должен написать) обработчик этого прерывания. Он должен выяснить причину прерывания, после чего, если это необходимо, устранить ее, а также выполнить другие действия. Более подробно этот вопрос обсуждается в разделе «Исключения сопроцессора и их обработка».
Поле управления точностью PC предназначено для выбора длины мантиссы. Возможные значения в этом поле означают:
緩 PC = 00 — длина мантиссы 24 бита;
ж PC = 10 — длина мантиссы 53 бита;
綫 PC = 11 — длина мантиссы 64 бита.
По умолчанию устанавливается значение поля PC - 11.
Поле RC позволяет управлять процессом округления чисел в ходе работы сопроцессора. Необходимость округления может возникнуть в ситуации, когда после выполнения очередной команды сопроцессора получается непредставимый результат, например периодическая дробь 3,333... Установив одно из значений в поле RC, можно выполнить округление в необходимую сторону. Для того чтобы выяснить характер округления, введем обозначения:
т — значение в ST(O) или результат работы некоторой команды, который не может быть точно представлен и поэтому должен быть округлен;
и а и b — наиболее близкие значения к значению т, которые могут быть представлены в регистре ST(O) сопроцессора, причем выполняется условие а < т < b. Далее приведены значения поля RC и описан соответствующий им характер округления:
戀; 00 — значение т округляется к ближайшему числу а или b;
緩 01 — значение т округляется в меньшую сторону, то есть т=a;
ж 10 — значение т округляется в большую сторону, то есть т = b;
犠и — производится отбрасывание дробной части т (может использоваться в операциях целочисленной арифметики).
Регистр тегов TWR
Регистр тегов TWR представляет собой совокупность двухразрядных полей. Каждое двухразрядное поле соответствует определенному физическому регистру стека (см. рис. 17.1) и характеризует его текущее состояние. Изменение состояния любого регистра стека отражается на содержимом соответствующего этому регистру поля регистра тега. Возможны следующие значения в полях регистра тега: 緣 00 — регистр стека сопроцессора занят допустимым ненулевым значением;
it 01 — регистр стека сопроцессора содержит нулевое значение;
10 — регистр стека сопроцессора содержит одно из специальных численных значений (см. ниже), за исключением нуля;
в11 — регистр пуст, и в него можно производить запись (нужно отметить, что это значение в одном из двухразрядных полей регистра тегов не означает, что все биты соответствующего регистра стека обязательно нулевые).
Мы не раз уже отмечали, что при написании программы разработчик манипулирует не абсолютными, а относительными номерами регистров стека. По этой причине у него могут возникнуть трудности при попытке интерпретации содержимого регистра тегов TWR с соответствующими физическими регистрами стека. В качестве связующего звена необходимо привлекать информацию из поля ТОР регистра SWR.
Форматы данных
Сопроцессор расширяет номенклатуру форматов данных, с которыми работает основной процессор. В этом нет ничего удивительного, так как формат данных любого устройства в существенной мере отражает специфику его работы. Сопроцессор специально разрабатывался для вычислений с плавающей точкой. Но сопроцессор может работать и с целыми числами, хотя и менее эффективно. Перечислим форматы данных, с которыми работает сопроцессор:
* двоичные целые числа в трех форматах — 16, 32 и 64 бита;
:戀: упакованные целые десятичные (BCD) числа — длина максимального числа составляет 18 упакованных десятичных цифр (9 байтов);
вещественные числа в трех форматах — коротком (32 бита), длинном (64 бита), расширенном (80 битов).
Кроме этих основных форматов, сопроцессор поддерживает специальные численные значения, к которым относятся:
я денормализованные вещественные числа — это числа, меньшие минимального нормализованного числа (см. ниже) для каждого вещественного формата, поддерживаемого сопроцессором;
я нуль;
is положительные и отрицательные значения бесконечности;
нечисла;
неопределенности и неподдерживаемые форматы.
Рассмотрим более подробно основные форматы данных, поддерживаемые сопроцессором. Важно отметить, что в самом сопроцессоре числа в этих форматах имеют одинаковое внутреннее представление — расширенный формат вещественного числа. Это один из форматов представления вещественных чисел, который точно соответствует формату регистров R0...R7 стека сопроцессора (см. рис. 17.1). Таким образом, даже если вы используете команды сопроцессора с целочисленными операндами, то после загрузки в сопроцессор операндов целого типа они автоматически преобразуются в формат расширенного вещественного числа.
Двоичные целые числа
Сопроцессор работает с тремя типами целых чисел (рис. 17.5).
Знак
I | Целое слово |
15О
口Короткое целое]
31О
□Длинное целое]
63О
Рис. 17.5. Форматы целых чисел сопроцессора
В табл. 17.1 представлены формат целых чисел, их размерность и диапазон значений.
Таблица 17.1.Форматы целых чисел сопроцессора
Формат
Размер, битов
Диапазон значений
Целое слово
16
-32 768...+32 767
Короткое целое
32
-2 • 1О9...+2 • 109
Длинное целое
64
-9-1О18...+9-1018
Выбирая формат данных, с которыми будет работать ваша программа, помните, что сопроцессор поддерживает операции с целыми числами, но работа с ними осуществляется неэффективно. Причина в ТОМ, что обработка сопроцессором целочисленных данных будет замедлена из-за дополнительного преобразования целых чисел в их внутреннее представление в виде эквивалентного вещественного числа расширенного формата.
В программе целые двоичные числа описываются обычным способом — с ис- пользованиемдиректив DW, DD и DQ. Например, целоечисло 5 можетбытьописано следующим образом:
ch_dw5;представлениев памяти:ch_dw=0500
ch:dd5;представлениев памяти:ch 二dw=®50000 00
ch_dq5;представлениев памяти:ch_dw=050000 0000000000
Работать с целыми числами может далеко не всякая команда сопроцессора. Подробную информацию о командах сопроцессора можно найти в приложении.
Упакованные целые десятичные числа
Сопроцессор поддерживает один формат упакованных целых десятичных чисел, или BCD-чисел (рис. 17.6). Как вы помните, для описания упакованного десятичного числа используется директива DT (см. главу 8). Данная директива позволяет описать 20 цифр в упакованном десятичном числе (по две в каждом байте). Из-за того что максимальная длина упакованного десятичного числа в сопроцессоре составляет только 9 байт, в регистры RO...R7 можно поместить только 18 упакованных десятичных цифр. Старший десятый байт игнорируется. Самый старший бит этого байта используется для хранения знака числа.
Знак
I I 口1吊6口15dlm13山水ii5q| d9ds I d衣| d5d4 | &dg l & 闽79О
Рис. 17.6. Формат десятичного числа сопроцессора
Упакованные десятичные числа также представляются в стеке сопроцессора в расширенном формате. Упакованные десятичные числа в программе описываются директивой DT. Например, целое число 5 365 904 в формате упакованного десятичного числа может быть описано следующим образом: ch_dt dt 5365904
;представление в памяти: ch_dt=04 59 36 05 00 00 00 00 00 00
Нужно отметить, что в сопроцессоре имеются всего две команды для работы с упакованными десятичными числами — это команды сохранения и загрузки.
Вещественные числа
Основной тип данных, с которыми работает сопроцессор, — вещественный. Данные этого типа описываются тремя форматами: коротким, длинным и расширенным (рис. 17.7).
Знак1 s| Характеристика q |Мантисса (М)13124 2301 sХарактеристика q 1635352Мантисса(M)|...|1С1Знак1 s| Характеристика q |Мантисса (М)13124 2301 sХарактеристика q 1635352Мантисса(M)|...|1С1I sХарактеристика q | Мантисса(M)|7964 63I sХарактеристика q | Мантисса(M)|7964 63КороткийформатДлинныйформатРасширенныйформатКороткийформатДлинныйформатРасширенныйформат
Знак
1 s| Характеристика q |
Мантисса (М)1
3124 23
0
1 sХарактеристика q 1
635352
Мантисса(M)|...|1
С1
Знак
1 s| Характеристика q |
Мантисса (М)1
3124 23
0
1 sХарактеристика q 1
635352
Мантисса(M)|...|1
С1
I sХарактеристика q | Мантисса(M)|
7964 63
I sХарактеристика q | Мантисса(M)|
7964 63
Короткийформат
Длинныйформат
Расширенныйформат
Короткийформат
Длинныйформат
Расширенныйформат
Рис. 17.7. Форматы вещественных чисел сопроцессора
Для представления вещественного числа используется следующая формула: А - (±M) • M(17.1)
Здесь:
• М — мантисса числа А (мантисса должна удовлетворять условию | М < 1);
緻N — основание системы счисления, представленное целым положительным числом;
II р — порядок числа, показывающий истинное положение точки в разрядах мантиссы (по этой причине вещественные числа имеют еще название чисел с плавающей точкой, так как ее положение в разрядах мантиссы зависит от значения порядка).
Для удобства обработки в процессоре чисел с плавающей точкой его архитектурой накладываются некоторые ограничения на компоненты формулы (17.1). Далее перечислены эти условия и ограничения для сопроцессоров, применяющихся в архитектуре IA-32.
涉 Основание системы счисления^=2.
緩 Мантисса М должна быть представлена в нормализованном виде. Нормализация может отличаться для разных типов процессоров. Для ЕС ЭВМ, например, мантисса нормализованного числа должна удовлетворять условию 1/NW\М\ < 1. Это означает, что старший бит представления должен быть единичным. Для случая, когда N= 2, это соответствует отношению 1/2 < |М| < 1 или в двоичном виде0,10...00<|M|< 0Д1...11,то есть первая цифра после запятой должна быть значащей (единицей), а порядок pt соответственно, таким, чтобы это условие выполнялось. Для архитектуры сопроцессора IA-32 нормализованным является число несколько иного вида:
Л =(-1)S-N4-M(17.2)
Здесь:
s — значение знакового разряда (0 — число больше нуля, 1 — число меньше нуля);
q — порядок числа, его назначение аналогично назначению порядка^ в формуле (17.1), но как поясняется далее, р и q — не одно и тоже.
В этой формуле знак имеют и порядок вещественного числа, и его мантисса. На рис. 17.7видно, что формат хранения вещественного числа в памяти имеет только поле для знака мантиссы. А где же хранится знак порядка?
В сопроцессоре Intel на аппаратном уровне принято соглашение, что порядок р определяется в формате вещественного числа особым значением q, называемым характеристикой. Величина q связана с порядкомр посредством следующей формулы и представляет собой некоторую константу (условно назовем ее фиксированным смещением)'.
q=p+ фиксированное смещение.(17.3)
Для каждого из трех возможных форматов вещественных чисел смещение q имеет разное, но фиксированное для конкретного формата значение, которое зависит от количества разрядов, отводимых под характеристику (табл. 17.2).
Таблица 172 Форматы вещественных чисел
Формат
Короткий
Длинный
Расширенный
Длина числа (биты)
32
64
80
Размерность мантиссы М
24
53
64
Диапазон значений
10-38...10+38
10^08...Ю+308
[0-4932 [ 0+4932
Размерность характеристики q
8
и
15
Значение фиксированного смещения
+127
+1023
+ 16 3831
Диапазон характеристик q
0...255
0...2047
0...32 767
Диапазон порядков р
-126...+127
-1022...+1023
-16 382...+16 383
В таблице показаны диапазоны значений характеристик q и соответствующих им истинных порядков р вещественных чисел. Отметим, что нулевому порядку вещественного числа в коротком формате соответствует значение характеристики
460Глава 17. Архитектура и программирование сопроцессора
равное 127, которому в двоичном представлении соответствует значение 01 и и и. Отрицательному порядку р, например -1, будет соответствовать характеристика q= + 127 = или в двоичном виде — 01 11 11 10. Положительному порядку р, например +1, будет соответствовать характеристика q = 1 + 127 = 128, или в двоичном виде — 10 00 00 00. То есть все положительные порядки имеют в двоичном представлении характеристики старший бит равный единице, а отрицательные порядки — нет. Таким образом, знак порядка «спрятан» в старшем бите характеристики. Теперь вам должно быть понятно, откуда появились значения в двух последних строках табл. 17.2.
Так как нормализованное вещественное число всегда имеет целую единичную часть (исключая перечисленные ранее специальные численные значения), то при его представлении в памяти появляется возможность считать первый разряд вещественного числа единичным по умолчанию и учитывать его наличие только на аппаратном уровне. Это дает возможность увеличить диапазон представимых чисел, так как появляется лишний разряд, пригодный для представления мантиссы числа. Но это справедливо только для короткого и длинного форматов вещественных чисел. Расширенный формат как внутренний формат представления числа любого типа в сопроцессоре содержит целую единичную часть вещественного в явном виде.
Как определить вещественное число или зарезервировать место для его размещения в программе на ассемблере?
Короткое вещественное число длиной в 32 разряда определяется директивой DD. При этом обязательным в записи числа является наличие десятичной точки, даже если оно не имеет дробной части. Для транслятора десятичная точка является указанием, что число нужно представить в виде числа с плавающей точкой в коротком формате (см. рис. 17.7). Это же касается длинного и расширенного форматов представления вещественных чисел, определяемых директивами DQ и DT. Другой способ задания вещественного числадирективамиDD, DQ и DT — экспоненциальная форма с использованием символа «е>>. Вид вещественного числа в поле операндов директив DD, DQ и DT можно представить синтаксической диаграммой (рис. 17.8).
^)~От-©";^)~От-©";d —десятичная цифра 0..9d —десятичная цифра 0..9
^)~От-©";
^)~От-©";
d —десятичная цифра 0..9
d —десятичная цифра 0..9
Рис. 17.8. Синтаксис вещественных чисел в директивах DD, DQ и DT
Чтобы окончательно разобраться в тонкостях представления вещественных чисел различных форматов в памяти, рассмотрим несколько примеров.
Определим в программе вещественное число 45,56 в коротком формате. Это можно сделать тремя способами: dd 45.56 dd 45.56е0 dd 0.4556e2
В памяти это число будет выглядеть так:
Учитывая, что в архитектуре Intel принят «перевернутый» порядок следования байтов в памяти в соответствии с принципом «младший байт по младшему адресу», истинное представление числа 45,56 будет следующим:
Двоичное представление в памяти числа 45,56 иллюстрирует рис. 17.9.
Знак
i
0100 0010 ООН ОНО ООН 1101 0111 0001
Характеристика 45..56
= 132
Позиция воображаемойточки
Рис.Двоичное представление в памяти вещественного числа в директиве DD
Из рисунка видно, что старшая единица мантиссы при представлении в памяти отсутствует.
Определим теперь в программе вещественное число 45,56 в длинном формате. Это можно сделать двумя способами:
dq 45.56
dq
В памяти это число будет выглядеть так:
47 el 7a 14 ae c7 46 40.
Перевернув его, получим истинное значение:
*40 46 c7 ae 1 4 7a el 47.
Разберите его по компонентам вещественного числа самостоятельно.
Наконец, определим в программе вещественное число 45,56 в расширенном формате:
dt 45.56
В памяти это число будет выглядеть так:
713d 0ad7 a3 70 3d b6 04 40.
Перевернув его, получим истинное значение в памяти:
40 04 b6 3d 70 a3 d7 0a 3d 71.
Двоичное представление числа 45,56 полезно рассмотреть подробнее (рис.
Знак мантиссы
У
01000000000001001011011000111101011100001010001111010111000010101110000101110001
/,…/
ХарактеристикаА
i
(p = 5),Позиция воображаемой точки
Рис.Двоичное представление в памяти вещественного числа в директиве DT
0100 0000 0000 0100 101101100011 11010111 0000 1010
ООН 1101 0111 0000 1010 11100001 0111 0001.
Данное число имеет следующее назначение битов:
• 0 — знак《+»;
幾 100 0000 0000 0100 - характеристика q = 16 388;
緣 1011 01 — целая часть числа (45);
т 10 0011 1101 0111 0000 1010 0011 1101 0111 0000 1010 1110 0001 0111 0001 - дробная часть числа (0,56).
Как видно, в мантиссе явно присутствует старшая единица, чего не было в коротком и длинном форматах представления вещественного числа.
Дальнейшее обсуждение требует четкого понимания того, каким образом из дробного десятичного числа получается его расширенное вещественное представление. Рассмотрим этот процесс по шагам.
Переведем десятичную дробь 45,56 в двоичное представление. Подробно алгоритм такого перевода описан в главе 4. В результате получим эквивалентное двоичное представление:
45,56[o =
- 101101.100011110101110000101000111101011100001010111000010Ш0001.
Нормализуем число. Для этого переносим точку влево до тех пор, пока в целой части числа не останется одна двоичная единица. Число переносов влево (или вправо, если десятичное число было меньше единицы) станет порядком числа. Но будьте внимательны — в поле характеристики заносится смещенное значение порядка (см. табл. 17.2). Таким образом, после перемещения точки получаем значение порядка равное 5. Соответственно, характеристика будет выглядеть так:
5+16383 - 16 38810 =
Сформированный результат в виде вещественного числа в расширенном формате состоит из трех компонентов:
蕊 знака — 0;
в характеристики — 100000000000100;
• мантиссы - 1011 01100011 1101 0111 00001010 0011 1101 0111 0000 1010 1110 0001 0111 0001.
Позже вы научитесь пользоваться отладчиком для работы с сопроцессором и получите возможность просматривать содержимое регистров стека (см. раздел «Использование отладчика»). При этом содержимое регистров стека будет изображаться в шестнадцатеричном виде: 40 04 Ь6 3d 70 аЗ d7 Оа 3d 71. Видно, что это представление полностью совпадает с приведенным ранее представлением числа в памяти, если оно описано в директиве DT.
В качестве итога еще раз подчеркнем, что расширенный формат представления вещественного числа является единственным форматом представления чисел в регистрах сопроцессора. Само преобразование производится автоматически при загрузке числа в стек сопроцессора. Исключение составляет расширенный формат.
Специальные численные значения
Несмотря на большой диапазон вещественных значений, представимых в регистрах стека сопроцессора, понятно, что бесконечные значения находятся за рамками этого диапазона. Для того чтобы иметь возможность реагировать на вычислительные ситуации, в которых возникают такие значения, в сопроцессоре и предусмотрены специальные комбинации битов, называемые специальными численными значениями. При необходимости программист может сам кодировать специальные численные значения, поскольку вещественные числа, описанные директивой DT, соответствующие команды сопроцессора загружают без всяких преобразований.
Денормализованные вещественные числа
Денормализованныевещественные числа — это числа, которые меньше минимального нормализованного числа для каждого вещественного формата. Поясним природу денормализованных чисел с использованием числовой шкалы. Например, для вещественного числа в расширенном формате диапазон представимых значений в сопроцессоре на числовой шкале будет выглядеть так, как показано на рис. 17.11.
Нормализованные вещественные числав расширенном формате
ИСТИННЫМ нулем и минимально представимым нормализованным числом есть еще бесконечное количество очень маленьких чисел. Это и есть так называемые денор- мализованные числа. Они имеют нулевой порядок и ненулевую мантиссу. Диапазон представимых в сопроцессоре денормализованных чисел не безграничен, так как количество разрядов мантиссы ограниченно (рис. 17.12).
Вопрос о том, каким образом сопроцессор реагирует на появление денормали- зованных чисел, будет рассмотрен в разделе «Исключения сопроцессора и их обработка». При формировании денормализованного значения в некотором регистре стека в соответствующем этому регистру теге регистра TWR формируется специальное значение (10).
Нуль
Нультакже относят к специальным численным значениям. Это делается из-за того, что это значение особо выделяется среди корректных вещественных значений, формируемых как результат работы некоторой команды. Более того, нуль может формироваться как реакция сопроцессора на определенную вычислительную ситуацию.
Значение истинного нуля может иметь знак (рис. 17.13), что, впрочем, не влияет на его восприятие командами сопроцессора. Если необходимо определить знак нуля, то используйте команду FXAM. В результате работы этой команды в бит С1 регистра SWR заносится знак операнда. При загрузке нуля в регистр стека в соответствующем теге регистра TWR формируется специальное значение (01).
|о|оо..оо~I ооооооо|
79 7864 63О
00| 0000000|
79 7864 63О
Рис. 17.13. Представление нуля в регистре стека сопроцессора
Значение нуля может быть сформировано в результате возникновения ситуации антипереполнения (см. далее), а также при работе команд с нулевыми операндами.
Бесконечность
Сопроцессор имеет средства в виде специальных битовых значений для представления бесконечности. Формат регистра стека сопроцессора, содержащего значение бесконечности, приведен на рис. 17.14.
I 0|亘11 I 10000ооо|
79 7864 630
] 1|ТГ 11—~] 10000000|
79 7864 63О
Рис. 17.14. Представление значения бесконечности в регистре стека сопроцессора
Из рисунка видно, что значение бесконечности может иметь знак, при этом значения мантиссы и характеристики фиксированы. Именно в этом отличие значения бесконечности от остальных специальных значений.
Среди причин, приводящих к формированию значения бесконечности, можно выделить переполнение и деление на нуль. При формировании значения бесконечности в некотором регистре стека в соответствующем теге регистра TWR формируется специальное значение (10).
Нечисла
К нечислам относятся такие битовые последовательности в регистре стека сопроцессора, которые не совпадают ни с одним из рассмотренных ранее форматов значений. Нечисло должно иметь единичную мантиссу и любую характеристику, кроме 100...00, которая зарезервирована для значения бесконечность. Различают два типа нечисел:
it SNAN (Signaling Non a Number) — сигнальные нечисла;
幾 QNAN (Quiet Non A Number) — спокойные (тихие) нечисла.
Сигнальное нечисло — битовое значение с единичным значением полей характеристики и мантиссой, первый бит которой, следующий за первым единичным значащим битом, равен нулю (рис. 17.15, а). Сопроцессор реагирует на появление этого числа в регистре стека возбуждением исключения недействительной операции. Программисты могут формировать эти числа в регистре стека сопроцессора преднамеренно, например, чтобы искусственно возбудить в нужной ситуации указанное исключение. Очевидно, что именно по этой причине данные числа называются сигнальными. Если снять маску у флага недействительной операции в регистре CWR, то будет вызван обработчик, который выполнит заданные программистом действия.
797864630
бх]行二11 —] 11ХХХххх ]
797864630
в1J 11.. 11I 11000000]
79 786463О
Рис. 17.15.Представление нечисел в регистре стека сопроцессора
Спокойное нечисло — битовое значение с единичным значением полей характеристики и мантиссой, первые два бита которой равны единице (рис. 17.15, б).
Сопроцессор самостоятельно не формирует сигнальных чисел, но в качестве реакции на определенные исключения он может формировать спокойные нечис- ла, например нечисло вещественной неопределенности (рис. 17.15, б). Вещественная неопределенность формируется как маскированная реакция сопроцессора на исключение недействительной операции (см. раздел «Исключения сопроцессора и их обработка»). Другие спокойные нечисла могут формироваться после выполнения команд, в которых хотя бы один из операндов был спокойным нечислом. Это может породить «цепную реакцию», ведущую к ошибочному результату. Поэтому в процессе вычислений рекомендуется периодически контролировать результаты исполнения команд на предмет появления спокойных нечисел.
При формировании нечисла в некотором регистре стека в соответствующем теге регистра TWR формируется специальное значение (10).
Неподдерживаемые форматы
Необходимо иметь в виду, что помимо рассмотренных существует довольно много битовых наборов, которые можно представить в расширенном формате вещественного числа. Для большинства их значений формируется исключение недействительной операции.
Система команд сопроцессора
Система команд сопроцессора включает около 80 машинных команд. Рассмотрим их классификацию (рис. 17.16).
Передачи данныхПередачи данных| Команды сопроцессораП| Команды сопроцессораП(сравнения данных(сравнения данныхTpa 吧 енпентные]Tpa 吧 енпентные]IIВещественныеданныеВещественныеданныеЦелочисленныеданныеЦелочисленныеданные—fild—fist(p)—fild—fist(p)ДесятичныеданныеДесятичныеданные—fbld—fbsKp)Загрузка констант—fbld—fbsKp)Загрузка констант—fldz-fld1—fldpi-fld!2t- fldl2e—L-fldln2—fldz-fld1—fldpi-fld!2t- fldl2e—L-fldln2ОбменfxchОбменfxchВещественныеданныеВещественныеданныеLfcomZfcomp(p) L fucom(p)(pp)LfcomZfcomp(p) L fucom(p)(pp)Целочисленные данныеЦелочисленные данныеficom(p)ficom(p)Анализ st(O)Анализ st(O)С нулемLftstС нулемLftstУсловное сравнение (Pentium II/III)Условное сравнение (Pentium II/III)fcomifcomipfcomifcomip——Условная пересылка (Pentium II/III) 卜fcomi ^fcomip fucomiУсловная пересылка (Pentium II/III) 卜fcomi ^fcomip fucomi—J Деление Lfdiv(r)/fdiv(r)p—J Деление Lfdiv(r)/fdiv(r)pВещественныеВещественныеданныеданныеI Сложение口[fadd(p) :I Вычитание二4subfr)/sub(r)pУмножений IL fmul(p)I Сложение口[fadd(p) :I Вычитание二4subfr)/sub(r)pУмножений IL fmul(p)ЦелочисленныеданныеW Сложение二I-TВычитаниеLfisub(r): I—I ,¥мна;к^и 包] I Lfjmul*-l Деление口[gdiv(ij—ВспомогательныеарифметическиекомандыЦелочисленныеданныеW Сложение二I-TВычитаниеLfisub(r): I—I ,¥мна;к^и 包] I Lfjmul*-l Деление口[gdiv(ij—Вспомогательныеарифметическиекоманды-feqrt — fprem-tabs — fpreml-fchs — fscaleL fxtract — frndint-feqrt — fprem-tabs — fpreml-fchs — fscaleL fxtract — frndintВычисления тригонометрических функций нСинус|I Lfsin Hsincos l■"-П КосинусII g8$ _ I~"] ТангенсII Lfptan 二 T Арктангенс ILfpatanВычислениелогарифмовм степенейВычисления тригонометрических функций нСинус|I Lfsin Hsincos l■"-П КосинусII g8$ _ I~"] ТангенсII Lfptan 二 T Арктангенс ILfpatanВычислениелогарифмовм степенейTdl2t -fldl2e -fldlg2 -fldln2 -f2xm1Tdl2t -fldl2e -fldlg2 -fldln2 -f2xm1Управления ]1Управления ]1fldcw/f(n)stcw f(n)stswfrstorfldcw/f(n)stcw f(n)stswfrstorРаботасо стекомrfincstp/fdecstp L ffreeПереключениережимовP fsetpm/frstpmРаботасо стекомrfincstp/fdecstp L ffreeПереключениережимовP fsetpm/frstpmfnop -fwait • 」fnop -fwait • 」
Передачи данных
Передачи данных
| Команды сопроцессораП
| Команды сопроцессораП
(сравнения данных
(сравнения данных
Tpa 吧 енпентные]
Tpa 吧 енпентные]
I
I
Вещественные
данные
Вещественные
данные
Целочисленные
данные
Целочисленные
данные
—fild
—fist(p)
—fild
—fist(p)
Десятичныеданные
Десятичныеданные
—fbld
—fbsKp)
Загрузка констант
—fbld
—fbsKp)
Загрузка констант
—fldz
-fld1
—fldpi
-fld!2t
- fldl2e
—
L-fldln2
—fldz
-fld1
—fldpi
-fld!2t
- fldl2e
—
L-fldln2
Обмен
fxch
Обмен
fxch
Вещественныеданные
Вещественныеданные
LfcomZfcomp(p) L fucom(p)(pp)
LfcomZfcomp(p) L fucom(p)(pp)
Целочисленные данные
Целочисленные данные
ficom(p)
ficom(p)
Анализ st(O)
Анализ st(O)
С нулем
Lftst
С нулем
Lftst
Условное сравнение (Pentium II/III)
Условное сравнение (Pentium II/III)
fcomi
fcomip
fcomi
fcomip
—
—
Условная пересылка (Pentium II/III) 卜fcomi ^fcomip fucomi
Условная пересылка (Pentium II/III) 卜fcomi ^fcomip fucomi
—J Деление Lfdiv(r)/fdiv(r)p
—J Деление Lfdiv(r)/fdiv(r)p
Вещественные
Вещественные
данные
данные
I Сложение口
[fadd(p) :
I Вычитание二
4subfr)/sub(r)p
Умножений I
L fmul(p)
I Сложение口
[fadd(p) :
I Вычитание二
4subfr)/sub(r)p
Умножений I
L fmul(p)
Целочисленныеданные
W Сложение二
I-TВычитание
Lfisub(r): I—I ,¥мна;к^и 包] I Lfjmul
*-l Деление口
[gdiv(ij—
Вспомогательныеарифметическиекоманды
Целочисленныеданные
W Сложение二
I-TВычитание
Lfisub(r): I—I ,¥мна;к^и 包] I Lfjmul
*-l Деление口
[gdiv(ij—
Вспомогательныеарифметическиекоманды
-feqrt — fprem
-tabs — fpreml
-fchs — fscale
L fxtract — frndint
-feqrt — fprem
-tabs — fpreml
-fchs — fscale
L fxtract — frndint
Вычисления тригонометрических функций нСинус|
I Lfsin Hsincos l■"-П КосинусI
I g8$ _ I~"] ТангенсI
I Lfptan 二 T Арктангенс I
Lfpatan
Вычислениелогарифмовм степеней
Вычисления тригонометрических функций нСинус|
I Lfsin Hsincos l■"-П КосинусI
I g8$ _ I~"] ТангенсI
I Lfptan 二 T Арктангенс I
Lfpatan
Вычислениелогарифмовм степеней
Tdl2t -fldl2e -fldlg2 -fldln2 -f2xm1
Tdl2t -fldl2e -fldlg2 -fldln2 -f2xm1
Управления ]1
Управления ]1
fldcw/f(n)stcw f(n)stsw
frstor
fldcw/f(n)stcw f(n)stsw
frstor
Работа
со стеком
rfincstp/fdecstp L ffree
Переключениережимов
P fsetpm/frstpm
Работа
со стеком
rfincstp/fdecstp L ffree
Переключениережимов
P fsetpm/frstpm
fnop -
fwait • 」
fnop -
fwait • 」
Рис. 17.16. Функциональная классификация команд сопроцессора
Мнемоническое обозначение команд сопроцессора характеризует особенности их работы и в связи с этим может представлять определенный интерес. Цоэтому коротко рассмотрим основные моменты образования названий команд.
Я Все мнемонические обозначения начинаются с символа F (Float).
繼 Вторая буква мнемонического обозначения определяет тип операнда в памяти, с которым работает команда:
I — целое двоичное число;
В — целое десятичное число;
D отсутствие буквы — вещественное число.
9 Последняя буква Р в мнемоническом обозначении команды означает, что последним действием команды обязательно является извлечение операнда из стека. Последняя или предпоследняя буква R (reversed) в мнемоническом обозначении команды означает реверсивное следование операндов при выполнении команд вычитания и деления, так как для них важен порядок следования операндов. Полезной может оказаться и информация о машинных форматах команд сопроцессора. Если давать им общую характеристику, то важно отметить, что в целом система команд сопроцессора отличается большой гибкостью в выборе вариантов задания команд, реализующих определенную операцию, и их операндов. Минимальная длина команды сопроцессора — 2 байта. При обсуждении команд в тексте данной главы будет приводиться лишь их схема. Детально машинное представление команд сопроцессора и сами команды описаны в приложении.
Методика написания программ для сопроцессора имеет свои особенности. Главная причина здесь — в стековой организации сопроцессора. Для того чтобы написать программу для вычисления некоторого выражения, его необходимо предварительно преобразовать в удобный для программирования сопроцессора вид. Процесс преобразования напоминает подготовку выражения для метода трансляции, основанного на использовании обратной польской записи (ПОЛИЗ). Рассмотрим суть ПОЛИЗ на примере вычисления выражения
a +b 'C - d / { + b).
Графически это выражение представляется в виде дерева (рис. 17.17)..
Рис. 17.17. Представление выражения в виде дерева
Листья ветвей дерева соответствуют операндам, а узлы — операциям. Пусть началом обхода будет лист самой левой ветви дерева. Тогда для получения обратной польской записи выражения необходимо двигаться по дереву слева направо, при этом узлы должны просматриваться только после обхода всех исходящих из него ветвей. В результате выражение будет выглядеть так:
abc х + dab + / -
Другое название такой формы записи — постфиксная запись, Это означает, что знак операции записывается после операндов, участвующих в операции. Постфиксная запись позволяет вычислять выражения за один проход с учетом приоритета арифметических операций.
Алгоритм вычисления выражений в постфиксной записи имеет следующий вид.
Выбрать очередной символ записи выражения в форме ПОЛ ИЗ.
Если очередной выбранный символ — операнд, то поместить его в стек, после чего вернуться к шагу
Если очередной выбранный символ — знак операции, то выполнить ее с одним или двумя операндами на вершине стека. Результат операции необходимо поместить обратно на вершину стека.
Если в исходной записи выражения в форме ПОЛИЗ еще остались символы, то вернуться к шагу 1, иначе — на вершине стека получить результат вычисления выражения.
При разработке программ необходимо учитывать следующие факторы:
綫 ограниченность глубины стека сопроцессора;
В: несовпадение форматов операндов;
в отсутствие поддержки на уровне команд сопроцессора некоторых операций, таких как возведение в степень, вычисление тригонометрических функций.
Для решения этих проблем вам придется разрабатывать дополнительный программный код, отклоняясь от классической (последовательной) обработки арифметических выражений, представленных в форме ПОЛИЗ. Однако стержнем программы останется строка в форме ПОЛИЗ.
Среди файлов к данной главе находятся файлы программы, с помощью которой вы можете облегчить себе задачу преобразования простого арифметического выражения в форму ПОЛИЗ. Она поддерживает ограниченное количество операций, но их набор вы можете расширить, дополнив конфигурационный файл polis.cfg и исходный текст программы.
Здесь возникает интересная алгоритмическая задача. В чем ее суть? Для тех операций, которые реализованы на уровне команд сопроцессора, особых проблем нет. И наоборот, для выражений, содержащих операции, не реализованные на уровне команд сопроцессора, возникает проблема преобразования исходной строки в форму ПОЛИЗ. В качестве примера можно привести такую распространенную операцию, как возведение в степень. В соответствующей программе, которую мы рассмотрим чуть позже, реализуется ряд операций, необходимых для возведения в степень. То есть в выражении, преобразованном в форму ПОЛИЗ, будут записаны некоторые операции, отсутствующие в исходной записи этого выражения. Это означает, что при формировании строки ПОЛИЗ программа должна выполнять неявную подстановку дополнительных операндов и операций в соответствии с некоторой формулой приведения.
Команды передачи данных
Группа команд передачи данных предназначена для организации обмена между регистрами стека, вершиной стека сопроцессора и ячейками оперативной памяти.
Команды этой группы имеют такое же значение для программирования сопроцессора, как команда — для программирования основного процессора. С помощью команд передачи данных осуществляются все перемещения значений операндов в сопроцессор и из него. По этой причине для каждого из трех типов данных, с которыми может работать сопроцессор, существует своя подгруппа команд передачи данных. Собственно, на этом уровне все его умения по работе с различными форматами данных и заканчиваются. Главной функцией всех команд загрузки данных в сопроцессор является преобразование данных к единому представлению в виде вещественного числа расширенного формата. Это же касается и обратной операции — сохранения в памяти данных из сопроцессора.
Команды передачи данных можно разделить на следующие группы:
команды передачи данных в вещественном формате;
команды передачи данных в целочисленном формате;
команды передачи данных в десятичном формате.
Далее перечислены команды передачи данных в вещественном формате.
FLD источник — загрузка вещественного числа из области памяти на вершину стека сопроцессора.
приемник — сохранение вещественного числа из вершины стека сопроцессора в память. Как следует из анализа мнемокода команды (отсутствует символ Р), сохранение числа в памяти не сопровождается выталкиванием его из стека, то есть текущая вершина стека сопроцессора не меняется (поле ТОР не меняется).
FSTP приемник — сохранение вещественного числа из вершины стека сопроцессора в память. В отличие откоманды, в конце мнемонического
обозначения данной команды присутствует символ Р, что означает выталкивание вещественного числа из стека после его сохранения в памяти. Команда изменяет поле ТОР, увеличивая его на единицу. Вследствие этого вершиной стека становится следующий больший по своемуномеру регистр стека
сопроцессора.
Далее перечислены команды передачи данных в целочисленном формате.
ш FILD источник — загрузка целого числа из памяти на вершину стека сопроцессора.
FIST приемник — сохранение целого числа из вершины стека сопроцессора в память. Сохранение целого числа в памяти не сопровождается выталкиванием его из стека, то есть текущая вершина стека сопроцессора не изменяется.
приемник — сохранение целого числа из вершины стека в память. Аналогичноранее о команде FSTP,действием команды является выталкивание числа из стека спреобразованием его в целое
значение.
И наконец, осталось перечислить команды передачи данных в десятичном формате.
FBLD источник — загрузка десятичного числа из памяти на вершину стека сопроцессора.
в FBSTP приемник — сохранение десятичного числа из вершины стека сопроцессора в области памяти. Значение выталкивается из стека после преобразования его в формат десятичного числа. Заметьте, что для десятичных чисел нет команды сохранения значения в памяти без выталкивания из стека.
К группе команд передачи данных можно отнести также команду обмена вершины регистрового стека ST(O) с любым другим регистром стека сопроцессора ST⑴: fxch st(i)
Действие команд загрузки FLD, FILD и FBLD можно сравнить с командой PUSH основного процессора. Аналогично ей (PUSH уменьшает значение в регистре SP) команды загрузки сопроцессора перед сохранением значения в регистровом стеке сопроцессора вычитают из содержимого поля ТОР регистра состояния SWR единицу. Это означает, что вершиной стека становится регистр с физическим номером на единицу меньше. При этом возможно переполнение стека. Так как стек сопроцессора состоит из ограниченного числа регистров, то в него может быть записано максимум восемь значений. Из-за кольцевой организации стека девятое записываемое значение затирает первое. Программа должна иметь возможность обработать такую ситуацию. По этой причине почти все команды, помещающие свой операнд в стек сопроцессора, после уменьшения значения поля ТОР проверяет регистр — кандидат на новую вершину стека — на предмет его занятости. Для анализа этой и подобных ситуаций используется регистр TWR, содержащий слово тегов (см. рис. 17.1). Наличие регистра тегов в архитектуре сопроцессора позволяет освободить программиста от разработки сложной процедуры распознавания содержимого регистров сопроцессора и дает самому сопроцессору возможность фиксировать определенные ситуации, например попытку чтения из пустого регистра или запись в непустой регистр. Возникновение таких ситуаций фиксируется в регистре состояния SWR (см. рис. 17.2), предназначенном для сохранения общей информации о сопроцессоре. Используя специальные команды сопроцессора, можно извлечь из него или, напротив, записать в него информацию (см. далее подраздел «Команды управления сопроцессором» данного раздела).
В качестве примера определим несколько констант и выполним их пересылки между оперативной памятью и регистрами сопроцессора, а также между регистрами стека. При этом будем следить за состоянием стека сопроцессора. В ходе реализации этих операций вполне закономерно возникает вопрос о том, имеются ли какие-либо средства для наблюдения за состоянием регистров сопроцессора, аналогично тому, как это делалось при работе с основным процессором. Для него, как вы помните, мы использовали отладчик Turbo Debuger. Оказывается, что далеко ходить не нужно. Тот же Turbo Debuger предоставляет нам эту возможность. Чтобы не нарушать структуру общего изложения, материал по Turbo Debuger вынесен в отдельный раздел «Использование отладчика». С ним можно ознакомиться прямо сейчас, после чего вернуться обратно. При наличии у вас другого отладчика, отличного от Turbo Debuger, следует обратиться к его описанию и поискать аналогичные средства.
Введите программу из листинга 17.1. Выполните ее под управлением отладчика и проследите за тем, как меняется состояние регистров сопроцессора в окне Numeric processor.
ЛистингИсследование команд передачи данных
;--i—fpu.asm*—
.586p
masm
modelusel6 small
100h
.data
dt 43567
x dw 3
у real dq 34e7
dt st dt 0
; сегмент данных
;ch dt=00 00 00 00 00 00 00 04 35 67
;x=00 03
;y_real=41 b4 43 fd 00 00 00 00
x_stdw 0
y_real_stdq 0
.code
main proc
movax,
movds, ax
ch dt
x
у real
st(2) fbstpch_dt_st
процедуры main
;st(0)=43S67
;st(l)=43567, st(0)=3
;st(2)=43567, st(l)=3,
;st(2)=340000000, st(l)=3, st(0)=43567
;st(l)=340000000,st(0)=3
dt st=00 00 00 00 00 00 00 04 35 67
x_st
fstpy_real_st
exit:
;st(0)=340000000, x st=00 03
;y realb4 43 fd 00 00 00 00
mov ax,
int
mainendp
end main
Исследовать работу этой программы полезно с открытым окном Dump, так как при этом хорошо видны различия в представлении типов данных.
Команды загрузки констант
Основным назначением сопроцессора является поддержка вычислений с плавающей точкой. В математических вычислениях довольно часто встречаются предопределенные константы, и сопроцессор хранит значения некоторых из них. Другая причина использования этих констант заключается в том, что для определения их в памяти (в расширенном формате) требуется 10 байт, а это для хранения, например, единицы расточительно (сама команда загрузки константы, хранящейся в сопроцессоре, занимает два байта). В формате, отличном от расширенного, эти константы хранить не имеет смысла, так как теряется время на их преобразование в тот же расширенный формат. Для каждой предопределенной константы существует специальная команда, которая производит загрузку ее на вершину регистрового стека сопроцессора:
FLDZ — загрузка нуля;
FLD1 — загрузка единицы;
FLDPI — загрузка числа
FLDL2T — загрузка двоичного логарифма десяти;
загрузка двоичного логарифма экспоненты (числа
FLDLG2 — загрузка десятичного логарифма двойки;
загрузка натурального логарифма двойки.
Команды сравнения данных
Команды сравнения данных сравнивают значение числа в вершине стека и операнда, указанного в команде.
纓 FCOM [операнд_в_памяти] — команда без операндов сравнивает два значения: одно находится в регистре ST(O), другое в регистре ST(1). Если указан операнд [опе- ранд_в_памяти], то сравнивается значение в регистре ST(O) стека сопроцессора со значением в памяти.
II FCOMP операнд — команда сравнивает значение в вершине стека сопроцессора ST(O) со значением операнда, который находится в регистре или в памяти. Последним действием команды является выталкивание значения из ST(O).
綫 FCOMPP операнд — команда аналогична по действию команде FCOM без операндов, но последним ее действием является выталкивание из стека значений обоих регистров, ST(O) и ST(1).
я FICOM операнд_в_памяти — команда сравнивает значение в вершине стека сопроцессора ST(0) с целым операндом в памяти. Длина целого операнда — 16 или 32 бита, то есть это целое слово и короткое целое (см. табл. 17.1).
II FICOMP операнд — команда сравнивает значение в вершине стека сопроцессора 51(0) с целым операндом в памяти. После сравнения и установки битов C3...C0 команда выталкивает значение из ST(O). Длина целого операнда — 16 или 32 бита, то есть это целое слово и короткое целое (см. табл. 17.1).
9 FTST — команда не имеет операндов и сравнивает значения в ST(O) с нулем (зна・・ чением 00).
Предыдущие команды сравнения работают корректно, если операнды в них являются целыми или вещественными числами. Когда один из операндов оказывается нечислом, то фиксируется исключение недействительной ситуации, а коды условияC3...C0соответствуют исключительной ситуации несравнимых или неупорядоченных операндов. Само же действие сравнения не производится. Процессор предоставляет три команды, позволяющие все же произвести сравнение таких операндов, но как вещественных чисел без учета их порядков.
в; FUCOM st(i) — команда сравнивает значения (без учета их порядков) в регистрах стека сопроцессора "(0) и sr⑴.
9 FUCOMP st(i) — команда сравнивает значения (без учета их порядков) в регистрах стека сопроцессора ST(0) и ST⑴. Последним действием команды является выталкивание значения из вершины стека.
Я FUCOMPP st(i) — команда сравнивает значения (без учета их порядков) в регистрах стека сопроцессора ST(0) и ST⑴. Последние два действия команды одинаковы — выталкивание значения из вершины стека.
В результате работы команд сравнения в регистре состояния устанавливаются следующие значения битов кодаусловия СЗ, С2, СО:
鑽 если ST(O) > операнда, то 000;
験 если ST(0) < операнда, то 001;
если ST(O) = операнду, то 100;
если операнды неупорядочены, то 111.
Для того Чтобы получить возможность реагировать на эти коды командами условного перехода основного процессора (вспомните, что они реагируют на флаги в EFLAGS), нужно как-то записать сформированные биты условия СЗ, С2, СО в регистр E FLAGS. В системе команд сопроцессора существует команда FSTSW, которая позволяет запомнить слово состояния сопроцессора в регистре АХ или ячейке памяти. Далее значения нужных битов извлекаются и анализируются командами основного процессора. Например, запись старшего байта слова состояния, в котором находятся биты C0...C3, в младший байт регистра EFLAGS/FLAGS осуществляется командой SAHF. Эта команда записывает содержимое АН в младший байт регистра EFLAGS/FLAGS. После этого бит СО записывается на место флага CF, C2 — на место PF, СЗ — на место ZF. Бит С1 выпадает из общего правила, так как в регистре флагов на месте соответствующего ему бита находится единица. Анализ этого бита нужно проводить с помощью логических команд основного процессора. Зная все это, вам остается, исходя из особенностей своего алгоритма, применять те команды условного перехода, которые анализируют состояние указанных флагов.
В качестве иллюстрации рассмотрим программу разбиения массива вещественных чисел в формате двойного слова на два массива. В первый массив поместим все элементы, которые больше или равны нулю, а во второй — меньше нуля (листинг 17.2).
Листинг 17.2. Исследование команд сравнения данных
.586p masm model.stack .data.586p masm model.stack .datausel6 small100h;сегмент данныхusel6 small100h;сегмент данных;исходный массивmas dd -2.0, 45.7, -9.4, 7.3, 60.3, -58.44, 890e7, -98746e3;исходный массивmas dd -2.0, 45.7, -9.4, 7.3, 60.3, -58.44, 890e7, -98746e3mas_h_0 dd i_mas_h_0 mas_l20 dd i nias-l 0mas_h_0 dd i_mas_h_0 mas_l20 dd i nias-l 0. code mainmov mov хог mov. code mainmov mov хог mov8 dup (0) dd 08 dup (0) dd 08 dup (0) dd 08 dup (0) dd 0ргос ax, @data ds, ax esi, esi ex, 8ргос ax, @data ds, ax esi, esi ex, 8fi ni tfldzcycl:fcom mas[esi *4]fi ni tfldzcycl:fcom mas[esi *4]fstsw axsahfjp errorjc И1_0;переход,fstsw axsahfjp errorjc И1_0;переход,;массив значений, больше либо равных 0, ;текущий индекс в mas_h_0:массив значений меньших 0;текущий индекс в mas_l_0;массив значений, больше либо равных 0, ;текущий индекс в mas_h_0:массив значений меньших 0;текущий индекс в mas_l_0;начало процедуры main;начало процедуры main;счетчик цикловприведение сопроцессора в начальное состояние ;загрузка нуля в st(0);счетчик цикловприведение сопроцессора в начальное состояние ;загрузка нуля в st(0)сравнение нуля в st(0);с очередным элементом массива mas;сохранение swr в регистре ах;запись swr->ax-> регистр флагов;переход по "плохому" операнду в команде fcomсравнение нуля в st(0);с очередным элементом массива mas;сохранение swr в регистре ах;запись swr->ax-> регистр флагов;переход по "плохому" операнду в команде fcomесли mas[esi*4]>= 0если mas[esi*4]>= 0;пересылка операнда mas[esi *4], меньшего;пересылка операнда mas[esi *4], меньшегоmov mov mov inchi_0:mov mov mov inchi_0:eax, mas[esi*4]edi, i_mas_l_0 mas_l_0 [edf*4], eax i_nias_l_0cycl_bsteax, mas[esi*4]edi, i_mas_l_0 mas_l_0 [edf*4], eax i_nias_l_0cycl_bst(mas[esi*4] >=st(0))0. в массив mas_l 0(mas[esi*4] >=st(0))0. в массив mas_l 0;пересылка операнда mas[esi *4], большего mov eax, mas [esi*4];пересылка операнда mas[esi *4], большего mov eax, mas [esi*4]или равного 0, в массив mas_h_0продолжение夕или равного 0, в массив mas_h_0продолжение夕
.586p masm model
.stack .data
.586p masm model
.stack .data
usel6 small
100h
;сегмент данных
usel6 small
100h
;сегмент данных
;исходный массив
mas dd -2.0, 45.7, -9.4, 7.3, 60.3, -58.44, 890e7, -98746e3
;исходный массив
mas dd -2.0, 45.7, -9.4, 7.3, 60.3, -58.44, 890e7, -98746e3
mas_h_0 dd i_mas_h_0 mas_l20 dd i nias-l 0
mas_h_0 dd i_mas_h_0 mas_l20 dd i nias-l 0
. code main
mov mov хог mov
. code main
mov mov хог mov
8 dup (0) dd 0
8 dup (0) dd 0
8 dup (0) dd 0
8 dup (0) dd 0
ргос ax, @data ds, ax esi, esi ex, 8
ргос ax, @data ds, ax esi, esi ex, 8
fi ni t
fldz
cycl:
fcom mas[esi *4]
fi ni t
fldz
cycl:
fcom mas[esi *4]
fstsw ax
sahf
jp error
jc И1_0;переход,
fstsw ax
sahf
jp error
jc И1_0;переход,
;массив значений, больше либо равных 0, ;текущий индекс в mas_h_0
:массив значений меньших 0
;текущий индекс в mas_l_0
;массив значений, больше либо равных 0, ;текущий индекс в mas_h_0
:массив значений меньших 0
;текущий индекс в mas_l_0
;начало процедуры main
;начало процедуры main
;счетчик циклов
приведение сопроцессора в начальное состояние ;загрузка нуля в st(0)
;счетчик циклов
приведение сопроцессора в начальное состояние ;загрузка нуля в st(0)
сравнение нуля в st(0)
;с очередным элементом массива mas
;сохранение swr в регистре ах
;запись swr->ax-> регистр флагов
;переход по "плохому" операнду в команде fcom
сравнение нуля в st(0)
;с очередным элементом массива mas
;сохранение swr в регистре ах
;запись swr->ax-> регистр флагов
;переход по "плохому" операнду в команде fcom
если mas[esi*4]>= 0
если mas[esi*4]>= 0
;пересылка операнда mas[esi *4], меньшего
;пересылка операнда mas[esi *4], меньшего
mov mov mov inc
hi_0:
mov mov mov inc
hi_0:
eax, mas[esi*4]
edi, i_mas_l_0 mas_l_0 [edf*4], eax i_nias_l_0
cycl_bst
eax, mas[esi*4]
edi, i_mas_l_0 mas_l_0 [edf*4], eax i_nias_l_0
cycl_bst
(mas[esi*4] >=st(0))
0. в массив mas_l 0
(mas[esi*4] >=st(0))
0. в массив mas_l 0
;пересылка операнда mas[esi *4], большего mov eax, mas [esi*4]
;пересылка операнда mas[esi *4], большего mov eax, mas [esi*4]
или равного 0, в массив mas_h_0
продолжение夕
или равного 0, в массив mas_h_0
продолжение夕
Листинг17.2(продолжение)
том edi, i_mas_h_0
mov raas_h_0[edi*4], eax
1nc i mas h 0
cyct^bst:~
inc si
loop cycl error:
:здесь можно вывести сообщение об ошибке в задании операндов exit:
mov ax, 4c00h
int 21h
main endp end main
К группе команд сравнения данных логично отнести и команду FXAM, которая анализирует операнд в вершине стека сопроцессора ST(O) и формирует значение битов СО, Cl,С2, СЗ в регистре состояния сопроцессораSWR. По состоянию этих битов можно судить:
о знаке мантиссы — знаковый бит операнда в ST(O) заносится в бит СО регистра SWR;
經 корректности записи вещественного числа в ST(0) — идентифицируются пустой регистр, корректное вещественное число, нечисло и неизвестный формат;
Я типе специального численного значения: бесконечность, нуль, денормализован- ный операнд.
Подробное описание команды FXAM можно найти в приложении.
Арифметические команды
Команды сопроцессора, входящие в группу арифметических команд, реализуют четыре основные арифметические операции — сложение, вычитание, умножение и деление. Имеется также несколько дополнительных команд, предназначенных для повышения эффективности использования основных арифметических команд. С точки зрения типов операндов арифметические команды сопроцессора можно разделить на команды, работающие с вещественными и целыми числами. Рассмотрим их.
Целочисленные арифметические команды
Целочисленные арифметические команды предназначены для работы на тех участках вычислительных алгоритмов, где в качестве исходных данных используются целые числа в памяти в формате слово и короткое слово, имеющие размерность 16и 32 бита.
При рассмотрении целочисленных арифметических команд обратите внимание на большую гибкость задания операндов в этих командах.
FIADD источник — команда складывает значения ST(O) и целочисленного источника, в качестве которого выступает 16- или 32-разрядный операнд в памяти. Результат сложения запоминается в регистре стека сопроцессора ST(O).
趣 FISUB источник — команда вычитает значение целочисленного источника из ST(O). Результат вычитания запоминается в регистре стека сопроцессора ST(0). В качестве источника выступает 16-или 32-разрядный целочисленный операнд в памяти.
鑒気 FIMUL источник — команда умножает значение целочисленного источника на содержимое ST(O). Результат умножения запоминается в регистре стека сопроцессора ST(O). В качестве источника выступает 16- или 32-разрядный целочисленный операнд в памяти.
綫 FIDIV источник — команда делит содержимое ST(O) на значение целочисленного источника. Результат деления запоминается в регистре стека сопроцессора ST(O).
В качестве источника выступает 16- или 32-разрядный целочисленный операнд в памяти.
Для команд, реализующих арифметические действия деления и вычитания, важен порядок расположения операндов. По этой причине система команд сопроцессора содержит соответствующие реверсивные команды, повышающие удобство программирования вычислительных алгоритмов. Чтобы отличить эти команды от обычных команд деления и вычитания, их мнемокоды оканчиваются символом R.
FISUBR источник — команда вычитает значение ST(O) из целочисленного источника. Результат вычитания запоминается в регистре стека сопроцессора ST(O). В качестве источника выступает 16- или 32-разрядный целочисленный операнд в памяти.
幾 FIDIVR источник — команда делит значение целочисленного источника на содержимое ST(O). Результат деления запоминается в регистре стека сопроцессора ST(O). В качестве источника выступает 16- или 32-разрядный целочисленный операнд в памяти.
Рассмотрим пример программы вычисления значения и (листинг 17.3):
х- и
иесли а工0;
а
и = х + у, если а = 0.
Все переменные х, у и а целого типа в формате слова. Результат необходимо сохранить в ячейке памяти в формате десятичного числа.
Листинг 17.3. Исследование целочисленных арифметических команд
,86p
masm
model usel6 small
.stack 100h
.data ;сегмент данных
исходный массив
adw0
хdw8
уdw4
udt0
.code
mainproc
mov ax, @data
mov ds, ax
finit ; приведение сопроцессора в начальное состояние
fild а ;загрузка значение а в st(0)
fxam;определяем тип а
fstsw ах сохранение swr в регистре ах sahf
;запись swr->ax-> регистр флагов
Листинг 17.3(продолжение)
j c no_null jp no二null jnz no二null
;вычисление формулы u=x+y: f i Id x fiaddу
fbstpu
jmpexit
no_null:
;вычисление формулы u=(x-y)/а: f ild x
fisub у f d i v a fbstp u exi t:
movax, 4c00h
int21h
mainendp
end main
Вещественные арифметические команды
Схема расположения операндов вещественных команд традиционна для команд сопроцессора. Один из операндов располагается в вершине стека сопроцессора — регистре ST(O), куда после выполнения команды записывается и результат, а второй операнд может быть расположен либо в памяти, либо в другом регистре стека сопроцессора. Допустимыми типами операндов в памяти являются все перечисленные ранее вещественные форматы за исключением расширенного.
В отличие от целочисленных арифметических команд, вещественные арифметические команды допускают большее разнообразие в сочетании местоположения операндов и самих команд для выполнения конкретного арифметического действия. Так, например, можно выделить три возможных варианта команды сложения. В дополнение к этим трем вариантам существует еще одна команда сложения, производящая дополнительное действие — удаление значения из стека.
FADD — команда складывает значения в ST(0) и ST(1). Результат сложения запоминается в регистре стека сопроцессора ST(0).
緣 FADD источник — команда складывает значения ST(O) и источника, представляющего адрес ячейки памяти. Результат сложения запоминается в регистре стека сопроцессора ST(0).
FADD st ⑴,st — команда складывает значение в регистре стека сопроцессора ST(i) со значением в вершине стека ST(0). Результат сложения запоминается в регистре ST⑴.
FADDP St ⑴,st — команда производит сложение вещественных операндов аналогично команде FADD St ⑴,st, однако последним действием команды является выталкивание значения из вершины стека сопроцессора ST(0). Результат сложения остается в регистре ST(i-l).
Для выполнения операции вычитания также имеется большой набор команд.
FSUB — команда вычитает значение в ST(1) из значения в ST(O). Результат вычитания запоминается в регистре стека сопроцессора ST(0).
и FSUB источник — команда вычитает значение источника из значения в ST(0). Источник представляет адрес ячейки памяти, содержащей допустимое вещественное число. Результат сложения запоминается в регистре стека сопроцессора ST(O).
и FSUB st(i),st — команда вычитает значение в вершине стека ST(O) из значения в регистре стека сопроцессора ST(i). Результат вычитания запоминается в регистре стека сопроцессора ST⑴.
* FSUBP st(i),st — команда вычитает вещественные операнды аналогично команде FSUB st(i),st. Последним действием команды является выталкивание значения из вершины стека сопроцессора ST(O). Результат вычитания остается в регистре ST(i-l).
Для удобства группа команд вычитания вещественных чисел дополнена командами реверсивного вычитания.
я FSUBR st(i),st — команда вычитает значение в вершине стека ST(O) из значения в регистре стека сопроцессора ST(i). Результат вычитания запоминается в вершине стека сопроцессора — регистре ST(O).
FSUBRP st(i),st — команда производит вычитание подобно команде FSUBR st(i),st. Последним действием команды является выталкивание значения из вершины стека сопроцессора ST(O). Результат вычитания остается в регистре ST(i-l).
Изучая команды умножения вещественных операндов, обратите внимание не то, что операнды располагаются исключительно в стеке сопроцессора.
承:FMUL — команда не имеет операндов. Умножает значения в ST(0) на содержимое в ST(1). Результат умножения запоминается в регистре стека сопроцессора ST(0).
II FMUL st(i) — команда умножает значение в ST(O) на содержимое регистра стека ST(i). Результат умножения запоминается в регистре стека сопроцессора ST(O). FMUL st(i),st — команда умножает значения в ST(O) на содержимое произвольного регистра стека sr⑴. Результат умножения запоминается в регистре стека сопроцессора ST⑴.
3殮 FMULP st(i),st — команда производит умножение подобно команде FMUL st(i),st. Последним действием команды является выталкивание значения из вершины стека сопроцессора ST(O). Результат умножения остается в регистре ST(iT).
И наконец, рассмотрим команды, реализующие деление вещественных данных. Подобно командам умножения, операнды этих команд располагаются в стеке сопроцессора:
:綫 FDIV — команда (без операндов) делит содержимого регистра ST(O) на значение регистра сопроцессора ST(1). Результат деления запоминается в регистре стека сопроцессора ST(0).
翁 FDIV st(i) — команда делит содержимое регистра ST(O) на содержимое регистра сопроцессора ST⑴. Результат деления запоминается в регистре стека сопроцессора st(0).
綫 FDIV st(i),st — команда производит деление аналогично команде FDIV st(i), но результат деления запоминается в регистре стека сопроцессора ST(i).
FDIVP st(i),st — команда производит деление аналогично команде FDIV st(i),st. Последним действием команды является выталкивание значения из вершины стека сопроцессора ST(O). Результат деления остается в регистре ST(i-l).
Для реализации деления в сопроцессоре также предусмотрены две реверсивные команды, отличительным признаком которых является наличие символа R в качестве последнего или предпоследнего символа мнемокода:
I! FDIVRst(i),st — команда делит содержимое регистра ST(i) на содержимое вершины регистра сопроцессора ST(O). Результат деления запоминается в регистре стека сопроцессора ST(O).
緻 FDIVRP st(i),st — команда делит содержимое регистра ST(i) на содержимое вершины регистра сопроцессора 5T(0). Результат деления запоминается в регистре стека сопроцессора ST(i), после чего производится выталкивание содержимого sr(o) из стека. Результат деления остается в регистре ST(i-l).
Разработаем программу вычисления факториала числа 10 (листинг 17.4):
Напомним, что вычисление факториала заключается в выполнении последова- тельногоумножения 1 • 2 • 3• (г— 1) • г.
Листинг 17.4. Исследование вещественных арифметических команд
.586p
masm
modelusel6 small
.stack 100h
.data
;сегмент данных
;исходный
массив
i equ 10
уdq 0
.code
mainproc
mov
ax r @data
mov
ds, ax
f init
;приведение сопроцессора в начальное состояние
fldl
;st(0)=l!
fldl
;st(0)=i=l. st(l)=l!
fst
У
mov
ex, i-1
;первый элемент уже вычислили
cycl:
fldl
fadd
Р
fmul
$t(l). St(0)
;st(0) = i=2, 3.. . st(l)=i!
fldl
fdi v
st(0), st(2)
;1/1!
fadd
У
:накопление суммы
fstp
У
;сохранение суммы
loop
cycl
exit:
mov
ах, 4c00h
int
21h
mainendp
end main
Дополнительные арифметические команды
Далее перечислены команды, входящие в группу дополнительных арифметических команд/
FSQRT — вычисление квадратного корня из значения, находящегося в вершине стека сопроцессора — регистре ST(O). Команда не имеет операндов. Результат вычисления помещается в регистр ST(0). Следует отметить, что данная команда обладает определенными достоинствами. Во-первых, результат извлечения достаточно точен, во-вторых, скорость исполнения чуть больше скорости выполнения команды деления вещественных чисел FDIV;
9 FABS — вычисление модуля значения, находящегося в вершине стека сопроцессора — регистре ST(O). Команда не имеет операндов. Результат вычисления помещается в регистр ST(O);
И FCHS — изменение знака значения, находящегося в вершине стека сопроцессора — регистре ST(O). Команда не имеет операндов. Результат вычисления помещается обратно в регистр ST(O). Отличие команды FCHS от команды FABS в том, что команда FCHS только инвертирует знаковый разряд значения в регистре ST⑼, не меняя значения остальных битов. Команда вычисления модуля FABS при наличии отрицательного значения в регистре夕⑼, наряду с инвертированием знакового разряда, выполняет изменение остальных битов значения таким образом, чтобы в ST(O) получилось соответствующее положительное число.
. FXTRACT — команда выделения порядка и мантиссы значения, находящегося в вершине стека сопроцессора — регистре ST(0). Команда не имеет операндов. Результат выделения помещается в два регистра стека — мантисса в ST(O), а порядок в ST(1). При этом мантисса представляется вещественным числом с тем же знаком, что и у исходного числа, и порядком равным нулю. Порядок, помещенный в ST⑴, представляется как истинный порядок, то есть без константы смещения, в виде вещественного числа со знаком и соответствует величине Р формулы(17.1).
На последнюю команду следует обратить особое внимание, так как она позволяет выделить и затем по отдельности проанализировать порядок и мантиссу числа, содержащегося в вершине стека. Для того чтобы разобраться с тонкостями работы этой команды, изучите листинг 17.5. Исходное десятичное число для этой программы равно 45,56. Оно взято из рассмотренного ранее примера по определению десятичного числа в расширенном формате (см. раздел «Форматы данных»).
Листинг 17.5. Исследование работы команды FXTRACT
586p
masm
model usel6 small
;сегмент данных;y_real=4004 b63d 70a3 d70a 3d71;начало процедуры main;сегмент данных;y_real=4004 b63d 70a3 d70a 3d71;начало процедуры main.stack 100h
;сегмент данных
;y_real=4004 b63d 70a3 d70a 3d71
;начало процедуры main
;сегмент данных
;y_real=4004 b63d 70a3 d70a 3d71
;начало процедуры main
.data
m_dt dt 0
ch_dt dt 0
y_real dt 45.56
.code
main proc
mov ax, @data
Листинг 17,5 (продолжение)
mov ds,
ax
fid у real
;st (0)=4004 b63d
70a3
d70a
3d71
(45.56)
fxtract
;st(l)=4001
a000
...
0000
(5),
;st(0)=3fff
b63d
70a3
d70a
3d71
(1.42375)
fstp m_
dt
;st(0)=4001
a000
...
0000
;m_dt=3fff
b63d
70a3
d70a
3d71
(1.42375)
fstp ch
_dt
b63d
70a3
d70a
3d71
(1.42375)
;ch_dt=4001
a000
...
0000
(5)
exi t:
mov ax,
4c00h
int 21h
main endp
end main
Введите эту программу, получите исполняемый модуль и загрузите его в отладчик Turbo Debuger. Раскройте на весь экран окно Numeric processor, чтобы наблюдать за содержимым регистров стека сопроцессора в шестнадцатеричном формате. В пошаговом режиме проследите за теми действиями, которые выполняет команда FXTRACT. Вы обнаружите, что команда FXTRACT выделяет характеристику вещественного числа в расширенном формате по формуле (17.3) вычисляет истинный порядок числа, представляет его как число со знаком и заносит в регистр ST(1). He забывайте, что это порядок нормализованного числа в двоичном формате, поэтому для нашего примера он равен 5 (в формуле (17.2) N = 2). Далее команда FXTRACT формирует нулевой порядок значения в ST(0), занося в поле характеристики значение 3fffh(1638310). На этом ее работа заканчивается. Следующие за FXTRACT команды сохраняют значения мантиссы и порядка в памяти как самостоятельные значения. Восстановить исходное значение можно командой масштабирования FSCALE, которая к тому же позволяет легко производить умножение на целочисленную степень числа 2. Эта команда изменяет порядок значения, находящегося в вершине стека сопроцессора — регистре ST(0), на величину в ST(1). Команда не имеет операндов. Величина в ST(1) рассматривается как число со знаком. Его прибавление к полю порядка вещественного числа в ST(O) означает его умножение на величину 2st(1),то есть ST(0) = ST(0) • 2盟1).
С помощью данной команды удобно масштабировать на степень двойки некоторую последовательность чисел в памяти. Для этого достаточно поочередно загружать числа последовательности в вершину стека, после чего применять команду FSCALE и сохранять значения обратно в памяти. Команда FSCALE по алгоритму работы является обратной команде FXTRACT. Чтобы убедиться в этом, модифицируем листинг 17.5, превратив его в листинг 17.6.
Листинг 17.6.Листинг 17.6.• 586pmasmmodel usel6 small.stack 100h.datay_real dt 45.56. codemain procmov ax, @datamov ds, ax fid y_real• 586pmasmmodel usel6 small.stack 100h.datay_real dt 45.56. codemain procmov ax, @datamov ds, ax fid y_realИсследование работы команды FSCALE
Листинг 17.6.
Листинг 17.6.
• 586p
masm
model usel6 small
.stack 100h
.data
y_real dt 45.56
. code
main proc
mov ax, @data
mov ds, ax fid y_real
• 586p
masm
model usel6 small
.stack 100h
.data
y_real dt 45.56
. code
main proc
mov ax, @data
mov ds, ax fid y_real
;сегмент данных
;y_real=4004 b63d 70a3 d70a 3d71
;начало процедуры main
;st(0)=4004 b63d 70a3 d70a 3d71 (45.56)
fxtract;st(l)=4001 a000 ... 0000 (5)
;st(0)=3fff b63d 70a3 d70a 3d71 (1.42375)
fscale;st(@') = 4004 b63d 70a3 d70a 3d71 (45.56)
exit:,
команда FRNDINT. Эта шине стека сопроцеср Возможны четыре} ся значениями в ДвухВкоманда FRNDINT. Эта шине стека сопроцеср Возможны четыре} ся значениями в ДвухВСопроцессор ммеёг программно-аппаратные средства для округления тех результатов работы команд, которые не могут быть точно представлены. Но операция округления может быть проведена к значению в регистре 51(0) и принудительно, для этого предназначена последняя команда в группе дополнительных команд —
команда FRNDINT. Эта шине стека сопроцеср Возможны четыре} ся значениями в ДвухВ
команда FRNDINT. Эта шине стека сопроцеср Возможны четыре} ся значениями в ДвухВ
команда округляет до целого значение, находящееся в вер- ора — регистре 51(0). Команда не имеет операндов.
режима округления величины в 51(0), которые определяют- азрядном поле RC управляющего регистра сопроцессора. Как
изменить режим округления? С помощью двух команд, FSTCWR и FLDCWR, которые,
соответственно, запис цессора и восстанавл) регистра находится й значениеполяRC.соответственно, запис цессора и восстанавл) регистра находится й значениеполяRC.ывают в память содержимое управляющего регистра сопро- ивают его обратно. Таким образом, пока содержимое этого памяти, вы имеете возможность установить необходимое
соответственно, запис цессора и восстанавл) регистра находится й значениеполяRC.
соответственно, запис цессора и восстанавл) регистра находится й значениеполяRC.
Исследуем работу |команды FRNDINT на следующем примере (листинг 17.7).
Листинг 17.7. Исследованиеработы команды FRNDINT
,586р
masm
model use!6 small
.stack 100h
.data^сегмент данных
meml6 dw 0
y_real dt 10.0
x_3 dd 3.0
.code!
mainprocj начало процедуры main
mov mov
ax, @data ds, ax
fid
y_real
j
fid
x_3
!
fdi v
ist(0)=3.333...33
fstew
meml6
j
and
meml6,
qilOOllllllllllb
fIdcw
meml6
frndint
jrc=0, st(0)=3
fid y_
real
fid x_
3
fdiv
ist(o)=3.333...33
fstcw
me ml 6
and
men)16,
liiieoiiiiiiiiiib
or
meml6,
liiieiiiiiiiinib
fldcw
meml6
!
frndint
;「C=®1, st(0)=3
fid
y_real
fid
x_3
fdiv
;|st(0) = 3.333.. .33
fstcw
meml6
and
meml6,
llillOOllllllllllb
or
meml6,
1Ц1101111111111Ь
fldcw
mem!6
j
frndint
;|rc = 10, st(0)=4
fid
y_real
16 Зак. 256
Листинг (продолжение)
fid
x_3
fdiv
;st(0)=3.333...33
fstcw
meml6
and
meml6,
1111001111111111b
or
meml6,
llllllllllllllllb
fldcw
meml6
frndint
:rc=ll.
exi
mov
ax, 4c00h
int
21h
main
end main
Рассмотрим еще один пример. Разработаем программу вычисления следующего выражения (листинг 17.8):
=-
Результат сохраним в той же ячейке памяти, в которой хранится переменная у, в формате двойного слова.
Листинг 17.8. Вычисление выражения
.586р
masm
modelsmall
100h
данных
данные:
хdd-29e-4
уdq4.6
zdd0
.code
mainproc
mov
mov
сопроцессора в начальное состояние
x ;st(0)=x
fabs;st(0)=|x|
fsqrt
у
придется вычислять через умножение
fstst(l)
f
fst z exit:
mov
int
mainendp
endmain
Как вы знаете, в сопроцессоре нет команды возведения в степень. Далее мы покажем, как можно решить эту проблему.
Команды трансцендентных функций
Сопроцессор имеет ряд команд, предназначенных для вычисления значений тригонометрических функций, таких как синус, косинус, тангенс, арктангенс, а также значений логарифмических и показательных функций. Наличие этих команд значи-
тельно облегчает жи^нь программисту, вынужденному интенсивно заниматься разработкой вычисли|гельных алгоритмов. Выигрыш налицо. Во-первых, отпадает необходимость самом У разрабатывать соответствующие подпрограммы. Во-вторых, точность результатов! выполнения трансцендентных команд очень высока.
Необходимо обратить внимание читателя на то, что значения аргументов в командах, вычисляющий результат тригонометрических функций, должны задаваться в радианах. В связи с ртим приведем правило пересчета. Для нахождения радианной меры угла по его) градусной мере необходимо число градусов умножить на я/180 (=0,017453), ч^тсло минут - на〃(180 • 60) (==0,000291), а число секунд - нал/(180 • 60 • 60) (4,000005) и найденные произведения сложить.
Далее перечислены команды трансцендентных функций.
後 FCOS — команда вычисляет косинус угла, находящийся в вершине стека сопроцессора — регистре ST(0). Команда не имеет операндов. Результат возвращается в регистр ST⑼.
* FSIN — команда вычисляет синус угла, находящийся в вершине стека сопроцессора — регистре St(O). Команда не имеет операндов. Результат возвращается в регистр ST(O).;
哪 FSINCOS — команд^ вычисляет синус и косинус угла, находящиеся в вершине стека сопроцессора — регистре ST(O). Команда не имеет операндов. Результат возвращается в регистрах ST(O) и ST⑴. При этом синус помещается в ST(0), а косинус — в ST⑴•;
FPTAN — команда в(ычисляет частичный тангенс угла, находящийся в вершине стека сопроцессор^ — регистре ST(O). Команда не имеет операндов. Результат возвращается в регистрах ST(O) и ST(1).
緻 FPRATAN — команд^ вычисляет частичный арктангенс угла, находящийся в вершине стека сопроцессора — регистре ST(O). Команда не имеет операндов. Результат возвращается в регистрах ST(0) и ST(1).
Интересна история команды FPTAN. В отличие от команд вычисления синуса и косинуса, появившихся только в системе команд сопроцессора i387, команда FPTAN присутствовала еще ц системе команд сопроцессора i8087. Выполнение ее имело следующую особенность: результат команды возвращался в виде двух значений — в регистрах ST(O) и ST( 1), но ни одно из них не являлось истинным значением тангенса. Истинное знач»нке получается лишь после выполнения операции деления ST(O)/ST(1). Таким образом, для получения тангенса требуется еще команда деления. Зачем это нужнб? Ранее мы упомянули о том, что команды для вычисления синуса и косинуса появились только в сопроцессоре i387, поэтому возникает вопрос о том, как вычислялись значения этих функций в ранних сопроцессорах. Чтобы понять это, введей необходимые обозначения: команда FPTAN вычисляет частичный тангенс 4Hcjia z, значение которого находится в границах 0 < Z V л/4. Результат работы команды FPTAN, как уже было отмечено, размещается в двух местах: х — в регистре ST(P),у — в регистре ST(1). Значения х, у и z таковы, что удовлетворяют соотношению
tgG/2)=y/r
Более того, используя значения х и у, можно получить значения остальных тригонометрических функций. Для этого используются следующие соотношения:
ctg(z/2) = x/y;
sinQ) = (2^/x)/((l + (y/r 冷;COS(Z)= (1 -^/x)2)/(l + @/*)2).
Теперь вам, наверное, понятен термин «частичный» тангенс. К счастью, в процессоре i387 появились самостоятельные команды для вычисления синуса и косинуса, вследствие чего отпала необходимость составлять соответствующие подпрограммы. Что же до команды FPTAN, то она по-прежнему выдает два значения в регистрах ST(O) и ST(1). Но теперь уже значение в ST(1) всегда равно единице, а это означает, что в ST(0) находится истинное значение тангенса числа, находившегося в ST(O) до выдачи команды FPTAN.
Рассмотрим теперь подробней команду вычисления частичного арктангенса угла FPATAN. Если использовать введенные нами при рассмотрении команды FPTAN обозначения, то для команды FPATAN действует следующее отношение:
z = arctg(x/y).
Значения х и у размещаются в стеке следующим образом: х — в регистре si(o), у — в регистре в st(i). Результат z возвращается в sr(o), причем перед этим выполняется выталкивание значений х и у из стека сопроцессора. Команда FPATAN широко применяется для вычисления значений обратных тригонометрических функций, таких как arcsin, arccos, arcctg, arccosec, arcsec. Например, для вычисления функции arcsin используется следующая формула:
arcsin(〃) =arcsin(〃) =
arcsin(〃) =
arcsin(〃) =
Для вычисления этой формулы необходимо выполнить следующую последовательность шагов.
Если а является мерой угла в градусах, то выполнить ее преобразование в радианную меру по правилу, приведенному ранее.
Поместить в стек а в радианной мере.
Вычислить значение выражения VI - a2 и поместить его в стек.
Выполнить команду FPATAN с аргументами в регистрах ST(O) и ST⑴. В sr(Q) должно быть значение выражения VI - a2, в st(l) — a.
В результате этих действий в регистре ST(O) будет сформировано значение выражения arcsin(a).
Аналогично вычисляются значения других обратных тригонометрических функций.
Для вычисления функции arccos(〃) используется формула
arccos(o) = 2 arctgarccos(o) = 2 arctg
arccos(o) = 2 arctg
arccos(o) = 2 arctg
Для еенеобходимо выполнить следующую последовательность
шагов:
Если а является мерой угла в градусах, то выполнить ее преобразование в радианную меру поприведенному ранее
Вычислитьвыражения4一〃 и поместитьеговстек.
ВычислитьвыраженияVl + a и поместитьеговстек.
ВыполнитьFPATAN с аргументами в регистрахи Вдолжно
быть значение выражения Vl + a , в—•
В результате этихв регистре ST(O) сформируется значение выраже
ния
Для вычисленияиспользуется формула
=
Для еенеобходимо выполнить следующую последовательность
шагов:
Если а является ме рой угла в градусах, то выполнить ее преобразование в радианную меру по пра зилу, приведенному ранее.
Командой FLD1естить в стек значение 1.
Поместить в стек з^гачение а.
Выполнить команду FPATAN с аргументами в регистрах ST(O) и ST(1). В ST(O)должно быть значение а, в 开⑴一 1.
В результате этих действий в регистре ST⑼ сформируется значение выраженияI
Если вамвычислить значения других тригонометрических функций, то ход вашихбыть аналогичен приведенному выше. Зная
набор функций,сопроцессором, вам следует искать формулы
приведения (илиих самим), выражающие нереализованные в сопроцес
соре функции посредс твом реализованных. Не забывайте контролировать диапазоны значений аргументов для тех команд, которые требуют этого.
Для демонстрациикоманд данной группы реализуем два алгоритма построенияизображений на основе определенных математических
зависимостей.этих примеров мы завершим разработку Windows-приложения, о которомв главе 16. Если вы помните, это было приложение, в которомнереализованными два пункта меню: Графика Эффек
ты Павлин и Графика Эффекты Кружева. Рассмотрим вначале суть алгоритмов, в соответствии сстроятся эти графические изображения. Математиче
ские зависимости построения этих фигур взяты из [17].
Фигура «Павлин»из множества отрезков. Один конец каждого из
отрезков располагается вдоль горизонтальной линии. Расположение второго конца рассчитывается поформулам, содержащим тригонометрические
функции sin и cos.шагов построения фигуры «Павлин» вы
глядит следующим образом.
Организуется цикл по переменной xl (координата первого конца каждого отрезка), которая изменяется от 0 до значения icycl.
Для текущего значения xl вычисляются значения координат другого конца отрезка (х2, у2) по формулам
х2 = 1120 + ilOO • sin(xl/i30);
у2 = 190+ ilOO • cos(xl/i30).
Строится отрезок, координаты концов которого располагаются в точках (xl, icenter) и (х2, у2).
Если значение xl не превысило значения icycl, то цикл повторяется для xl, увеличенного на 1.
В этом алгоритме значения icenter, icycl, 190, ilOO, 1120, i30 представляют собой константы, изменяя которые/вы будете получать на экране другие варианты графического изображения «Павлин». На рис. 17.18 показан результат работы этого алгоритма.
Рис. 17.18. Результат работы приложения (пункт меню Графика ► Эффекты > Павлин)
Несущественные детали алгоритма вы легко восстановите по фрагменту программы, приведенному в листинге 17.9. Полный текст программы приведен в каталоге данной главы среди файлов, прилагаемых к книге1.
Листинг 17.9. Фрагмент программы для воспроизведения фигуры «Павлин» .data
юпределение констант для фигуры "Павлин"
xl dd 1
х2 dd 0
у2 dd 0
Все прилагаемые к книге файлы можно найти по адресу http://www.piter.com/download. — Примеч. ред.
130 dw 30
190 dw 9®
1100 dw 100
1120 dw 120
icenter dd 100
icycl dd 319
.code ♦ + ♦ ; -IflenuProc
;обработка сообщенийj от меню MenuProc proc
@@idmpeacock:; "Павлин"
;очистим окно ;выполним первичное заполнение растра серым цветом ;получим дескриптор серой кисти hbrush=GetStockObject(GRAY_BRUSH)
pushGRAY_BRU$H一
callGetStockQbject
mov@@hbrusheax
;выбираем кисть в контекст памяти SelectObject(memdc, @@hbrush)
push@@hbrush!
pushmemdc
callSelectObject
;заполняем выбранной кистью виртуальное окно
;BOOL PatBlt(HDC hdc ] int nXLeft, int nYLeft, int nWidth, int nHeight, DWORD dwRop)
pushPATCOPY
pushraaxY
push maxX !
pushNULL
pushNULL
pushmemdc
callPatBlt
movecx, icycl
pushecx
@@ml:
finit
вычислим x2=120+100(sin(xl/30)
popecx
movxl, ecx
cmpecx, 0
j e@@m2
dececx
pushecx
fildxl
fidiv130 i
fsin
fimul1100
fiadd1120
fistpx2
^вычислим y2=120+100fcos(xl/30)
fildxl
fidiv130I
fcos
fimul1100j
fiadd190'
fistpУ2
;рисуем отрезок:
pushNULL
pushicenter
pushxl
MoveToEx,MoveToEx,callcallpushmemdc
MoveToEx,
MoveToEx,
call
call
ЛистингЛистингpush push push call;генерация push push push call jmp@@m2:jmppush push push call;генерация push push push call jmp@@m2:jmpy2
Листинг
Листинг
push push push call
;генерация push push push call jmp
@@m2:
jmp
push push push call
;генерация push push push call jmp
@@m2:
jmp
x2
memdc
LineTo
сообщения WM_PAINT для вывода строки на экран 0
NULL
@@hwnd
InvalidateRect
@@ml
@@exi t@@exi tФигура «Кружева» формируется следующим образом. В окне приложения строятся вершины правильного многоугольника, количество которых задается константой N. Каждая из этих N вершин соединяется отрезками с другими вершинами. Координаты вершин многоугольника задаются формулами
@@exi t
@@exi t
xi хс + • •yi ус + •
Здесь i — номер вершины; — радиус описанной около многоугольника окружности; (хс, ус) — координаты центра многоугольника.
Рис.Результат работы приложения (пункт меню Графика Эффекты Кружева)Рис.Результат работы приложения (пункт меню Графика Эффекты Кружева)В этом алгоритме значения хс, ус представляют собой константы, изменяя которые, вы будете получать на экране другие варианты графического изображения «Кружева». На рис. показан результат работы программы.
Рис.Результат работы приложения (пункт меню Графика Эффекты Кружева)
Рис.Результат работы приложения (пункт меню Графика Эффекты Кружева)
Несущественные детали алгоритма вы легко восстановите по фрагменту программы, приведенному в листинге Полный текст программы можно найти среди файлов, прилагаемых к книге, в каталоге данной главы.
Листинг 17.10. Фрагмент программы для изображения фигуры «Кружева»
.data
;определение констант для фигуры "Кружева" ;N - число вершин правильного многоугольника
;его можно менять - Попробуйте!!!
N equ 18•
Хс equ 16®
Yc equ 100
masX dd N dup (0}
00000000masY dd N dup (01 i N dw N R dw 99 DTT dd t dd i dd j dd i2 dw
0000
0000
.code
; :: : :MenuProc
;обработка сообщений । от меню
MenuProc
proc
@@idmlaces
:;"Кружев?”
;очистим окно
;выполним
первичное Паполнение растра серым цветом
;получим дескриптор :ерой кисти hbrush=GetStockObject(GRAY_BRUSH)
push
GRAY_BRUfiH
call
GetStockpbject
mov
@@hb「ush,【 eax
;выбираем
кисть в контекст памяти SelectObj ect(memdc t @@hbrush)
push
@@hbrushi
push
memdc
call
SelectObj ect
заполняем
выбранной! кистью виртуальное окно
BOOL PatBlt(HDC hdcL int nXLeft, int nYLeft, 1 nt nWidth,
int nHeight, 'DWORD cfwRop)
push
PATCOPY
push
maxY:
push
maxX
push
NULL
push
NULL
push
memdc 1
call
PatBlt !
;вычислим
DTT=2*p1/Ni
finit
fid
Pii
fidiv
IN;
f imul
f istp
DTTi
mov
t, 0
mov
i, 0
;заполняем массивы m 口 sX и masY координатами вершин прямоугольника
@@m3:
I
mov
eax, i
add
eax, DTT:'
mov
t, eax
Jild
t
fcos
fimul
R
mov
esi , i
продолжение
Листинг 17.10(продолжение)
fistpmasX[esi *4]addmasX[esi *4], Xcfildtfsin fimulRfistpmasY[esi*4]moveax, Ycsubeax, masY[esi *4]movmasY[esi *4], eaxincicmpi, N @@m3соединим вершины многоугольникаmovi,0@@m5:moveax, imovj , eax@@m4: incj;рисуем отрезок:pushNULLmov pushesi, i masY[esi*4]pushmasX[esi *4;pushmemdccallMoveToExmovedi , jpushmasY[edi*4]pushmasX[edi *4]pushmemdccallLineTocmp],Njl@@m4incicmpi, NfistpmasX[esi *4]addmasX[esi *4], Xcfildtfsin fimulRfistpmasY[esi*4]moveax, Ycsubeax, masY[esi *4]movmasY[esi *4], eaxincicmpi, N @@m3соединим вершины многоугольникаmovi,0@@m5:moveax, imovj , eax@@m4: incj;рисуем отрезок:pushNULLmov pushesi, i masY[esi*4]pushmasX[esi *4;pushmemdccallMoveToExmovedi , jpushmasY[edi*4]pushmasX[edi *4]pushmemdccallLineTocmp],Njl@@m4incicmpi, N八 генерация push八 генерация pushcall jmpcall jmp@@m5сообщения WM PAINT для вывода строки на экран0NULL@@hwndInvalidateRect@@exit@@m5сообщения WM PAINT для вывода строки на экран0NULL@@hwndInvalidateRect@@exit
fistp
masX[esi *4]
add
masX[esi *4], Xc
fild
t
fsin fimul
R
fistp
masY[esi*4]
mov
eax, Yc
sub
eax, masY[esi *4]
mov
masY[esi *4], eax
inc
i
cmp
i, N @@m3
соединим вершины многоугольника
mov
i,0
@@m5:
mov
eax, i
mov
j , eax
@@m4: inc
j
;рисуем отрезок:
push
NULL
mov push
esi, i masY[esi*4]
push
masX[esi *4;
push
memdc
call
MoveToEx
mov
edi , j
push
masY[edi*4]
push
masX[edi *4]
push
memdc
call
LineTo
cmp
],N
jl
@@m4
inc
i
cmp
i, N
fistp
masX[esi *4]
add
masX[esi *4], Xc
fild
t
fsin fimul
R
fistp
masY[esi*4]
mov
eax, Yc
sub
eax, masY[esi *4]
mov
masY[esi *4], eax
inc
i
cmp
i, N @@m3
соединим вершины многоугольника
mov
i,0
@@m5:
mov
eax, i
mov
j , eax
@@m4: inc
j
;рисуем отрезок:
push
NULL
mov push
esi, i masY[esi*4]
push
masX[esi *4;
push
memdc
call
MoveToEx
mov
edi , j
push
masY[edi*4]
push
masX[edi *4]
push
memdc
call
LineTo
cmp
],N
jl
@@m4
inc
i
cmp
i, N
八 генерация push
八 генерация push
call jmp
call jmp
@@m5
сообщения WM PAINT для вывода строки на экран
0
NULL
@@hwnd
InvalidateRect
@@exit
@@m5
сообщения WM PAINT для вывода строки на экран
0
NULL
@@hwnd
InvalidateRect
@@exit
Отметим еще две команды сопроцессора, FPREM и FPREM1.
Команда FPREM — команда получения частичного остатка от деления. Исходные значения делимого и делителя размещаются в стеке — делимое в ST(0), делитель в ST(1). Делитель рассматривается как некоторый модуль. Поэтому в результате работы команды получается остаток от деления по модулю. Но произойти это может не сразу, так как этот результат в общем случае достигается за несколько последовательных обращений к команде FPREM, если значения операндов сильно различаются. Физически работа команды заключается в реализации хорошо известного всем действия: деления в столбик. При этом каждое промежуточное деление осуществляется отдельной командой FPREM. Цикл, центральное место в котором занимает команда FPREM, завершается, когда очередная полученная разность в ST(0) становится меньше значения модуля в ST(1). Судить об этом можно по состоянию флага с2 в регистре состояния SWR:
* если С2 = 0, то работа команды FPREM полностью завершена, так как разность в ST(0) меньше значения модуля в ST(1);
戀 если С2 = 1, то необходимо продолжить выполнение команды FPREM, так как разность в ST(O) больше значения модуля в WD
Таким образом, необходимо анализировать флаг С2 в теле цикла. Для этого С2 записывается в регистр флагов основного процессора с последующим анализом его командами усновЕ ого перехода. Другой способ заключается в сравнении ST(O) и5Т(1). ;
Необходимость в таком частичном исполнении команды FPREM возникает из-за того, что если делимое слишком велико, а делитель мал, то время получения конечного частичного остатка, удовлетворяющего условию "(0) <夕⑴, может быть достаточно большим. При этом становится невозможной своевременная реакция на поступающие запросы прерываний, возможно, достаточно важные для того, чтобы быть задержанными в обработке.
В контексте рассмотрения нами трансцендендентных команд пример использования команды FPREM является наиболее показательным. При рассмотрении команды FPTAN мы упомянули о том, что аргумент z функции tg должен находиться в диапазоне 0 <z< л/4. Исходя из того, что данная тригонометрическая функция является периодической, возникает необходимость понижения размерности аргумента z, находящегосфарамками указанного диапазона допустимых значений Для команды FPTAN, что и Делается с помощью команды FPREM.
Важно отметить, qто команда FPREM не соответствует последнему стандарту IEEE-754 на вычисления с плавающей точкой. По этой причине в систему команд сопроцессора i387 бьфа введена команда FPREM1, которая отличается от команды FPREM тем, что накладывается дополнительное требование на значение остатка в ST(O). Это значение не должно превышать половины модуля в ST(1)・В остальном работа команды FPREM 1 аналогична работе FPREM.
Команды FPREM и FPREM1 имеют еще одну особенность, представляющую интерес для команд, вычисляющих значения периодических тригонометрических функций. После полного завершения работы команды FPREM/FPREM1 (когда С2 = 0) биты СО, СЗ, С1 содержатзначениятрехмладшихбитов частного, которыелогически представляют собой численное значение номера одного из восьми октантов единичного круга. Это, несомненно, важная информация при работе с тригонометрическими функциями.
Далее рассмотрим|еще тритрансцендентные команды.
Команда F2XM1 — команда вычисления значения функции у = 2х - 1. Исходное значение х размещается в вершине стека сопроцессора в регистре 5Т(0) и должно лежать в диапазоне一1 < х< 1. Результату замещает х в регистре ST(O). Эта команда может быть использована для вычисления различных показательных функций (например, далее показано, как она используется для возведения числа в степень).
Единица вычитается для того, чтобы получить точный результат, когда значение х близко к нулю] Вспомните, что нормализованное число всегда содержит в качестве первой значащей цифры единицу. Поэтому, если в результате вычисления функции Т получается число 1,000000000456..., то команда F2XM1, вычитая 1 из этого числа и зате^ нормализуя результат, формирует больше значащих цифр, то есть делает его бол^е точным. Неявное вычитание единицы командой F2XM1 компенсируется командой FADD с единичным операндом.
Команда FYL2X — команда вычисления значения функции 2 K у log2(^)- Исходное значение х размещается в вершине стека сопроцессора, а исходное значение у — в регистре ST(1). Значение х должно лежать в диапазоне 0 < х < +°°, а значение у — в диапазоне -оо<у< 十8. Перед тем как осуществить запись результата z в вершину стека, команда FYL2X выталкивает значения х и у из стека и только после этого производит запись z в регистр ST(O).
Команды сопроцессора F2XM1 иочень полезны, в частности, для реализа
ции операции возведения в степень любого числа по любому основанию. Специальной команды в сопроцессоре для возведения в степень нет. Поэтому программисту нужно искать подходящую формулу приведения, которая позволит реализовать отсутствующую операцию имеющимися программно-аппаратными средствами сопроцессора. Возведение в произвольную степень числа с любым основанием производится по формуле:
炉工2д」<>&(#).
Вычисление значения выражения у • log2(х)для любого у и х > О производится специальной командой сопроцессора FYL2X, рассмотренной ранее. Вычисление 1х производится командой(лишнее вычитание единицы пока не замечаем, так
как его всегда можно компенсировать сложением с единицей). Но в последнем действии есть тонкий момент, который связан с тем, что величина аргументах должна лежать в диапазоне: -1 <х< 1. А как быть в случае, если х превышает это значение (например, При вычислении выражения 3 •командой вы
получите в стеке значение 12. Попытка вычислить значение командой F2XM1 ни к чему не приведет — результат окажется неопределенным (по моим наблюдениям, значениепопросту не изменяется). В этой ситуации логично вспомнить
о другой команде сопроцессора, FSCALE, которая также вычисляет значение выражения Т, но для целых значений х со знаком. Применив формулу 2"” = 2" • 2", получаем решение проблемы. Разделяем дробный показатель степени, больший |1|, на две части — целую и дробную. После этого вычисляем отдельно командами FSCALE и F2XM1 степени двойки и перемножаем результаты (не забываем компенсировать вычитание единицы в командепоследующим сложением резуль
тата с единицей).
Далее представлен обобщенный алгоритм вычисления степени произвольного числа х по произвольному показателю у (он не претендует на оптимальность решения; его основное назначение — раскрытие принципов реализации этой операции).
Загрузить в стек основание степени х.
Загрузить в стек показатель степени у и проверить его знак. Если у < О, то запомнить этот факт (для шага 8) и заменить в стеке значение у его модулем (это следует из формулы=
Командой FYL2X вычислить значение выражения z = у lg2(x).
Проверить z:
П если значение |z| < 1, то обозначить z какz2;
п если значение \z\ > 1, то представить z в виде двух слагаемых г\ +z2, гдег1 — целое значение, a z2 — значение меньше единицы (например, число 16,84
следует представить в виде суммы 16 + 0,84); в программе это можно выполнить путем циклического вычитания единицы из начального значения г до тех пор, пока оно не станет меньше единицы (количество вычитаний нужно запомнить, обозначив через п).
Выполнить команду F2XM1 с аргументом z2.
Выполнить команды FLD1 и FADD, компенсирующие единицу, которая была вычтена из результат^ функции 2Л на шаге 5 командой F2XML
Выполнить команду FSCALE с аргументом zl. Переменную zl нужно инициализировать нулем, ч!о позволит получить корректный результат, даже если исходное значение z было меньше единицы.
Перемножить результаты, полученные на шагах 6 и 7, — в вершине стека окажется результат возведения в степень. Если показатель степени, проверяемый на шаге 2, был отрицательным, необходимо единицу разделить на этот результат. Для этого в ст^кзагружаем единицу командой FLD1 и применяем команду FDIV.
Данный алгоритм (реализует программа листинга 17.11.
Листинг17.11.Возведение числа в произвольную степень
.586р
masm
model
usel6 small
.stack
100h■
.data
:сегмент данных
flag
db 0:
pl dd
0
у dt
2.0;осн6вание степени
x dt
2®; показатель степени
. code
proc:начало процедуры main
main
mov
ax @data!
mov
ds ах
finit fid у
ахахfid x ftst
ах
ах
переход, если х >=0взврдем flag, если х<0переход, если х >=0взврдем flag, если х<0fstsw sahf
переход, если х >=0
взврдем flag, если х<0
переход, если х >=0
взврдем flag, если х<0
jnc ml
inc flag fabs
fyl2x fst st(l) fabs
;сравним |z| с единицей: fldl!
fcom f stsw ax!
sahf j p exitюпфандынесравнимы
jnc m2
;есл1 |z|<l, TO переход на m2
j z m3
;если ,lz,l=L, то переход на тЗ
xor ecx,
;еслк I 2 I >1, то приводим к формуле z=zl+z2, ;z2 f дробное и z2<l:
ecx; сче(гмик вычитаний
ml: fxch
где zl - целое,
Листинг
ml2:inc ex
st(l), st(0)
fcom
ax
sahf
не сравнимы jz
jncra2;если|z|<l, топереходнаm2
jmp;еслитопереходна
mov1
jmp $+7
movecx
fxch
f 2xml
1 степениfscale
fldl fscale fxch fincstp fmul-
на отрицательную степень
cmp flag, 1 jnz exit fldl fxch fdiv
exit: mov ax, int
mainendp
end main
Используя тождество-•можно вычислить логарифм
любого числа по любому основанию. Значенияи некоторых дру
гих логарифмических функций вычисляются соответствующими командами сопроцессора: FLDL2T, FLDL2E,FLDLN2.
Еще одна команда FYL2XP1 — команда вычисления значения функции z = -у log2(x + 1). Исходное значение х размещается в вершине стека сопроцессора — регистре ST(O), а исходное значение у — в регистре ST(1). Значениех должно лежать в диапазоне 0 V \х\ < 1 一 l/及,а значение у — в диапазоне 一8 V у < +оо. Перед тем как записать результат z в вершину стека — регистр ST(0), команда FYL2XP1 выталкивает из стека значения х и у.
Очень интересную программу можно найти среди файлов, прилагаемых к книге, в подкаталогекаталога данной главы. Программа преобразует содер
жимое вершины стека в символьный вид и выводит его на экран. В этом примере иллюстрируется работа многих уже рассмотренных команд.
Команды управления сопроцессором
Последняя группа команд предназначена для общего управления работой сопроцессора. Команды этой группы имеют особенность — перед началом своего выполнения они не проверяют наличие незамаскированных исключений. Однако такая проверка может понадобиться, в частности, для того, чтобы при параллельной работе основного процессора и сопроцессора предотвратить разрушение информа-
ции, необходимой ДЛЯ корректной обработки исключений, возникающих в сопроцессоре. Поэтому некрторые команды управления имеют аналоги, выполняющие те же действия плюс cj Дну дополнительную функцию — проверку наличия исключения в сопроцессоре! Эти команды имеют одинаковые мнемокоды (и машинные коды тоже), различающиеся только вторым символом — символом п:
犠 мнемокод, не содержащий второго символа п, обозначает команду, которая перед началом своегр выполнения проверяет наличие незамаскированных исключений;
繳 мнемокод, содержащий второй символ п, обозначает команду, которая перед началом своего выполнения не проверяет наличия незамаскированных исключений, то есть выполняется немедленно, что позволяет сэкономить несколько машинных тактов.
Как уже упоминав ось, эти команды имеют одинаковый машинный код. Отличие лишь в том, что перед командами, не содержащими символа п, транслятор ассемблера вставляет команду WAIT. Команда WAIT является полноценной командой основного процессора, и ее при необходимости можно указывать явно. Команда WAIT имеет аналог ср4ди команд сопроцессора — FWAIT. Обеим этим командам соответствует код операции 9bh.
Команда WAIT/FWAp — это команда ожидания. Она предназначена для синхронизации работы процессора и сопроцессора. Так как основной процессор и сопроцессор работают пар^лельно, то может создаться ситуация, когда за командой сопроцессора, измени ющей данные в памяти, следует команда основного процессора, которой эти данные требуются. Чтобы синхронизировать работу этих команд, необходимо включит D между ними команду WAIT/FWAIT. Встретив данную команду в потоке команд, основной процессор приостановит свою работу до тех пор, пока не поступит апгаратный сигнал о завершении очередной команды в сопроцессоре. Здесь есть еще один эффект, отмеченный ранее. Он касается корректной обработки исключений и связанной с ними информации.
Далее с необходимой степенью подробности будут рассмотрены команды, входящие в группу команд управления. Некоторые из этих команд мы уже применяли в программах этой главы.
Из всех команд управления первой логично рассмотреть команду, приводящую сопроцессор в некоторое начальное состояние, — это команда инициализации сопроцессора FINIT/FNI^IT. Она инициализирует управляющие регистры сопроцессора определенными значениями.
и Регистр управления CWR инициализируется числом 037h, что означает установку следующих режи ов:
D поле округлен [я RC = 00 — округление к ближайшему целому;
П биты 0...5 установлены в единицу, что означает маскирование всех исключений;'
п поле управления точностью PC = 11 — максимальная точность (64 бита).
趣 Регистр состояний SWR инициализируется нулевым значением, что означает отсутствие исключений и указание на то, что физический регистр стека сопроцессора RO является вершиной стека и соответствует логическому регистру ST(O).
緻 Регистр тегов TWR инициализируется единичным значением — это означает, что все регистры стека сопроцессора пусты.
ii Регистры указателей данных DPR и команд IPR инициализируются нулевыми значениями.
Данную команду используют перед первой командой сопроцессора в программе и в других случаях, когда необходимо привести сопроцессор в начальное состояние.
Следующие две команды работают с регистром состояния SWR.
溝 FSTSW/FNSTSW ах — команда сохранения содержимого регистра состояния SWR в регистре АХ. Эту команду целесообразно использовать для подготовки к условным переходам по описанной при рассмотрении команд сравнения схеме.
я FSTSW/FNSTSW приемник — команда сохранения содержимого регистра состояния SWR в ячейке памяти. От рассмотренной ранее команда отличается типом операнда — теперь это ячейка памяти размером два байта (в соответствии с размерностью регистра SWR).
Обратите внимание на то, что команды, работающие с регистром SWR, выполняют только считывание содержимого этого регистра. Запись не предусмотрена, так как она попросту не требуется за исключением, возможно, поля ТОР, которое является указателем текущей вершины стека. Для изменения этого поля предусмотрены отдельные команды FINCSTP, FDECSTP, которые будут рассмотрены далее. Следующие две команды, работающие с информацией в регистре управления CWR, напротив, поддерживают действие записи и чтения содержимого этого регистра. 盘 FSTCW/FNSTCW приемник — команда сохранения содержимого регистра управления CWR в ячейке памяти размером два байта. Эту команду целесообразно использовать для анализа полей маскирования исключений, управления точностью и округления. Следует заметить, что операндом не является регистр AX, в отличие от команды FSTSW/FNSTSW.
ii FLDCW источник — команда загрузки значения ячейки памяти размером 16 битов в регистр управления CWR. Эта команда выполняет действие, противоположное действию команды FSTCW/FNSTCW. Команду FLDCW целесообразно использовать для задания или изменения режима работы сопроцессора. Следует отметить, что если в регистре состояния SWR установлен любой бит исключения, то попытка загрузки нового содержимого в регистр управления CWR приведет к возбуждению исключения. По этой причине необходимо перед загрузкой ре- гистраCWR сбросить все флаги исключений в регистре SWR.
Следующая команда — команда без операндов FCLEX/FNCLEX — позволяет сбросить флаги исключений в регистре состояния SWR сопроцессора, которые, в частности, необходимы для корректного выполнения команды FLDCW. Ее также применяют в случаях, когда необходимо сбрасывать флаги исключений в регистре SWR, например, в конце подпрограмм обработки исключений. Если этого не делать, то при исполнении первой же команды сопроцессора прерванной программы (кроме тех команд, которые имеют в названии своего мнемокода второй символ л) опять возбуждается исключение.
Как мы отметили ранее, сопроцессор имеет две команды, которые работают с указателем стека в регистре SWR.
FINCSTP — команда] увеличения указателя стека на единицу (поле ТОР) в регистре SWR. Команда не имеет операндов. Действие команды FINCSTP подобно действию команды FST, но она извлекает значение операнда из стека «в никуда». Таким образом, эту команду можно использовать для выталкивания ставшего ненужным операнда из вершины стека. Команда работает только с полем ТОР и не изменяетсоответствующееданному регистру поле в регистре теговTWR, то есть регистр остае|гся занятым и его содержимое из стека не извлекается.
FDECSTP —команд уменьшения указателя стека (поле ТОР) в регистреSWR. Команда не имеет операндов. Действие команды FDECSTP подобно действию команды FLD, но она] не помещает значения операнда в стек. Таким образом, эту команду можно использовать для проталкивания внутрь стека операндов, ранее включенных审него. Команда работает только с полем ТОР и не изменяет соответствующее [данному регистру поле в регистре тегов TWR, то есть регистр остается пустым.
Следующая команда, FFREE st(i), помечает любой регистр стека сопроцессора как пустой. Это команда освобождения регистра стека ST(i). Команда записывает в поле регистратег叫соответствующего регистру ST(i), значение 1 lb, что соответствует пустомурегис|тру. При этом указатель стека (поле ТОР) в регистре SWR и содержимое самого регистра не изменяются. Применяя эту команду, помните, что ST(i) — это относительный, а не физический номер регистра стека сопроцессора. Необходимость в этфй команде может возникнуть при попытке записи в регистр ST(i), который помеченкак непустой. В этом случае будет возбуждено исключение. Для предотвращений этого применяется команда FFREE.
В сопроцессоре еЕь также команда FNOP, не производящая никаких действий и влияющая только *а регистр указателя команды IPR.
В группе команд ^правления можно выделить подгруппу команд, работающих с так называемой средой сопроцессора. Среда сопроцессора — это совокупность регистров сопроцессора и их значений. Среда сопроцессора может быть частичной или полной. О какой именно среде идет речь, определяется одной из рассмотренных далее команд:
№ FSAVE/FNSAVEnpn&MHMK — команда сохранения полного состояния среды сопроцессора в намят’, адрес которой указан операндом приемник. Размер области памяти зависит 0Т размера операнда сегмента кода (use!6 или use32):
D usel6 — область памяти должна составлять 94 байта (рис. 17.20, а, б): 80 байтов для восьм!и регистров из стека сопроцессора и 14 байтов для остальных регистров сопроцессора сдополнительной информацией;
D use32 —область памяти должна составлять 108 байтов (рис. 17.20,6,2): 80 байтов для ВОСЬМИ регистров из стека сопроцессора и 28 байтов для остальных регистров сопроцессора с дополнительной информацией;
II FRSTOR источник 4 команда восстановления полного состояния среды сопроцессора из области (памяти, адрес которой указан операндом источник. Сопроцессор будет работать в новой среде сразу после окончания работы команды FRSTOR.
use16 и R-режим
use16 и Р-режим
Регистр CWR
0
Регистр CWR
■ 0
Регистр SWR
2
■ Регистр SWR
2
Регистр TWRI
4
Регистр TWR
4
Регистр IPR (0-15)
6
Регистр IPR (0-15)I
6
IPR(16-19)|o|Koflоперации (0-10) I
8
Селектор CS
В
Регистр DPR (0-1 5)
10
Регистр DPR (0-15)J
10
DPR (16-19)| 000000000000
12
Селектор операнда
12
st(0)
14
st(0)
14
st(1)
24
st(1)
24
st(7)
84
st(7)
84
15110
150
а
б
use32 и R-режим
0000000000000000
Регистр CWR
0
0000000000000000
Регистр SWR
4
0000000000000000
Регистр TWR
8
0000000000000000
Регистр IPR (0-15)I
12
0000
|IPR (16-31
lol Кодоперации(О-Ю)
16
0000000000000000 '
Регистр DPR (0-15)I
20
0000
|DPR (16-31)
|000000000000
24
st(0)
28
St(1)
38
st(7)
98
31
15
11
0
use32 и Р-режим
0000000000000000
Регистр CWR
0
0000000000000000
Регистр SWR
4
0000000000000000
Регистр TWR .
8
Регистр IPR(0-31)1
12
00000000000000001
Регистр IPR (31-47)I
16
Регистр DPR (0-31)I
20
0000 000000000000I
Регистр DPR (3f)I
24
st(0)
28
st(1)
38
st(7)
98
3115
0
Рис. 17.20. Структура области памяти с полной средой сопроцессора в различных режимах
Следующие две команды сохраняют частичную среду сопроцессора, в состав которой входят регистры SWR, CWR, TWR, DPR И IPR.
FSTENV/FNSTENV приемник — команда сохранения частичного состояния среды сопроцессора в область памяти, адрес которой указан операндом приемник. Размер области памяти зависит от размера операнда сегмента кода (usel6 или use32).
Формат области частичной среды сопроцессора совпадает с форматом области полной среды (рир. 17.21), за исключением содержимого стека сопроцессора (80 байт).
00000000 рооооооо1
Регистр CWR
00000000 30000000
Регистр SWR
00000000 30000000
Регистр TWR
Регистр IPR (0-31)I
ооооооооОооооооо
Регистр IPR (31-47)I
Регистр DPR (0-31)1
оооо oooodooooooo 〔
Регистр DPR (31У7)]
use32 и Р-режимuse32 и Р-режим313115151111ОО後 FLDENV источник — Команда восстановления частичного состояния среды сопроцессора содержим|ым из области памяти, адрес которой указан операндом источник. Информация в данной области памяти была ранее сохранена командой FSTENV/FNSTENV. |
use32 и Р-режим
use32 и Р-режим
31
31
15
15
11
11
О
О
use16 и R 叩 ежим
use16 и Р 叩 ежим
Реги( тр CWR
0
Регистр CWR
0
Реги,тр SWR
2
Регистр SWR
2
Реги4тр TWR
4
Регистр TWR
4
Регистр
IPR(0-15)
6
Регистр IPR (0-1 5)
6
IPR (16-19) |о|
Код операции (
0-10)
8
Селектор CS
8
Регистр | DPR (0-15)|
10
Регистр DPR(М5)|
10
DPR(16-19)|
000000000000
12
Селектор операнда
12
1511
0
15
0
use32 и R-режим
000000000
3000000
Регистр CWR
0
000000000
3000000
Регистр SWR
4
000000000
DOOOOOO
Регистр TWR|
8
000000000
эоооооо
Регистр IPR (0-1 5)1
12
0000 1
IPR (16-31)
|о| Код операции (0-10) 1
16
000000000
)000000
Регистр DPR (0-15)1
20
0000 1
DPR (1&-31)
1000000000000
24
31
|
15
110
0
4
8
12
16
20
24
Рис. 17.21.|Структураобласти памяти с частичной средой сопроцессорав различных режимах
Команды сохранения среды целесообразно применять в обработчиках исключений, так как только с помощью данных команд можно получить доступ, например, к регистрам DRR и IPR. Не исключено использование этих команд в подпрограммах или в многозадачной среде для переключения контекстов программ. Области памяти, содержащие сохраненные состояния среды сопроцессора, есть
смысл располагать IB стеке основной программы. Тогда, в частности, появляется возможность передать информацию коду, находящемуся в другом кольце защиты основного процессрра.
Судя по последним командам, сопроцессор может работать не только в реальном, но и в защищенном режиме. Для этого необходимо выполнять переключение
сопроцессора между этими режимами. Операция переключения реализуется специальными командами.
幾 FSETPM — команда переключения сопроцессора из реального в защищенный режим. Команда не имеет операндов. Действие команды влияет только на выполнение команд сохранения и восстановления среды. Для реального и защищенного режимов состав и формат информации среды сопроцессора несколько различается.
綴 FRSTPM — команда переключения сопроцессора из защищенного в реальный режим. Команда не имеет операндов. Действие команды влияет только На выполнение команд сохранения и восстановления среды.
Исключения сопроцессора и их обработка
Важнойособенностьюсопроцессораявляетсявозможностьраспознаванияособых ситуаций, которые могут возникать в процессе вычислений. При этом сопроцессор формирует определенные управляющие сигналы и устанавливает бит в своем регистре состояния SWR. Согласно идеологии IA-32, прерывания классифицируются по источникам возникновения. Один из возможных типов прерываний — исключения, которые являются внутренними прерываниями процессора, возникающими в ходе вычислительного процесса. Особые ситуации, возникающие в сопроцессоре, полностью соответствуют определению понятия «исключение».
В сопроцессоре могут возникать шесть типов исключений. Все они были перечислены при обсуждении форматов регистра состояния SWR и регистра управления CWR. Вспомним основные моменты. В регистре состояния SWR6 битов, каждый из которых играет роль флага для определенного типа исключения. При возникновении исключения устанавливается соответствующий флаг в этом регистре (см. рис. 17.2). Сопроцессор аппаратно позволяет запретить явную обработку любого из этих типов исключений. Для этого в регистре управления CWR есть 6 битов, играющих роль масок, установка которых и позволяет запретить возникновение соответствующих исключений. Важно отметить, что запрещение обработки исключения вовсе не означает того, что сопроцессор оставляет вычислительную ситуацию неизменной. При возникновении исключения, имеющего единичное состояние соответствующего бита в регистре CWR, сопроцессор выполняет так называемую маскированную реакцию, которая включает в себя некоторую последовательность предопределенных разработчиками процессора действий. Естественно, что в них невозможно было предусмотреть все потребности программистов, которым, помимо всего прочего, могут понадобиться и нестандартные варианты реакции на исключения. В этом случае программисту необходимо установить соответствующий бит в регистре CWR. О том, как нужно реагировать на возникновение незамаскированного исключения, мы поговорим чуть позже, а пока же разберемся с причинами возникновения исключений и маскированными реакциями на них со стороны сопроцессора. Эта информация может быть полезна для того, чтобы разобраться с логическими ошибками программы и правильно запрограммировать реакцию на их возникновение.
I
Недействительная операция
Причиной исключения недействительной операции являются ошибки в логике программы, наиболее! типичные из которых следующие:
- загрузка операнда|в непустой регистр стека сопроцессора;
驗 попытка извлечения операнда из пустого регистра стека сопроцессора;
幾 использование операнда с недопустимым для данной операции значением.
При возникновении этого исключения устанавливается флаг IE (Invalid Operation) в регистре SWR (см. рис. 17.2), маскируется оно битом IM регистра управления CWR (см. рис. 17.4). Ивключение недействительной операции возникает при работе со стеком и при ар:ифметических вычислениях. В каких именно операциях возникло это исключен^е, судят по содержимому бита SF (Stack Fault — ошибка стека) регистра состояния SWR. Если он установлен в единицу, это говорит о том, что исключение было вызвано ошибкой в работе стека сопроцессора; напротив, нулевое состояние би^а SF говорит о том, что в команде встретился неверный операнд.
Маскированная реакция на исключение недействительной операции зависит от причины ошибки. Если она возникла в результате некорректной работы стека, то сопроцессор перезаписывает содержимое того регистра стека, обращение к которому вызвало исключение. При перезаписи в него заносится специальное численное значение — спокойное нечисло. Если ошибка возникла в результате недопустимого операнда」 то в большинстве ситуаций в регистре стека сопроцессора возвращается также ^покойное нечисло.
Кстати, исключен ие недействительной операции — это единственное исключение, которое не нужно маскировать. Программисту следует самому вмешиваться в обработку ошибочных ситуаций путем вызова соответствующего обработчика.
Деление на ноль
Исключение деления на ноль возникает в командах, которые явно или неявно выполняют деление. э|о команда FDIV и ее варианты — FYL2X и FXTRACT. Факт возникновения этого исключения фиксируется флагом ZE (Zero Divide) регистра состояния сопроцессора § WR и при необходимости маскируется битом ZM регистра управления CWR. ME скированная реакция сопроцессора заключается в формировании результата в *кде знаковой бесконечности.
jоперанда
Исключение денор^ализованного операнда возникает, когда команда пытается выполнить операций с денормалиэованным операндом. При этом устанавливается флаг DE (Denormajlized Operand), который маскируется битом DM регистра управления CWR. Если это исключение замаскировано, то его возникновение приводит только к установке 1флага DE, после чего сопроцессор нормализует операнд и вычислительный процесс продолжается. Если исключение денормализованного операнда не замаскирорано, то вызывается обработчик исключения, который позволяет произвести необходимую обработку ситуации.
Переполнение и антипереполнение
Ситуации переполнения и антипереполнения возникают в случаях, когда порядок результата слишком велик или слишком мал для формата приемника. При возникновении этих исключений в регистре SWR устанавливаются флаги ОЕ (Overflow) и UE (Underflow). Эти исключения маскируются битами ОМ и UM регистра управления CWR. Исключения могут возникнуть при работе арифметических команд и команд, преобразующих формат операндов, таких как FST.
Маскированная реакция для ситуации переполнения состоит в формировании граничных (максимальных или минимальных) значений, представимых в сопроцессоре, или специального численного значения в виде знаковой бесконечности.
Немаскированная реакция на возникновение этих исключений зависит от того, где должен формироваться результат. Если приемник — память, то мантисса результата округляется, а порядок приводится к середине своего диапазона. Если приемник — ячейка памяти, то значение в ней не запоминается, при этом не изменяется и содержимое регистра стека.
Неточный результат
Исключение неточного результата возникает в случае, когда результат работы команды нельзя точно представить в формате приемника и его приходится округлять. Например, вычисление любой периодической дроби вроде 1/3 будет приводить к возникновению такого типа исключения. В какую сторону произошло округление, можно судить по значению бита С1:
纓 если С1 = 0, то результат был усечен;
綫 если С1 = 1, то результат был округлен в большую сторону.
При возникновении этих исключений в регистре SWR устанавливаются флаг РЕ (Precision). Исключение маскируется битом РМ регистра управления CWR.
Немаскируемая обработка исключений
Как производить немаскируемую обработку исключений? Для этого необходимо установить в ноль те флаги в регистре CWR, которые соответствуют интересующим нас типам исключений. Далее нужно написать обработчик исключения, реализующий последовательность действий по корректировке ситуации, приведшей к исключению. Неясным остается вопрос о том, как передать управление обработчику исключения? Для ответа на него нужно разобраться с проблемой взаимодействия процессора и сопроцессора при возникновении исключения.
В главе 2 мы упоминали о существовании группы системных регистров процессора. В контексте нашего рассмотрения интерес представляет один из них — регистр CRO. Он имеет несколько битов, имеющих отношение к сопроцессору (табл. 17.3).
Таблица 17.3. Биты CRO, имеющие отношение к сопроцессору
Положение бита в CRO
Название бита
Назначение
1
МР (Math Present)
Сопроцессор присутствует. Бит должен быть установлен в 1
Положение бита в CRO
Название бита !1
Назначение
2
ЕМ (Е
Math)
nulation
Эмуляция сопроцессора. Когда ЕМ 1, выполнение любой команды сопроцессора вызывает исключение 7. В программах для микропроцессосоров i8086…i386 это позволяло иметь альтернативные фрагменты кода для выполнения одинаковых вычислений с использованием команд сопроцессора (ЕМ 0) и без них (ЕМ = 1),то есть тогда, когда в конкретной конфигурации компьютера сопроцессор отсутствует
3
TS (Та Switch
sk ed)
Задача переключена. Бит предназначен для согласования контекстов основного процессора и сопроцессора. Бит TS устанавливается в единицу при каждом переключении задачи. Состояние этого бита проверяется процессором, если очередной выбранной командой является команда сопроцессора. Если бит TS установлен в единицу, то процессор возбуждает исключение 7, обработчик которого выполняет необходимые действия, возможно, по сохранению или восстановлению контекста вычислений с плавающей точкой. С битом TS работает команда CLTS, которая устанавливает значение этого бита в 0
4
ET (Extension
Type)
Тип расширения. Единичное значение этого бита означает поддержку инструкций сопроцессора
5
NE (N
Error)
umeric
Численная ошибка. Бит определяет способ обработки исключений сопроцессора: через сигнал внешнего прерывания или путем генерации исключения (см. далее)
Начиная с модели i486 процессор и сопроцессор размещаются в одном корпусе. Это упростило организацию взаимодействия между ними. Рассмотрим процессы, протекающие в компьютере при возникновении одного из шести перечисленных ранее исключений сопроцессора. При возникновении ситуации исключения сопроцессор устанавливает бит суммарной ошибки ES в регистре состояния SWR и формирует на однс м из своих выходов сигнал ошибки. Этот сигнал ошибки одновременно воспринимается самим процессором, который генерирует исключение 10h, и в то же самое время, независимо от процессора, заводится на вход IRQ13 программируемого контроллера прерываний, обработчик которого вызывается через вектор прерывания 75h [8]. Таким образом, появление сигнала ошибки на выходе сопроцессора приводит к генерации в основном процессоре двух исключений с номерами 10h я 75h.
Исключение 1 Oh ^является синхронным, так как вызов его обработчика санкционируется процессором при выполнении команд WAIT/FWAIT. Данные команды в процессорах i486 и Pentium встроены практически во все команды сопроцессора за исключением некоторых команд управления, поэтому работа любой команды сопроцессора начинается с выяснения того, было ли зафиксировано какое-нибудь
из незамаскированных исключений. Здесь важно то, что контекст вычислительной ситуации после выполнения команды, вызвавшей исключение, оказывается полностью сформированным (так как исключение синхронное, то есть ожидаемое) и с ним можно корректно работать.
Если сигнал поступает на вход программируемого контроллера прерываний IRQ13, то обработка исключения 75h может начаться в процессоре раньше, чем в сопроцессоре закончит выполняться команда, вызвавшая исключение, то есть в этом случае обработка прерывания является асинхронной к вычислительному процессу.
Необходимо отметить влияние бита NE на процессы, протекающие в компьютере при возникновении исключения. Его состояние определяет стиль обработки исключения процессором. Если бит NE = 1, то процессор возбуждает исключение 16 (обработка в стиле i286и выше), если NE = 0, то при возникновении исключения процессор останавливается и ждет прерывания от программируемого контроллера прерываний (обработка в стиле i8086). По умолчанию бит NEустанавливается в 0.
Если посмотреть на распределение прерываний в реальном и защищенном режимах, то необходимо обратить внимание на номер 7 вектора прерываний — обращение к несуществующему сопроцессору. Прерывание появилось в процессоре i286, для которого сопроцессор не являлся обязательным устройством. Для того чтобы программа, выполняющая математические вычисления, была независимой от аппаратной конфигурации конкретного компьютера, писалось два варианта фрагментов кода, на которых эти вычисления выполнялись, — один с использованием команд сопроцессора и второй с использованием целочисленных команд. При распознавании в потоке команд инструкций сопроцессора процессоры i286и 1386про- веряли бит эмуляции сопроцессора ЕМ (см. табл. 17.3). Если он был равен 1, то процессор возбуждал исключение 7. Это означало, что сопроцессора в конфигурации компьютера нет и его функции должны эмулироваться командами целочисленного устройства. Забота об установке бита ЕМ ложилась на системное программное обеспечение.
Для процессоров i486 и Pentium состояние вычислительной среды определяется состоянием регистров после выполнения команды FINIT и содержимым регистра CRO (биты МР и NE).
Что должен делать обработчик исключений сопроцессора? Его действия зависят от того, какое незамаскированное исключение им обрабатывается. Следует отметить основные действия по обработке любого исключения.
Сохранение среды сопроцессора командой FSTENV. Это необходимо для последующего выяснения причин исключения, а в среде сопроцессора как раз и зафиксировано состояние регистров управления сопроцессором при возникновении исключения.
Сброс установленных битов исключений в регистре SWR для предотвращения циклического возникновения исключений.
Выяснение типа исключения. Если незамаскированными являются исключения нескольких типов, то обработчик путем анализа соответствующих битов в регистре SWR должен определить их. Заметим, что содержимое SWR берется из сохраненной при входе в процедуру по команде FSTENV среды сопроцессора.
Выполнениепо корректировке ситуации.
Возвращение впрограмму (командой IRET).
Однако выяснить причину исключения мало, это и несложно. Важно, исправив ситуацию, вернуть управление прерванной программе. В защищенном режиме работы процессора прерывания (исключения) делятся на несколько групп: сбои, ловушки, исключения. Это деление осуществляется в зависимости от того, какая информациятся о месте возникновения прерывания (исключения) и возможностипрерванной программы. Для сопроцессора ситуация ана
логична. Здесь инфор мация о месте возникновения исключения зависит от типа исключения. Так, для исключений недействительной операции, деления на ноль иоперанда запоминается адрес команды, вызвавшей исключение. То естьдля этих типов исключений распознается, и исключение
возбуждается до исполнения команды сопроцессора (по классификации для защищенного режима -• это сбой). При этом операнды в стекепамяти не модифицируются. Длятипов исключений ошибочная ситуация распознает
ся после выполнения действий «виноватой» команды. Это означает, что в стеке в качестве адресавозникновения исключения запоминается адрес
щей (после виновниц команды программы. При этом операнды в памяти и в регистровом стеке, возможно, будут изменены. Ваши действия должны учитывать эти особенности возниi кновения различных типов исключений.
Использование отладчика
Отладчик Turboпредоставляет широкие возможности для отладки про
грамм, использующих сопроцессор. Для наблюдения за состоянием регистров, составляющихмодель сопроцессора в среде Turbo Debuger, необходи
мо открыть специальное окноNumeric processor. Для этого выберите пункт главного меню View Numericили нажмите сочетание клавиши далее N. По
умолчанию окнов компактном виде. Для того чтобы раскрыть его пол
ностью, щелкните шью на стрелке в правом верхнем углу окна.
В заголовке окна отображаются четыре сообщения.
Модель(автоматически определяется отладчиком).
— сообщение о текущем содержимом указателя команд. Этот указатель содержит физический (20-разрядный) адрес памяти, по которому расположенавыполнявшаяся инструкция сопроцессора.
— cooоб адресе памяти, к которому обращалась последняя
команда(если она имела адресный операнд).
9 «OPCODE=...» — !сообщение о коде операции последней исполняемой команды сопроцессора. Интересно отметить то, как формирует отладчик код операции в этомотмечали, что машинный код операции всех команд сопроцессора начис одинаковой последовательности битов —
поэтому в поле OPCODE эти биты отбрасываются. Например, код команды—
(ввиде —1001 0000 ОНО). Убираем пять битов, одина
ковых для кода операции каждой команды сопроцессора, и получаем то, что
видим в поле OPCODE заголовка окна Numeric processor, — 0106h (0000 0001 0000
ОНО).
В окнеNumeric processor выделяются три области. Сразу заметим, что в отличие от областей окна CPU области окна Numeric processor нельзя раскрывать отдельно. ОсновнуючастьокнаNumericprocessorзанимаетобластьRegisters, которая отражает состояние восьми регистров стека сопроцессораST(0)...ST(7),Указываются только логические номера регистров. Наиболее полная информация о регистрах стека предоставляется, если окно Numeric processor развернуто. Рассмотрим поля Registers, описывающие состояние каждого из регистров стека сопроцессора. Первое поле показывает состояние регистра. Возможные значения в этом поле следующие:
舞 EMPTY - «пустой»;
雖 VALID — в регистре корректное вещественное число;
ZERO — в поле нулевое значение;
曝 NaN — в регистре находится специальное численное значение — нечисло (Not a Number).
Второе поле показывает логический номер регистра стека. Третье поле содержит значения в регистре в виде 80-разрядного числа с плавающей точкой. Четвертое поле показывает содержимое регистра стека в шестнадцатеричном виде.
В ходе отладки вы можете влиять на содержимое регистров стека. Для этого в области Registers можно вызвать контекстное меню, активизируемое правой кнопкой мыши. В меню три команды:
綫 ZERO — обнуление содержимого регистра;
• EMPTY — освобождение регистра стека, при этом содержимое самого регистра стека не изменяется, а изменению подвергается только тег в регистре тегов, в который заносится значение lib;
it CHANGE — запись в регистр стека некоторого значения, которое должно быть в допустимом формате в соответствии с синтаксисом ассемблера.
Следующую область окна Numeric processor условно можно назвать Control. Область Control содержит совокупность полей, названия которых совпадают с названиями битов или полей битов в регистре управления сопроцессором CWR. Перечислим эти поля:
緻 IM — маска недействительной операции;
懇 DM —маскаденормализованного операнда;
糞 ZM — маска деления на нуль;
綴 ОМ —маскапереполнения;
- UM — маска отрицательного переполнения;
鹽 РМ — маска точности;
赛 IEM — маска запроса на прерывание (для i8087);
. PC — поле управления точностью;
舞 RC — полеуправления округлением;
缴IC — поле управления значением бесконечности.
Контекстное меню области Control содержит всего одну команду — Toggle. Ее назначение — циклическое изменение содержимого активного (в котором находится курсор) поля.
Третья область ок та Numeric processor — Status — содержит совокупность полей, названия которых совпадают с названиями битов или полей битов в регистре состояния сопроцессор! SWR:
IE — ошибка недействительной операции;
皆DE — ошибкадено{)мализованного операнда;
ZE — ошибкаделения на нуль;
ОЕ — ошибкапереполнения;
崎 UE — ошибка отри|цательногопереполнения;
РЕ — ошибкаточности;
IR — масказапроса на прерывание;
歙СС — код условия (состояниебитов СЗ, С2, С1, СО);
ST — указатель вершины стека (поле ТОР регистра SWR).
Контекстное меню области Status содержит всего одну команду — Toggle. Ее назначение — цикличес кое изменение содержимого активного поля.
Сам процесс отладки программы ничем не отличается от процесса отладки программы для основного процессора.
Общие рекомендации
по программированию сопроцессора
В заключение главы сформулируем некоторые общие рекомендации по написанию программ для српроцессора.
Первый фрагмен| программы с командами сопроцессора должен начинаться с команды FINIT.EbHпрограмма содержит несколько независимых ДРУГ от друга
фрагментов с командами сопроцессора, каждый такой фрагмент должен начи-
наться с команды FINIT.
• При написаниипрограммы вы должны учитывать то, что процессор и сопроцессор работают |параллельно. То есть вам необходимо особенно тщательно программировать участки, на которых планируется параллельное выполнение команд обоих процессоров. Особое внимание обращайте на синхронизацию общих операндов I и обработку возможных исключительных ситуаций.
緣 Рекомендуется обработку исключений доверять самому сопроцессору, кроме исключения неде:йствительной операции, что позволит своевременно обнаружить ошибки алгоритма.
犠 При написании программ следует установить такой режим округления, который позволит ПО7 учить максимально точный результат.
験 Для повышения г роизводительности процессора при передаче данных необходимо использовать директиву EVEN. Ее действие заключается в том, что данные,
описываемые следующей за ней одной из директив резервирования и инициализации данных, размещаются по ближайшему адресу, значение которого кратно 2. Так как все типы данных сопроцессора имеют длину, кратную двум, то желательно все ячейки памяти, содержащие значения для обработки сопроцессором, размещать в сегменте данных одним блоком, предваряя их описание директивой EVEN, например:
.data
even
ch_ldd 35.78
ch 2dt 0987687686
Итоги
к Математический сопроцессор значительно расширяет возможности компьютера по выполнению вычислений над числами из очень большого диапазона значений.
Центром программной модели сопроцессора является регистровый стек, который является наиболее эффективной структурой программирования вычислительных алгоритмов. Использование стека предполагает, что программист предварительно преобразует исходное выражение в формуполиз. Форма ПОЛИЗ, в частности, используется в трансляторах при разборе и генерации кода различных синтаксических конструкций программы (не только математических выражений).
т Сопроцессор на уровне своей системы команд поддерживает большую номенклатуру типов данных: три формата целых чисел, три формата вещественных чисел, десятичные числа. При разработке вычислительных алгоритмов и подборе для их реализации команд сопроцессора следует помнить, что сопроцессор поддерживает только один внутренний формат представления данных — вещественные числа расширенного формата. По этой причине команды сопроцессора, работающие с форматами, отличными от расширенного, вынуждены выполнять дополнительное преобразование данных. Операция преобразования требует дополнительного количества (и немалого) машинных тактов, что не может не сказаться на общем времени выполнения программы.
Система команд сопроцессора состоит из нескольких групп, призванных удовлетворить основные потребности программиста в средствах реализации большинства вычислительных алгоритмов. При отсутствия поддержки на уровне команд сопроцессора каких-либо математических операций они довольно просто могут быть реализованы с помощью математических формул приведения через существующие команды.
В процессе работы внутри сопроцессора могут возникать различные ситуации, требующие внешнего вмешательства. Их называют исключениями. Исключения разбиты на б типов, которым соответствуют по 6 битов в регистрах SWR и CWR. Эти биты позволяют управлять обработкой соответствующих исключе-
ний. Биты в SWR фиксируют возникновение исключений определенного типа. Биты в CWR определяют способ обработки возникших исключений. Если при возникновении исключения некоторого типа соответствующий этому исключению бит в CWR равен 1,это означает, что обработка исключения данного типа замаскирована и сопроцессор должен сам исправить ошибочную ситуацию. Если соответствующий возникшему исключению битвCWR равен 0, это означает, что программист сам желает исправить ошибочную ситуацию. Для этого он должен написать обработчик исключения.
Разработку программ удобно вести с использованием отладчика Turbo Debuger, который предоставляет полную информацию о состоянии вычислительного процесса, использующего команды процессора и сопроцессора.
Вместо заключения...
Ну вот и все! Прощаясь с читателем, позволю себе несколько мыслей о жизни вслух (не примите их в качестве наставлений).
В данной книге я попытался посмотреть на язык ассемблера как на обычный язык программирования. При этом хотелось показать, что язык ничем не хуже, а в чем-то дажепривычных читателю языков высокого уровня. Осознание
этого особенно важно для начинающих программистов, которые зачастую с первых шагов в своей карьере попадают в «теплые, нежные, заботливые... (дальше придумайтеобъятия различных интегрированных оболочек, сред, студий...
Опасно это тем, что такой молодой, неопытный, наивный программист постепенно всецело поддается нежному шелесту этих интегрированных продуктов и засыпает. Спать он может долго, может быть, даже на протяжении всей своей профессиональной карьеры, и, возможно, при этом ему будет сниться сон, суть которого в том, что:
не нужно думать об оптимизации программы, ведь памяти и запаса быстродействия у современного процессора столько, что хватит на мгновенное псевдопа- раллельное выполнение десятка даже самых бездарных программ;
не нужно думать о работе с «железом», ведь современная ОС имеет столько драйверов устройств, что вряд ли встретится ситуация, когда к компьютеру потребуется подключить что-то нестандартное;
не нужно думать о защите своих программ — от вирусов нас защитит программа-антивирус;
боже упаси лезть в чужой исполняемый код, даже если ты «лопаешься» от любопытства;
и, наконец, нет необходимости что-то знать и о самом компьютере — стоит себе ящик, работает, когда его включишь, — чего еще нужно?
Обычно сон прерывается в тот момент, когда человек осознает, что сидит на совершенно конкретном компьютере, функционирующем на основе определенных программно-аппаратных средств (со всеми их изъянами и достоинствами), которые иногда требуют прямого вмешательства человека в свою работу. В этом чае можно, конечно, обратится к специалисту, но какой же ты тогда профессионал?.. Судить обо всем этом, конечно, читателю. Может быть, я в чем-то и не прав. Ведь компьютер уже давно сам стал повседневным рабочим инструментом людей множества профессий, которым действительно все сказанное раньше «до лампочки». Но если книга у вас в руках, то с большой долей вероятности можно предположить, что я не одинок в своих мыслях, и вы — человек, страдающий профессиональной бессонницей. Вас трудно ввести в заблуждение обещаниями легкого достижения поставленных целей, и вы способны самостоятельно выбрать себе дорогу для достижения вершины своей карьеры кратчайшим маршрутом.
Успехов вам на этом
Приложение
Система команд процессоров IA-32
Данное приложение содержит описание системы командархитектуры IA-32 до
Pentium IV включительно. Команды разбиты на 4 группы: целочисленныекоманды
сопроцессора, команды MMX-расширения и команды ХММ-расширения. В пределах каждой группы команды расположены в алфавитномДля каждой команды приведены следую
щие данные.
' Схема команды, поясняющая состав и назначение операндов.
Название команды с кратким описанием ее назначения.
Описание действия команды.
Описание флагов после выполнения команды, при этом приводятся сведения только о флагах, изменяемых командой, и используются следующие обозначения:
D 1 — флаг устанавливается (равен
П 0 — флаг сбрасывается (равен 0);
— значение флага зависит от результата выполнения команды;
П ? — после выполнения команды флаг не определен.
Машинные коды для всех возможных сочетаний операндов команды. Описание
кода производится ввиде — каждый байт машинного представления команды воспроизводится двумяцифрами. При описании машинного кода
используются следующие обозначения:
П /цифра — здесь цифра (от 0 до 7) представляет содержимое трехразрядного поля reg в байте modиспользуемое как часть кода операции;
П — означает, что байт mod r/m команды содержит как регистровый операнд, так и операнд r/m;
cb, cw, cd, ср —четырех- илизначение, следующее за полем
кода операции и используемое для определения смещения в сегменте кода (и возможное новое значение для сегментного регистра кода);
D ib, iw, id —двух- илинепосредственный операнд команды, кото
рый следует за полем кода операции, байтами mod r/m или sib, при этом код операции определяет, является ли непосредственный операнд знаковым значением, а все слова и двойные слова приводятся в порядке «младший байт по младшему адресу»;
П +rb,+rd — код регистра (от 0 до 7), добавляемый в байт кода операции, приводимого при
описании машинного кода первым значением (см. также рис.и табл. 3.3-3.5);
□ +i — число (от 0 до 7), используемое в машинном представлении команд сопроцессора, когда один из операндов является регистром ST(i) из стека регистров сопроцессора (i добавляется к одиночному байту кода операции, значение которого приведено при описании машинного кода первым).
Ж Машинные коды команды сопровождаютсяописанием синтаксиса командыдля соответствующего сочетания операндов. При описании синтаксиса используются следующие обозначения: П rel8 — относительный адрес из диапазона -128...+ 127, точка отсчета — конец данной машинной команды;
П rell6t ret32 — относительные адреса в пределах сегмента кода, содержащего данную команду, используемые для операндов с размером операнда 16 (usel6) и 32 (use32) бита соответственно;
П ptrl6:16 и ptrl6:32 — дальние указатели (обычно на адрес в сегменте кода, отличном от текущего), используемые при установленном атрибуте размера операнда 16 битов и 32 бита соответственно (запись «16:16» или «16:32» означает, что первая часть указателя является селектором или значением сегментного регистра кода, вторая — смещением в целевом сегменте кода);
П r8,rl6,r32— операнд в одном из регистров размером байт (AL, CL, DL, BL, АН, СН, DH, ВН), слово (AX, СХ, DX, BX, SP, BP, SI, DI) или двойное слово (ЕАХ, ЕСХ, EDX, EBX, ESP, EBP, ESI, EDI);
D i8, il6, i32 — непосредственный операнд размером байт (-128...+127), слово (-32 768... +32 767) или двойное слово (-2 147 483 648…+2 147 483 647);
Пm — операнд в памяти размером 16 или 32 бита;
П m8, ml6, т32, m48, т64, ml28 — операнд в памяти размером байт, слово, двойное слово, 48/ 64/128бит;
D r/m8 — байтовый операнд, который содержится либо в одном из регистров размером один байт(AL, CL, DL, BL, АН, СН, DH, ВН), либо в ячейке памяти размером один байт;
D r/ml6 — операнд в регистре размером в слово (АХ, СХ, DX, BX, SP, BP, SI, DI) или в ячейке памяти размером в слово (используется в командах, для которых атрибут размера операнда равен 16 бит);
D r/m32 — операнд в регистре размером в слово (ЕАХ, ЕСХ, EDX, EBX, ESP, EBP, ESI, EDI) или в ячейке памяти размером в двойное слово (используется в командах, для которых атрибут размера операнда равен 32 битам);
П ml6:16, ml6:32 — операнд в памяти, содержащий дальний указатель в виде двух чисел: число слева соответствует селектору сегмента указателя, второе число является смещением в сегменте, на который указывает этот селектор;
D ПЙ6&32, m!6&16, m32&32 — пары операндов в памяти, каждый элемент которых имеет размер, указанный слева и справа от знака &;
Р moffs8, moffs!6, moffs32 — переменная (смещение в памяти) типа байт, слово, двойное слово, требующаяся для выполнения некоторых вариантов команды mov (байт mod r/m не используется, адрес задается простым смещением относительно базы сегмента);
П sreg — сегментный регистр, кодировка вмашинном коде: ES = 0; CS 1; SS = 2; DS - 3; FS = 4; GS 5;
Р m32fp, m64fp, m80fp — операнды в формате сопроцессора (с плавающей точкой) в памяти;
Р E16int, m32int, m64int — целочисленные операнды в памяти, используемые в командах сопроцессора;
Р ST или ST(O) — верхний элемент стека сопроцессора;
Р ST(i) — г-н элемент стека сопроцессора (/ = 0...7);
Р rmmxO.. .rmmx7 — операнд в одном из регистров целочисленного расширения ММХ;
Р rmmx/m32 — младшая часть (32 бита) ММХ-регистра или 32-разрядный операнд в памяти;
□ rmmx/m64 — ММХ-регистр или 64-разрядный операнд в памяти;
D rxmm0...rxmm7 — операнд в одном из регистров расширения ММХ с плавающей точкой (в ХММ-регистре);
П rxmm/m32 — ХММ-регистр или 32-разрядный операнд в памяти;
П rxmm/m64 — ХММ-регистр или 64-разрядный операнд в памяти;
П rxmm/ml28 — ХММ-регистр или 128-разрядный операнд в памяти.
Для экономии места при описании некоторых команд могут быть опущены одна или несколько перечисленныхранее позиций. Например, отсутствие описания действия команды говорит о том, что сведений, приведенных в названии команды с кратким описанием ее назначения, достаточно для ее практического применения. Отсутствие описания флагов означает, что данная команда не изменяет флагов.
Некоторые регистры программной модели процессора имеют внутреннюю структуру. Чтобы идентифицировать поле такого регистра, в описании команд оно воспроизводится следующим образом: имя_регистра.имя_поля.
Целочисленные команды
ААА
• ААА
• 37
Корректировка результата сложения одноразрядных неупакованных BCD-чисел командой
ADD.
Действие: еслимладший полубайт регистра AL > 9или AF= 1, то: (AL)-W(AL) + 6; (АН) =(АН) + 1; AF = 1, CF = 1. В противном случае AFн CF = 0. В обоих случаях (AL) = (AL) AND Ofh.
Флаги: OF = ? SF = ? ZF = ? AF = r PF = ? CF = F
AAD
•AAD
D50A
Подготовка двухзначного неупакованного BCD-числа в регистре АХ для операции деления.
Действие: (AL) (АН х 10) + AL; (АН) - OOh.
Замечание: процессор воспринимает другой машинный код этой команды, не имеющий мне- моники,—D518.Еедействие:(AL)=(АНх18)+AL;(АН) =OOh.
Флаги: 0F = ? SF=rZF=? PF = rCF = ?
AAM
AAM
04 ОА
• Коррекция результата умножения двухнеупакованных BCD-чисел.
Действие: разделить содержимое регистра AL на 10; частное записать в регистр АН, остаток — в регистр A L.
Замечание: процессор воспринимает другой машинный код этой команды — D4 i8 (без мнемоники). Ее действие: разделить содержимое регистра AL на 18; частное записать в регистр АН, остаток — в регистр AL Если i8 = 0, то процессор генерирует исключение ошибки деления #DE.
Флаги: OF = ?SF=rZF= rAF = ? PF= rCF- ?
AAS
•AAS
•3F
17 Зак. 256
Коррекция результата вычитания командой SUB двух неупакованных одноразрядных BCD- чисел.
Действие: если младший полубайт регистра AL > 9 или флаг AF= 1, то значение младшего полубайтаALуменьшаетсяна6, значениеАНуменьшаетсяна1,флагиAFиCFустанавливаютсяв1. Если ни одно из этих условий не выполняется, команда устанавливает флаги AF = CF 0. В обоих случаях значение старшего полубайта регистра AL обнуляется.
Флаги: OF = ?SF«?ZF-? AF»rPF*?CF«r
ADC
• ADC приемник, источник
■ 14 ib
ADC AL,imm8
15iw
ADC AXjmml6
15id
ADC EAX,imm32
80/2 ib
ADC r/m8,imm8
81/21W
ADCr/ml6,imml6
81 /2 id
ADCr/m32,imm32
83 /2ib
ADC r/ml6,imm8
83 /2 ib
ADC r/m32jmm8
10/r
ADC r/m8,r8
11/r
ADCr/ml67rl6
11 /r
ADC 『/m32j32
12/г
ADC T8,r/m8
13/г
ADCrl6,r/ml6
13/г
ADC r32,r/m32
Сложение с учетом значения флагапереноса CF.
Действие: приемник = приемник + источник + CF. Флаги: OF = r SF= ZF = r AF - r PF= r CF = r
ADD
• ADD приемник, источник
・04 ib
05 iw
05 id
80 /0 ib
81 /0 iw
81 /0 id
83 /0 ib
83 /0 ib
00 /r
01 /r
01/r
02 /r
03 /r
03 /r
ADDAL,imm8
ADD AX,imml6
ADD EAX,imm32
ADD r/m8,imm8
ADD r/ml6Jmml6
ADD r/m32zimm32
ADD r/ml6jmm8,
ADD 17nl32jmm8
ADD r/m8,r8
ADDr/ml6zrl6
ADD r/m32,r32
ADD r8,r/m8
ADD rl6,r/ml6
ADD r32,r/m32
Сложение двух целочисленных двоичных операндов.
Действие: приемник = приемник + источник.
При переполнении приемника флаг CF устанавливается в 1.
Флаги: OF-rSF^rZF»rAF-r PF*rCF«r
AND
• AND приемник, источник
•24 ib
25 iw
AND ALimm8
AND AX,imml6
25 id AND EAXrimm32
80 /4ib AND r/m8,imm8.
81 /4 iw AND r/ml6,imml6
81 /4 id AND r/m32,imm32
83/4ibAND r/ml6fimm8
83 /4 ibAND r/mSZJmmS
20 /rAND r/m8,r8
21/rAND r/m 16/16
21/rAND r/m32,r32
22 /rAND r8,r/m8
23 /rAND rl6,r/ml6
23 /r ANDr32,r/m32
• Логическое И.
Действие: приемник = приемник AND источник.
Флаги: OF = О SF == r ZF = r AF = ? PF = r CF =О
ARPL
• ARPL приемник, источник
63 /r ARPLr/ml6,rl6
Настройка поля RPL селектора.
Действие: операнд приемник содержит селектор вызываемой программы, операнд источник — селектор вызывающей программы. Команда ARPL сравнивает биты RPL селектора приемника с битами RPL селектора источника и в зависимости от результатов этого сравнения выполняет действия:
犠 если RPL приемника < RPL источника, то ZF = 1 и RPL приемника = RPL источника;
覇: если RPL приемника > RPL источника, то: ZF之0.
Флаги: ZF г
BOUND
• BOUND индекс, границы_массива
62/гBOUND rl6,ml6&16
62 /гBOUND r32,m32&32
• Контроль нахождения индекса массива в границах.
Действие: сравнить значение в 16/32-разрядном регистре индекс с диапазоном значений, первое из которых — это нижний индекс, второе — верхний индекс плюс размер операнда в байтах. Значения нижнего и верхнего индексов расположены последовательно в двух ячейках памяти размером слово/двойное слово, адресуемых операндом 「ранпць1_ма(хмва. Если в результате проверки значение из регистра вышло за пределы указанного диапазона значений, то возбуждается прерывание с номером 5, если нет, программа продолжает выполнение.
BSF
• BSF результат, источник
• OF ВСBSFrl6//ml6
OF BCBSF r32,r/m32
• Определение номера позиции в операнде источник крайнего справа единичного бита.
Действие: просмотр битов операнда источник, начиная с младшего. Номер позиции первого единичного бита слева записывается в регистр результат, флаг ZF устанавливается в 0. Если единичных битов нет, то флаг ZF устанавливается в 1, а регистр результат оказывается неопределенным.
Флаги: OF= ? SF - ? ZF = rAF = ? PF = ? CF= ?
BSR
• BSR результат, источник
OFBDBSRП6,г/е16
OF BDBSR r32,r/m32
Определение номера позиции крайнегослева единичного бита в операнде источник.
Действие: команда просматривает биты операнда источник начиная со старшего бита 15/31, и, если встречается единичный бит, флаг ZF устанавливается в 0, а в регистр результат записывается номер позиции (отсчет осуществляется относительно нулевой позиции), в которой встретился единичный бит. Если единичных битов нет, то флаг ZF устанавливается в 1. Диапазон значений результата зависит от разрядности второго операнда: для 16/32-разрядных операндов это, соответственно,0...15/0...31.
Флаги: OF = ? SF = ? ZF = r AF = ? PF ? CF = ?
BSWAP
BSWAP источник
0FC8 + rdBSWAP r32
Изменение порядка следования байтов в операнде источник.
Действие:
汶 TEMPv источник;
. источник(7...0)<ТЕМР(31...24);
: источник(15...8)<ТЕМР(23...1б);依
источник(23..Лб)<ТЕМР(15...8);
掳 источник (31...24)<ТЕМР(7...0),
Здесь, TEMP — временная ячейка памяти (32 бита), которую процессор выделяет в своих внутренних структурах для выполнения этой операции.
ВТ
ВТ источник, индекс
OF A3ВТ r/ml6,rl6
OF A3ВТ r/m32,r32
OFBA /4ib ВТ r/ml6,imm8
OF BA /4 ib ВТ r/m32jmm8
Определение значения конкретного бита в операнде источник.
Действие: номер проверяемого бита операнда источник задается операндом индекс. После выполнения команды флаг CF устанавливается значением проверяемого бита.
Флаги:ОЕ = ?5Р = ?7Р«? AF»?PF-?CF-r
втс
ВТС источник, индекс
OFBBВТСг/т1б,г1б
OF BBВТС г/т32,「32
OF ВА /7 ib ВТС r/ml6,imm8
OF BA /7ib ВТС r/m32,imm8
Определение иинвертирование значениязаданного бита в операнде источник.
Действие: номер проверяемого бита операнда источник задается операндом индекс (значение из диапазона 0...31). После выполнения команды значение выбранного бита инвертируется, а флаг CF устанавливается исходным значением этого бита.
Флаги: OF = ? SF = ? ZF = ? AF = ? PF二? CF = r
BTR
• BTR источник, индекс
• OF ВЗBTR r/ml6zrl6
OF B3BTR r/m32,r32
OF BA /6 ib BTR r/ml6,imm8
OF BA/6ib BTR r/m32,imm8.
Определение значения заданного бита в операнде источник и сброс его в 0.
Действие: номер проверяемого бита операнда источник задается операндом индекс (значение из диапазона0...31). После выполнения команды флаг CF устанавливается исходным значением ■)ТОГО бита, а сам бит устанавливается в 0.
Флаги: OF ? SF = ? ZF = ? AF = ? PF = ? CF = r
BTS
BTS источник, индекс
OF AB BTS r/ml6zrl6
OF AB BTS r/m32,r32
OF BA /5ib BTS "ml6jmm8
OF BA/5ib BTS r/m32,imm8
Определение значения заданного бита в операнде источник и установка его в 1.
Действие: номер проверяемого бита задается операндом индекс (значение из диапазона 0...31). После выполнения команды флаг CF устанавливается исходным значением этого бита, а сам бит устанавливается в
Флаги: OF= ? SF=? ZF= ? AF= ? PF = ? CF = r
CALL
CALL цель
E8 cwCALL rell6
E8 cdCALL rel32
FF/2CALLr/ml6
FF/2CALL r/m32
9A cdCALL ptrl6:16
9A cpCALL ptrl6:32
FF/3CALLml6:16
FF/3CALL ml6:32
Вызов процедуры или переключение задачи.
Действие: далее перечислены возможные варианты задания операнда цель.
缎 rell6/32 — близкий относительный переход. Значение rell6/32 трактуется как знаковое и является смещением перехода относительно следующей за CALL команды в сегменте кода, то есть адрес цели равняется EIP/IP + rell6/32. В стек заносится содержимое EIP/IP.
т П6/32 или т16/32 — близкий абсолютный косвенный переход. Цель — регистр Г16/32 или ml6/32. Содержимое этого регистра является смещением команды, которой передается управление, в текущем сегменте кода. В EIP/IPзагружается содержимое из门 6/32 или Ш16/32. В стек заносится содержимое EIP/IP.
瞪 e16:16(32) — дальний абсолютный косвенный переход. Цель — адрес ячейки памяти размером 32/48 бит со структурой ml6:16(32), содержащей компоненты адреса перехода.
^ pt门6:26(32) — дальний абсолютный переход. Цель — компоненты полного адреса в виде 4- или 6-разрядного указателя, по которому необходимо произвести переход.
Особенности выполнения команды CALL в двух последних вариантах определяются режимом работы процессора.
笠發 В реальном режиме или режиме виртуального процессора 8086 (РЕ = 0 или РЕ = RS 1 и VM = 1) в EIP/IP и CS загружаются значения смещения и адреса сегмента из указателя в памяти или команды CALL. В стек заносится содержимое EIP/IP и CS.
繚 В защищенном режиме (РЕ = 1 и VM = 0) анализируется байт прав доступа AR в дескрипторе целевого сегмента. В зависимости от его значения производится пять типов перевода к цели: D передача управления подчиненному сегменту кода;
передача управления неподчиненному сегменту кода;
передача управления через шлюз вызова;
П переключение задачи (селектор соответствует дескриптору — шлюзу задачи);
П переключение задачи (селектор соответствует дескриптору — сегменту TSS).
Флаги (кроме случая переключения задачи): не изменяются.
При переключении задачи значения флагов изменяются содержимым регистра EFLAGS в сегменте состояния TSS задачи, на которую производится переключение.
CBW/CWDE
•CBW/CWDE
•98
CBW — преобразование байта в слово,
CWDE — преобразование слова в двойное слово.
Действие: команда копирует знаковый бит регистра AL(AX) на все биты регистра АН (ЕАХ).
CWD/CDQ
CWD/CDQ
•99
• CWD — преобразование слова в двойное слово,
CDQ — преобразование двойного слова в учетверенное слово.
Действие: команда CWD копирует значение старшего бита регистра АХ на все биты регистра DX; команда CDQ копирует знаковый бит регистра ЕАХ на все биты регистра EDX.
CLC
•CLC
•F8
Сброс флага переноса CF.
Флаги: CF = О
CLD
•CLD
FC
Сброс флага направления DF.
Флаги: OF = О
CLFLUSH
CLFLUSH операнд
OFAE/7
Аннулирование строки кэша, содержащей операнд.
Действие: аннулирует строки кэш-памяти, которая содержит линейные адреса, определяемые операндом, для кэшей всех уровней (данных и команд). Если аннулируемая строка помечена как измененная (dirty), то предварительно она записывается на диск.
CLI
•CLI
•FA
Сброс флага прерывания IF.
Флаги: IF = 0 (если CPL< = IOPL)
Замечания: флаг IF не сбрасывается, если CRO.PE - 1, CPL > IOPL и VM = 0 или CRO.PE = 1, IOPL < 3 иVM = 1. Флаг IF и команды CLI и STIне влияют на генерацию исключений и прерывания NMI.
CLTS
CLTS
OF Об
Сброс флага переключения задач TS в регистре CRO (бит 3) в 0.
CMC
•CMC
•F5
Инвертирование флага переноса CF.
Флаги: CF = r
CMOVcc
CMOVcc приемник, источник
Передача данных при выполнении условий, определяемых состоянием соответствующих флагов.
Действие: если состояние флагов (см. табл. П.1)соответствует условиям выполнения коман- ды,топроизводитсяпересылказначенияисточникавприемник.Впротивномслучаевыполнение команды завершается без пересылки.
Таблица П.1. Состояние флагов при выполнении команды CMOVcc
Машинный код
Мнемокод
Флаги
Пересылка из источника в приемник
OF 40 cw/cd
CMOVO rl6,r/ml6 (r32,r/m32)
OF= 1
Если переполнение|
OF 41 cw/cd
CMOVNOrl6,r/ml6 (r32,r/m32)
OF = 0
Если нет переполнения
OF42cw/cd
CMOVBrl6,r/ml6(r32,r/m32)
CMOVNAErl6,r/ml6(r32,r/m32)
CF = 1
Если ниже (не выше пли равно)
OF 42 cw/cd
CMOVCrl6,r/m 16 (r32,r/m32)|
CF= 1
Если перенос
OF 43cw/cd
CMOVNCrl6,r/ml6(r32,r/m32)
CF = 0
Еслинетпереноса
OF 44 cw/cd
CMOVE rl6,r/ml6 (r32,r/m32)
CMOVZrl6(r/ml6 (r32,r/m32)
ZF = 1
Если равно (нуль)
OF 45 cw/cd
CMOVNErl6, r/m!6 (r32,r/m32)
CMOVNZrl6, r/ml6 (r32,r/m32)
ZF = 0
Если не равно (не нуль)
OF46cw/cd
CMOVBE rl6, r/ml6 (r32,r/m32) MOVNArl6,r/m!6(r32,r/m32)
CF = 1|
ZF = 1
Если ниже или равно (не выше)
OF47cw/cd
CMOVA "6, r/ml6 (r32,r/m32)
MOVNBE "6, r/ml6 (r32,r/m32)
CF = 0& ZF-0
Если выше (не ниже или равно)
L3 продолжение H
Таблица П .1 (продолжение)
Машинный код
Мнемокод
Флаги
Пересылка из источника в приемник
OF 48 cw/cd
CMOVS rl6, r/ml6 (r32,r/m32)
SF = 1
Если знак
OF49cw/cd
CMOVNS г16, г/т!6 (r32,r/m32)
SF = 0
Если не знак
OF 4A cw/cd
CMOVP г16, г/т!6 (г32,г/т32)1
MOVPEZ6, г/т 16 (r32,r/m32)
PF = 1
Если паритет (четное количество единичных битов)
OF4Bcw/cd
CMOVNP г16, г/т16 (г32,г/т32) 1
CMOVPO г16, г/т 16 (г32,г/т32)
PF = 0
Если непаритет
OF 4C cw/cd
CMOVL г16,г/ш16 (r32,r/m32)1
CMOVNGE г!6,г/т16 (г32,г/т32)
SFoOF
Если меньше (не больше или равно)
OF 4D cw/cd
CMOVGE rl6, г/т16 (г32,г/т32) !
CMOVNLrl6, r/ml6 (г32,г/т32) i
SF = OF
Если больше или равно (не меньше)
OF 4E cw/cd
CMOVLE Г16, r/ml6 (r32Ir/m32) 1 CMOVNGг!6, г/т16(г32,г/т32).
ZF= 1|
SFoOF
Если меньше или равно (не больше)
OF4Fcw/cd
CMOVG г!6, г/т16 (г32,г/т32)|
CMOVNLErl6,r/ml6 (r32,r/m32)
ZF 工 0& SF-OF
Если больше (не меньше или равно)
СМР
СМРоперандах, операнд_2
3Cib CMP AL,imm8 3D iw CMP АХ, imml6 3D id CMP EAXfimm32 80/7 ib CMP r/m8,imm8 81 /7 iw CMP r/ml6,imml6 81 /7 id CMP r/m32,imm32 83/7ib CMP r/ml6,imm8 83/7 ib CMP r/m32,imm8 38/rCMP「/e8j8
39/r CMP r/m!6,rl6 39/r CMP r/m32/32 ЗА /гCMP『8j/m8
3B/rCMPrl6//ml6
3B/rCMPr32,r/m32
Сравнение двух операндов.
Действие: операнды операнд_1 и операнд_2 сравниваются методом вычитания, при этом сами операнды не изменяются. По результатам сравнения устанавливаются флаги (см. описание команды SBB).
Флаги: OF - г SF - г ZF * Г AF * Г PF - Г CF = Г
CMPS/CMPSB/CMPSW/CMPSD
■ CMPS приемник, источник
CMPSB/CMPSW/CMPSD
Аб А7 A7 Аб A7
A7
CMPS DS:(E)SI,ES:(E)DI
CMPS DS:SI, ES:DI
CMPS DS:ESI, ES:EDI
CMPSB
CMPSW
CMPSD
Сравнение цепочек байтов/слов/двойных слов. Адреса сравниваемыхэлементов цепочек предварительно загружаются: адрес источника в пару регистров DS:ESI/SI; адрес приемника в пару регистров ES:EDI/DI.
Действия:
Вычесть элементы (источник - приемник).
В зависимости от состояния флага DF изменить значение регистров ESI/SI и EDI/DI:
D если DF = 0 — содержимое регистров увеличивается на длину элемента цепочки;
□ если OF = 1 — содержимое регистров уменьшается на длину элемента цепочки.
В зависимости от результата вычитания устанавливаются флаги:
П если элементы цепочекне равны, то CF = 1, ZF = 0;
П если элементы цепочек или цепочки в целом равны, то CF = О, ZF = 1.
При наличии префикса повторения выполнить определяемые им действия (см. описание команд REPE/REPNE).*•
Флаги: OF = г SF = г ZF = г AF = г PF = г CF = г
CMPXCHG
CMPXCHG приемник, источник
OF BO/rCMPXCHG r/m8,r8
OF Bl/rCMPXCHG r/ml6,rl6J
OF Bl/rCMPXCHG r/m32,r32
Сравнение с аккумулятором и обмен.
Действие: если аккумулятор (AL/AX/EAX) и приемник не равны, то установить ZF в 0 и переслать содержимое приемника в аккумулятор (AL/AX/EAX). Если аккумулятор и приемник равны, то установить ZF в 1 и переслать источник в приемник.
Флаги: OF === rPF=rCF=
CMPXCHG8B
CMPXCHG8B приемник
OF C7 /1 e64
Сравнение и обмен восьми байтов.
Действия: сравнить содержимое регистров EDX:EAX и ячейки памяти приемник (m64). Если (EDX:EAX) = (т64), то ZF = 1, (т64) - (ЕСХ:ЕВХ). В противном случае: ZF =0, (EDX:EAX) = (тб4).
Флаги: ZF = r
CPUID
CPUID
OFA2.
Получение информации отекущем процессоре.
Действие: для получения информации о процессоре необходимо в регистр ЕАХ поместить параметр — одно из значений О, 1 или 2.
Если ЕАХ = 0, то в регистрах ЕАХ, ЕВХ, EDX, ЕСХ формируется следующая информация:
ЕАХ = п, где n — максимально допустимое значение параметра, которое может быть помещено в регистр ЕАХ для задания режима сбора информации;
И ЕВХ + EDX + ЕСХ — в этих регистрах содержится строка-идентификатор процессораGenuinelntcl (genuine — подлинный, истинный):
П EBX= 7 56E6547h"Genu" (Gin BL);
О ЕСХ = 6C65746Eh "ntel" (n in CL);
□ EDX -49656E69h "inel" (i in DL).
Если ЕАХ = 1, то в регистрах процессора сформируется следующая информация:
ЕАХ = n — информация о процессоре (см. табл. П.2 и П.З);
EDX= п — информация о возможностях процессора (см. табл. П.4).
Если ЕАХ = 2, то в регистрах ЕАХ, ЕВХ, ЕСХ и EDX формируется информация о кэш-памяти первого уровня и TLB-буферах. Первый байт регистра ЕАХ содержит число, означающее, сколько раз необходимо последовательно выполнить команду CPUID для получения полной информации о K3H卜哈 мяти первого уровня и TLB-буферах. Другие байты регистра ЕАХ и все байты регистров ЕВХ, ЕСХ и EDX содержат однобайтовые дескрипторы, характеризующие кэш-память и TLB-буферы (см. документацию по процессору). Старший бит каждого регистра характеризует достоверность информации в регистре. Если он равен нулю, то информация достоверна, иначе — регистр не используется.
Таблица П.2. Поля регистра ЕАХ после выполнения команды CPUID (при ЕАХ = 1)
Биты ЕАХ
Назначение
0...3
Версия изменений модели (stepping ID)
4...7
Модель в семействе (см. табл. А.З)
8...11
Семействопроцессоров(см. табл. А.З)
12...13
Тип процессора (00 — обычный процессор; 01 — Overdrive-процессор;
10 — процессор для использования в двухпроцессорных системах)
Таблица П.З. Значения битов 4., .7 и8...11 регистра ЕАХ
Биты ЕАХ (8... 11)
Биты ЕАХ (4 …7)1
Тип процессора
0100
0000 или ООО!1
I486DX
0100
0010
I486SX
0101
0010
Pentium 75-200
0101
0100
Pentium ммх 166-200
ОНО
0001
Pentium Pro
оно
ООН
Pentium II, модель 3
оно
0101
Pentium II, модель 5, Pentium II Xeon
оно
ОНО
Celeron,модель6
оно
0111
Pentium III и Pentium III Xeon
оно
ООП
Pentium II OverDrive
1111
0000
Pentium IV
Таблица П.4. Поля регистра EDX после выполнения команды CPUID (при ЕАХ = 1)
Биты
EDX
Назначение (если биты установлены)
0
Присутствуетсопроцессорс наборомкоманд i387
1
Поддержка расширенных возможностей обработки прерываний в режиме виртуального процессора i80861
2
Процессор поддерживает точки прерывания ввода-вывода (точки останова по обращению к портам) для предоставления расширенных возможностей отладки и доступ к регистрам DR4 и DR5. Флаг CR4.DE - 1
3
Процессор поддерживает 4-мегабайтные страницы (бит CR4.PSE)
Биты
EDX
Назначение (если биты установлены)
4
Поддержка счетчика меток реального времени TSC (команда RDTSC)
5
Поддержка команд RDMSR и WRMSR для работы с модельно-зависимыми регистрами
6
Процессор поддерживает физические адреса большие, чем 32 бита (CR4.PAE), расширенный формат элемента таблицы страниц, дополнительный уровень трансляции страничного адреса и 2-мегабайтные страницы
7
Поддержка исключения машинного контроля 18 (Machine Check Exception, MCE)
8
Поддержка инструкции CMPXCHG8B
9
Процессор содержит программно-доступный контроллер прерываний APIC, который
доступен для использования1
10
Резерв
И
Поддержка инструкций быстрых системных вызовов SYSENTER и SYSEXIT
12
Поддержка регистра управления кэшированием MTRR—CAP (относится к MSR- регистрам)
13
Поддержка работы с битом G, определяющим глобальность страницы в PTDE и РТЕ. Бит CR4.PGE = 1
14
Поддержка архитектуры машинного контроля (MSR-регистр MCG_CAP)
15
Поддержка команд CMOVcc, FMOVcc и FCOMI, если установлен бит EDX.O = 1 (см. выше)
16
Поддержка таблицы физических атрибутов страниц PAT
17
Поддержка 36-разрядной физической адресации с 4-мегабайтными страницами
18
Процессор поддерживает собственную идентификацию по уникальному 96-разрядному номеру PPN (Physical Processor Number), и эта поддержка активна
19
Поддержка команды CLFLUSH
20
Резерв
21
Процессор поддерживает способность записывать информацию в резидентный буфер памяти
22
Поддержка внутренних MSR-регистров для мониторинга температуры процессора и программного регулирования производительности процессора (ACPI)
23
Поддержка целочисленного MMX-расширения
24
Процессор поддерживает команды FXSAVE и FXRSTOR
25
Поддержка MMX-расширения (SSE) с плавающей точкой
26
Поддержка MMX-расширения (SSE2) с плавающей точкой
27
Поддержка самослежения (self snoop) за состоянием кэша
28
Процессор поддерживает технологию Hyper Threading
29
Процессор обеспечивает схемотехнический термальный контроль
30
Процессор архитектуры Intel Itanium; поддерживается соответствующий набор команд|
31
Поддержка контакта FERR#/PBE# для сигнализации о задержанном прерывании
Перед своей работой команда CPUID выполняет сериализацию команд записи в память.
DAA
•DAA
•27
• Десятичная коррекция результата сложения двух упакованных BCD-чисел сцедыо получения правильного двузначного десятичного числа.
Действия:
Если AFs 1 или значение младшей тетрады AL > 9, то: (AL) = (AL) + 6; AF =1;при возникновении переноса при сложении установить флаг CF. Иначе — AF = 0.
Если CF = 1 или значение старшей тетрады AL>9, то: (AL) = (AL) + 60; CF= 1. Иначе — CF = 0.
Флаги: OF « ? SF ■ г ZF = г AF * г PF * г CF * г
DAS
•DAS
•2F
• Десятичная коррекция после вычитания двух BCD-чисел вупакованном формате.
Действия:
Если AF = 1 или значениемладшей тетрады AL > 9, то (AL) (AL) - б; AF 1; в случае заема при вычитании установить флаг CF. Иначе — AF = 0.
Если CF = 1 или значение старшей тетрады AL > 9, то (AL) = (AL) -60; CF = 1. Иначе — CF = 0.
Флаги: OF«? SF«rZF*r AF»rPF«rCF-r
DEC
DEC операнд
FE /1DEC r/m8
FF/1DEC r/ml6
FF/1DEC r/m32
48 + rwDECГ16
48 + rdDEC r32
Уменьшение значения операнда на единицу.
Флаги: OF = r SF - г ZF = г AF = r PF = r, флаг CF не меняется.
DIV
DIV делитель
F6 /6 DIV r/m8
F7 /6DIVr/ml6
F7 /6DIV r/m32
Беззнаковое деление.
Действие: делимое задается неявно, и его размер зависит от размера делителя, который явно указывается в команде. Местоположения делимого, делителя, частного и остатка — в зависимости от их размерности (табл. П.5)..
Флаги: OF=?SF=?ZF=?AF=== ?
Таблица П.5. Местоположения делимого, делителя, частного и остатка после выполнения команды DIV
Размер операнда
Делимое
Делитель
Частное
Остаток
Максимальное частное
Слово (байт)
АХ
r/m8
AL
АН
255
Двойное слово (слово)
DX:AX
r/ml6
АХ
DX
65 535
Учетверенное слово (двойное слово)
EDX:EAX
r/m32
ЕАХ
EDX
232 -1
ENTER
ENTER размер_кадра, лексический_уровень
С8 iw 00 ENTER imm 16,0
C8iw01 ENTERimml6,l
C8 iw ib ENTER imml6,imrn8
Установить кадр стека для локальных переменных процедуры.
Действие: операнд размер_кадра определяет размер кадра стека, операндлексический_уровень задает лексическую вложенность кадра стека. Значение лексического уровня определяет количество указателей кадра стека, копируемых в область дисплея нового кадра стека из предыду- uiero кадра. Оба операнда непосредственные.
HLF
•HLT
•F4
Останов процессора.
Действие: команда переводит процессор в состояние останова. Выполнение будет продолжено но приходу разрешенного прерывания, NMI или аппаратного сброса. Команда HLT является привилегированной.
IDIV
IDIV делитель
F6 /7IDIV r/m8
F7 /7IDIV r/ml6
F7 /7IDIV r/m32
Целочисленное деление со знаком.
Действие: делимое задается неявно, и его размер зависит от размера делителя, который явно указывается в команде. Местоположения делимого, делителя, частного и остатка — в зависимости от их размера (табл. П.6),
Таблица П.6. Местоположения делимого, делителя, частного и остатка после выполнения команды IDIV
Размер операнда
Делимое
Делитель
Частное
Остаток
Максимальное частное
Слово(байт)
АХ
r/ni8
AL
АН
-128... +127
Двойное слово (слово)
DX:AX
г/ш16
АХ
DX
-32 768... +32 767
Учетверенное слово (двойноеслово)
EDX:EAX
r/m32
ЕАХ
EDX
-230一 1
Остаток всегда имеет знак делимого. Знак частного зависит от состояния знаковых битов (старших разрядов) делимого и делителя.
Флаги: 0F = ? SF= ? ZF = ? AF = ? PF = ?g?
IMUL
IMUL множитель_1
IMUL множитель.!, множитель_2
IMUL произведение, множитель_1, множитель_2
F6 /5IMULr/m8
F7 /5IMULr/ml6
F7 /5IMULr/m32
OFAF/r IMULrl6,r/ml6
OF AF /г IMULr32#r/m32
6B/ribIMUL rl6,r/ml6zimm8
6B /r ibIMUL r32//m32jmm8
6B/ribIMUL rl6,imm8
6B/rib IMULr32jmm8
69 /r iwIMUL rl6zr/ml6jmml6
69 /r idIMUL r32,r/m32,imni32
69 /r iwIMUL 门 6,imml6
69/rid IMULr32,imm32
Целочисленное умножение со знаком.
Действие: команда имеет три формы, различающиеся количеством операндов.
Я С одним операндом — требует явного указания местоположения только одного сомножителя, который может быть расположен в ячейке памяти или регистре. Местоположение произведения зависит от размерности множителей (табл. П.7).
薇 С двумя операндами — первый операнд определяет местоположение первого сомножителя. На его место впоследствии будет записан результат. Второй операнд определяет местоположение второго сомножителя.
緻 С тремя операндами — первый операнд определяет местоположение результата, второй операнд — местоположение первого сомножителя, третий операнд может быть непосредственно заданным значением размером в байт, слово или двойное слово.
Таблица П.7. Местоположения произведения после выполнения команды IMUL
Размер множителейМножитель 1Множитель 2
Произведение
БайтALr/m8
АХ
СловоАХ1 r/m16
DX:AX
Двойное словоDX:AX1 r/m321
EDX:EAX
Флаги (для однооперанднойкоманды): OF= CF 1 — значимые биты переносятся в верхнюю половину результата; OF»О CF = 0 — результат помещается точно в младшей половине результата. Состояние остальных флагов: SF- ? ZF= ? AF= ? PFн?
Флаги (для двух- и трехоперандной команды): OF^ 1 CF* 1 — результат слишком большой и усекается; OF = 1 CF - 1 — размер результата точно соответствует операнду назначения. Состояние остальных флагов: SF= ? ZF- ? AF= ? PF= ?
IN
IN аккумулятор, номер^порта
■ E4ibIN ALimm8
Е5 ibIN AXjmm8
Е5 ibIN EAX,imm8
ЕСIN ALDX
EDIN AX,DX
EDIN EAX,DX
Ввод операнда размером байт, слово, двойное слово из порта.
Действие: ввод операнда размером байт, слово, двойное слово из порта ввода-вывода в один из регистров AL/AX/EAX. Номер порта задается вторым операндом в виде непосредственного значения (0...255) или значения в регистре DX.
INC
•INC операнд
■ FE /0INC r/m8
FF /0INCr/ml6
FF/OINCr/m32.
40 + rwINC П6
40+ rdINC r32
■ Увеличение операнда размером байт, слово, двойное слово на 1. Команда не воздействует на флаг CF.
Флаги: OF * r $F * r ZF * r AF = г PF « r
INS/INSB/INSW/INSD
INS приемник, порт
INSB/INSW/INSD
6СINS ES:(E)DI, DX
6DINS ES:DI, DX
6DINS ES:EDI, DX
6CINSB
6DINSW
60INSD
• Ввод строк байтов/слов/двойных слов из порта ввода-вывода в память.
Действие: номер порта ввода-вывода хранится в регистре DX, адрес ячейки памяти — в ES: EDI/ DL Замена сегментного регистра недопустима. Команда передает элемент из порта ввода-вывода в память и в зависимости от состояния флага DF изменяет значение в регистре EDI/DI:
Л если DF = 0, увеличить содержимое этих регистров- на длину структурного элемента последовательности;
is если DF = 1, уменьшить содержимое этих регистров на длину структурного элемента последовательности.
При наличиипрефикса выполняются определяемые им действия (см. описание команды REP).
INT/INTO/INT 3
■ INT номер_прерывания
■ CDibINT номер_прерывания
. Вызов подпрограммы обслуживания прерывания.
INTO
СЕINTO
Прерывание, если переполнение.
INT3.
ССINT3
Вызов подпрограммы обслуживания прерывания 3.
Действие: команда INT генерирует вызов подпрограммы обслуживания прерывания с номером (0...255), заданным операндом команды.
В реальном режиме команда INT n записывает в стек регистр флагов EFLAGS/FLAGS и адрес возврата — содержимое регистров CS и EIP/IP. Далее сбрасываются в ноль флаги IF, TF и АС, после чего управление передается программе обработки прерывания с номером п.
В защищенном режиме проверяются условия VM = 1 и IOPL<3. Если они выполняются, то возбуждается исключение #GP(O). Иначе, проверяется тип дескриптора: он должен быть шлюзом ловушки, задачи, прерывания.
綴 Шлюз задачи — селектор в дескрипторе шлюза указывает на дескриптор TSS в GDT. Производится переключение задач (с вложением) и отслеживаются условия возникновения исключений.
齡 Шлюз ловушки или прерывания — при этом возможны два типа передачи управления обработчику прерывания: прерывание в режиме виртуального процессора 8086, передача управления между уровнями привилегий.
Команды вызова обработчиков прерываний INTO и INT 3 являются специализированными: INTO инициирует прерывание с номером 4, если установлен флаг OF; INT 3 генерируетспсциаль- ный однобайтовый код операции (Occh), который предназначается для вызова обработчика исключения отладки.
Флаги: в зависимости от режима работы процессора флаги IF, TF, NT, AC, RF иVMмогут быть очищены. Если прерывание использует шлюз задачи, то любые флаги могут быть установлены или очищены в соответствии с образом EFLAGS в TSS повой задачи.
INVD
INVD
OF 08.
Недостоверность кэш-памяти всех уровней.
Действие: очистка кэш-памяти первого уровня (внутренней) и генерация сигнала на очистку кэш-памяти второго уровня (внешней).
1NVLPG
INVLPG адрес
0F 01/7
Недостоверность элемента буфера TLB.
Действие: просмотр элементов буфера TLB и выяснение, соответствует ли адрес, указанный в команде, одному из элементов этого буфера. Если соответствие выявлено, то данный элемент буфера TLB помечается как недостоверный и работа заканчивается. Если соответствия не выявлено, то работа команды заканчивается.
IRET/IRETD
IRET/IRETD
CF
Возврат из прерывания
Действия: зависят от режима работы м икропроцесссора.
戀 В реальном режиме выполняется последовательное извлечение из стека содержимого регистров EIP/IP, CS и EFLAGS/FLAGS, и работа прерванной программы возобновляется.
В защищенном режиме действия команды IRET определяются флагами NT и VM в регистре EFLAGS, а также значением флага VM в образе EFLAGS, сохраненного в текущем стеке. В зависимости от их состояния процессор выполняет следующие виды возврата: возврат из режима V86; возврат в режим V86; возврат к коду на другом уровне привилегий; возврат из вложенной задачи. Если NT = 0, то производятся действия по возврату управления прерванной программе, при этом характер этих действийзависит отсоотношения уровней привилегированности прерванной программы и программы обработки прерывания. Если NT = 1, то производятся действия по переключению задач.
Флаги: Все флаги в регистре EFLAGS могут быть модифицированы.
Jcc
Jcc метка
Переход, если выполнено условиесс.
Действие: команды условного перехода, в зависимости от своей мнемоники, анализируют флаги, и если проверяемое условие истинно, то производится переход к ячейке, обозначенной операндом. Если проверяемое условие ложно, то производится переход к следующей команде.
Идентификатор метка преобразуется ассемблером в непосредственные значения rel8 (uselS) и rell6/rel32 (use32), которые во время выпо/игеннядобавляются к текущему значению IP/EIP. Мнемоника команд условного перехода показана в табл. П.8 (логические условия «больше» и «меньше» относятся к сравнениям целочисленных значений со знаком, а «выше» и «ниже» — к сравнениям целочисленных значений без знака).
Таблица П.8. Мнемоника команд условного перехода
Машинный код
Мнемокод
Флаги
Переход
70 cb
JO rcl8
OF = 1
Короткий, если переполнение|
71 cb
JNO rcl8
OF = 0
Короткий, если нет переполнения
72 сЬ
JB rel8
JC rel8
JNAE re!8
CF= 1
Короткий, если ниже
Короткий, если перепое
Короткий, если не выше или равно
73 сЬ
JAE re!8
JNBrel8
JNC rel8
CF = 0
Короткий, если выше или равно
Короткий, если не ниже
Короткий, если не перенос
74 cb
JE rel8I
JZ re!8
ZF = 1
Короткий, если равно Короткий, если нуль
75 cb
JNZ re!8I
JNErel8
ZF = 0
Короткий, если не нуль
Короткий, если не равно
76 cb
JBE rel8I
JNA rel8
CF= 1|ZF= 1
Короткий, если ниже или равно (нс выше)
77 cb
JA rel8. I
JNBE rcl8
CF = 0&ZF 0
Короткий, если выше (не ниже или равно)
78 cb
JS re!8I
SF= 1
Короткий, если знак
79 cb
JNS re!8
SF = 0
Короткий, если не знак
7А cb
JP rel8
JPE rel8
PF = 1
Короткий, если паритет (четное количествоединичных битов)
7В cb
JPO re!8 JNP re!8
PF = 0
Короткий, если не паритет (нечетное количество единичных битов)1
7С cb
JL rcl8 JNGE rel8
SF<>OF
Короткий, если меньше (не больше или равно)
7 Deb
JGE rel8
JNLrel8
SF = OF
Короткий, если больше или равно (не меньше)
7E cb
JLE rel8
JNG rei8
ZF= 1|SF<>OF
Короткий, если меньше или равно (не больше)
7F cb
}G I cl8I
JNLE rel8
ZF = 0&SF = OF
Короткий, если больше (не меньше или равно)
ЕЗ cb
JCXZ rel8
JECXZ rel8
Короткий, если СХ/ЕСХ = 0
OF 80 cw/cd
JO rell6/32
OF = 1
Близкий, если переполнение
OF 81 cw/cd
JNO rel 16/32
OF = 0
Близкий,'если нет переполнения
OF 82 cw/cd
JBrel 16/32
JC re! 16/32
JNAE rel 16/32
CF = 1
Близкий, если ниже
Близкий, если перенос
Близкий, если не выше или равно
OF 83 cw/cd
JAE rei 16/32 JNB rel 16/32 JNC rel 16/32
CF = 0
Близкий, если выше или равно
Близкий, если нс ниже
Близкий, если не перенос!
продолжение
Таблица П.8(продолжение)
Машинный код
Мнемокод
Флаги
Переход
OF 84 cw/cd
JErel16/32
Zel 16/32
ZF= 1
Близкий, если равно Близкий, если нуль
OF 85 cw/cd
JNZ rel 16/32 JNErell6/32
ZF = 0
Близкий, если не нуль Близкий, если не равно
OF 86 cw/cd
JBErell6/32
JNA rel 16/32
CF=1|ZF = 1
Близкий, если ниже или равно (невыше)
OF 87 cw/cd
J A rel 16/32
JNBE rell6/32
CF = 0&ZF = 0
Близкий, если выше (не ниже илиравно)
OF 88 cw/cd
JS rel 16/32
SF = 1
Близкий, если знак
OF 89 cw/cd
JNS rel 16/32
SF = 0
Близкий, если не знак
OF8Acw/cd
JP rel 16/32
JPE rell6/32
PF = 1
Близкий, если паритет (четное количество единичных битов)1
OF 8B cw/cd
JPO rell6/32
JNP rel 16/32
PF = 0
Близкий, если не паритет (нечетное количество единичных битов)
0F8C
JL rel 16/32
JNGE rel 16/32
SF<>OF
Близкий, если меньше (не больше или равно)
OF 8D cw/cd
JGE rel 16/32 JNL rel 16/32
SF=OF
Близкий, если больше или равно (не меньше)
OF 8E cw/cd
JLE rell6/32
JNG rel 16/32
ZF= 1|SF<>OF
Близкий, если меньше или равно (не больше)
OF 8F cw/cd
JG rel 16/32
JNLE rel 16/32
ZF - O&SF = OF
Близкий, если больше (не меньше или равно)
JMP
JMP цель
ЕВ cb
JMP rel8
Е9 cw
JMP rell6
E9 cd
JMP rel32
FF/4
JMPГ/17116
FF/4
JMP r/m32
EA cd
JMP ptrl6:16
EA cp
JMP ptrl6:32
FF/5
JMP ml6:16
FF/5
JMPml6:32
■ Безусловный переход к цели.
Действие: далее перечислены возможные варианты задания операнда цель:
懇 rel8/16/32 — короткий относительный переход. Значение rel8/16/32 трактуется как знаковое и является смещением перехода относительно следующей заJMP команды в сегменте кода, то есть адрес цели равен (EIP/IP) + (rel8/16/32);
徽 r!6(32)/ml6(32) — близкий абсолютный косвенный переход. Цель — регистр П6(32) или ячейка памяти ml6(32), содержащие адрес перехода в текущем сегменте кода;
瀰 ptrl6:16(32) — дальний абсолютный переход. Цель — компоненты полного адреса в виде 4- или 6-разрядного указателя, по которому необходимо произвести переход;
驚 ml6:16(32) — дальний абсолютный косвенный переход. Цель — адрес ячейки памяти размером 32(48) битов со структурой ml6:16(32), содержащей компоненты адреса перехода.
Выполнение команды при дальнем переходе зависит от режима работы процессора:
9 в реальном режиме или режиме виртуального процессора 8086 команда JMP передает управление по адресу, определяемому операндом цель, который может задаваться прямо (ptrl6:16(32)) или косвенно (т16:16(32));
в защищенном режиме выполняются три типа переходов: дальний переход в подчиненный или неподчищенный сегмент кода; дальний переход через шлюз вызова; переключение задачи. Команду JMP нельзя использовать для передачи управления между уровнями привилегий. Флаги: изменяются только при переключении задачи.
LAHF
LAHF
•9F
Загрузка регистра АН содержимым младшего байта регистра флаговEFLAGS/FLAGS.
Действие: EFLAGS/FLAGS(SF:ZF:O:AF:O:PF:1:CF)—>АН, здесь в скобках указаны порядок следования флагов, пересылаемых в АН, и значения полей с фиксированным значением.
LAR
LAR приемник, источник
OF 02/г . LAR门 6j/mi6 OF 02 /гLAR r32,r/m32
Загрузка байта нрав доступа AR в регистр общего назначения.
Действие: выясняется доступность дескриптора, селектор которого находится в операнде источник. Еслидескриптор недоступенна текущем уровне привилегий или по какой-тодругой причине, то флаг ZF устанавливается в 0 и работа команды заканчивается. Если дескриптор доступен, то действия команды зависят от установленного размера операнда:
滋 если операнд 16-разрядный, из дескриптора извлекаются биты 32...47, которые помещаются в 16-разрядный регистр приемник, и выполняется операция приемник = приемник AND OffOOh; если операнд 32-разрядный, из дескриптора извлекаются биты 32...61, которые помещаются в 32-разрядный регистр приемник, и выполняется операция приемник приемник AND OOPffOOh (тетрада «?» не определена).
В табл. П.9 определены допустимые для команды LAR типы дескрипторов сегментов. Тип определяет значение младшей тетрады байта AR в дескрипторе при установленном в единицу бите S.
Таблица П.9. Типы дескрипторов сегментов для команды SR
Тип
Название
Допустимость
0
Резерв
Нет
1
Доступный 16-разрядный сегмент TSS
Да
2
LDT
Да
3
Занятый 16-разрядныйсегментTSS1
Да
4
16-разрядный шлюз вызова
Да
5
16/32-разрядный шлюз задачи
Да
6
16-разрядный шлюз прерывания
Нет
7
16-разрядный шлюз ловушки
Нет
8
Резерв
Нет
9
Доступный32-разрядныйсегментTSS
Да
А
Резерв
Нет
В
Занятый 32-разрядный сегмент TSS ■1
Да
С
32-разрядный шлюз вызова
—продолжение е
Таблица П-9 (продолжение)
Тип
Название
Допустимость
D
Резерв
Нет
Е
32-разрядный шлюз прерывания
Нет
F
32-разрядный шлюзловушки
Нет
Флаги: ZF = г
LDS/LES/LFS/LGS/LSS
LDS приемник, источник
LES приемник, источник
LFSприемник,источник
LGS приемник, источник
LSS приемник, источник
С5 /гLDS Пб,т16:1б
C5/rLDS г32,т16:32
OF В2 /гLSS Г16,т1б:16
OF В2 /гLSS г32(т1б:32
C4/rLES г16,т16:1б.
C4/rLES г32,т!б:32
0FB4 /гLFS Г16,т1б:1б
OF В4 /гLFS г32,т16:32
OF В5 /гLGS г1б,т16:16
OFB5/rLGS г32,п11б:32
Загрузка из памяти полного указателя.
Действие: в зависимости от действующего режима адресации (use!6или use32) загружаются первые два (четыре) байта из ячейки памяти источник в 16(32)-разрядный регистр, указанный операндом приемник. Следующие два байта в источнике должны содержать сегментную составляющую некоторого адреса или селектор — они в зависимости от используемой команды загружаются врегистр DS/ES/FS/GS/SS.
LEA
LEA приемник, источник
8D /гLEA г1б,т
8D /гLEA г32,т
Загрузка эффективного адреса (смещения) операнда источник в приемник.
Действие: как показано в табл. Г1. 10, действие команды зависит от действующего режима адресации и размера операнда (usel6или use32).
Таблица П. 10. Действие команды LEA
Размер операнда
Размер адреса
Действие
16
16
Вычисляется 16-разрядный эффективный адрес источника и сохраняется в 16-разрядном регистреприемника
16
32
Вычисляется32-разрядныйэффективныйадресисточника. Младшие 16 бит этого адреса сохраняются в 16-разрядном регистре приемника
32
16
Вычисляется 16-разрядный эффективный адрес источника. Этот адрес расширяется нулем и сохраняется в 32-разрядном регистре приемника
32
32
Вычисляется 32-разрядный эффективный адрес источника и сохраняется в 32-разрядном регистре приемника
LEAVE
LEAVE
С9
Выход из процедуры высокогоуровня.
Действие: команда копирует регистр ЕВР/ВР в ESP/SP, тем самым восстанавливая в регистре ESP/SP то значение, которое было до вызова процедуры. Далеев регистр ЕВР/ВР значение извлекается из новой вершины стека, тем самым восстанавливается кадр вызывающей программы.
LGDT/LIDT
LGDT/LIDT источник
OF 01 /2LGDT Ш16&32
OF 01 /3LIDTml6&32
Загрузка регистра глобальной таблицы дескрипторов/дескрипторовпрерываний.
Действие: команда LGDT загружает 16 битов размера и 32 бита значения базового адреса начала таблицы GDT в памяти в системный регистр GDTR. Команда LIDT загружает 16 битов размера и 32 бита значения базового адреса начала таблицы IDT в памяти в системный регистр IDTR.
LLDT
LLDT источник
0F 00/2
Загрузка регистра локальной таблицы дескрипторов.
Действие: команда выполняет загрузку 16-разрядного регистра LDTR значением из памяти (размером в слово) или 16-разрядногорегистра.
LMSW
LMSW источник
0F01 /б
Загрузка слова состояния машины (младших 16 бит регистра CRO) значением из слова памяти или 16-разрядлогорегистра общего назначения.
LOCK
LOCK
FO
Префикс выдачи сигнала LOCK.
Действие: команда LOCK формирует префикс, инициирующий выдачу процессором сигнала блокировки системной шины LOCK. Используется в многопроцессорных конфигурациях, чтобы добиться монопольного владения системной шиной. Сигнал LOCK может формироваться лишь с определеннойноменклатурой команд процессора, работающих в цикле чтение—модификация— запись. К их числу относятся: BTS, BTR, BTC, XCHG, ADD, OR, ADC, SBB, AND, SUB, XOR, NOT, NEG, INC, DEC. Команда BTS всегда формируется с префиксом LOCK.
LODS/LODSB/LODSW/LODSD
LODS источник
LODSB/LODSW/LODSD
ACLODS DS:(E)SI.
ADLODS DS:SI
ADLODS DS:ESI
ACLODSB
ADLODSW
ADLODSD
Загрузка строки байтов (слов, двойных слов).
Действие: команда загружает элемент из последовательности (цепочки) в регистр-аккумулятор AL/AX/EAX. Адрес элемента содержится в паре DS:ESI/SI. Кроме операции извлечения элемента команда изменяет значение 51 на величину, равную длине элемента цепочки. Знак этой величины зависит от состояния флага DF: DF= 0 — значение положительное, DF= 1 — значение отрицательное.
LOOP/LOOPcc
LOOP/LOOPcc метка
E2cbLOOP rel8
El cbLOOPE\LOOPZ re 18
EOcbLOOPNE\LOOPNZ re 18
Управление циклом со счетчиком в регистре СХ\ЕСХ.
Действие LOOP: выполнить декремент содержимого регистра СХ\ЕСХ; проанализировать регистр СХ\ЕСХ: если СХ\ЕСХ = 0, передать управление следующей за LOOP команде, если СХ\ЕСХ>0, передать управление команде, метка которой указана в качестве операнда. Смещение метки относительно текущего значения регистра IP\EIP должно быть вдиапазоне -128...+ 127 байт.
Действие LOOPcc: выполнить декремент содержимого регистра СХ\ЕСХ; проанализировать регистр СХ\ЕСХ и флаг ZF. Если СХ\ЕСХ = 0, передать управление следующей за LOOPxx команде, если СХ\ЕСХ>0, передать управление команде, метка которой указана в качестве операнда LOOPxx. Если ZF = 0, то для команд LOOPE/LOOPZ это означает выход из цикла, а для команд LOOPNE/LOOPNZ — переход к началу цикла. Если ZF= 1, то для команд LOOPE/LOOPZ это означает переход к началу цикла, а для команд LOOPNE/LOOPNZ — выход из цикла.
LSL
LSL приемник, источник
OF 03/гLSLг1б,г/т1б
OF 03 /гLSL r32,r/m32
Загрузка размера сегмента.
Действие: извлечь из дескриптора, селектор которого содержит источник, значение размера сегмента и загрузить его в 16/32-разрядный регистр приемник. При этом проверяются условия: селектор не нулевой; селектор видим на текущем уровне привилегий; значение селектора актуально для текущих пределов дескрипторной таблицы GDT или LDT; тип дескриптора допустим в команде LSL (см. табл. П.9). Если эти условия выполняются, то флаг ZF устанавливается в 1 и в приемник загружается значение размера сегмента (в байтах). Если эти условия не выполняются, то флаг ZF устанавливается в 0 и приемник не изменяется.
Флаги: ZF
LTR
LTR источник
OF 00 /3
Загрузка регистра задачи.
Действие: помещение в регистр TR содержимого источника, который представляет собой селектор сегмента TSS. После этого сегмент TSS отмечается занятым, для чего устанавливается бит А в байте AR.
MOV
MOV приемник, источник
88 /гMOV r/m8,「8
89 /гMOV r/ml6/16
89 /гMOV r/m32,r32
/г /г АВ888 8/г /г АВ888 8MOV r8,r/m8
/г /г АВ
888 8
/г /г АВ
888 8
MOV rl6,r/ml6
MOV r32,r/m32
MOV r/ml6,Sreg**
MOV Sreg,r/ml6
АОMOV AL,moffs8*
AlMOV AX,moffsl6*
AlMOV EAX,moffs32*
A2MOV moffs8*,AL
A3MOV moffsl6*,AX
A3MOV moffs32*,EAXBO + rb MOV r8,imm8
B8 + rw MOV rl6,imml6
B8 + rd MOVr32,imm32
C6 /0MOV r/m8,imm8
C7 /0MOV r/ml6Jrniril6
C7/0MOV r/m32,imm32
Копирование содержимогооперанда источник в операнд приемник.
MOV источник
OF 22/г
MOVCRO,r32
OF 22 /r
MOV CR2,r32
OF 22/г
MOV CR3J32
OF 22 /r
MOV CR4,r32
OF 20 /r
MOV r32,CRO
OF 20/г
MOV r32,CR2
OF 20 /г
MOV r32,CR3
OF 20/г
MOV r32,CR4
OF 21/г
MOV r32, DRO-DR7
OF 23/г
MOV DR0-DR7, r32
Копирование операнда источник в системный (отладочный)регистр или из системного (отладочного) регистра.
Флаги: OF = ? SF = ? ZF = ? AF = ? PF = ? CF =?
MOVS/MOVSB/MOVSW/MOVSD
MOVS приемник, источник
MOVSB/MOVSW/MOVSD
А4MOVS ES:(E)DI, DS:(E)SI
А5MOVS ES:DI,DS:SI
A5MOVS ES:EDI,DS:ESI
A4MOVSB
A5MOVSW
A5MOVSD
Пересылка строк байтов (слов, двойных слов).
Действие: команда копирует байт, слово или двойное слово из операнда источник в операнд приемник, при этом адреса элементов предварительно должны быть загружены: адрес источника — впару регистров DS:ESI/SI (DS поумолчанию,допускаетсязаменасегмента); адрес приемника — в пару регистров ES:EDI/DI (замена сегмента не допускается).
В зависимости от состояния флага DF команда изменяет значение регистров ESI/SI и EDI/DI:
если DF = 0, то содержимое этих регистров увеличивается на длину структурного элемента последовательности;
если DF = 1, то содержимое этих регистров уменьшается па длину структурного элементапо- следовательности.
Для пересылки нескольких следующих друг за другом элементов необходимо использовать префикс REP.
MOVSX
MOVSX приемник, источник
OF BE/гMOVSXrl6,r/m8
OF BE /rMOVSX r32,r/m8
OFBF/r MOVSX r32,r/ml6
Пересылка со знаковым расширением.
Действие: команда преобразует операнд со злаком в эквивалентный ему операнд со знаком большей размерности. Для этого содержимое операнда источник начиная с младших разрядов записывается в операнд приемник. Старшие биты операнда приемник заполняются значением знакового разряда операнда источник.
MOVZX
MOVZX приемник, источник
OF Вб /г MOVZX rl6,r/m8
OF B6 /rMOVZX r32,r/m8
OFB7 /г MOVZX r32j/ml6
Пересылка с пулевым расширением.
Действие: команда преобразует операнд без знака в эквивалентный ему операнд без знака большей размерности. Для этого содержимое операнда источник, начиная с его младших разрядов, записывается в операндприемник. Старшие разрядыоперандаприемникзаполняютсядвоич- ным нулем.
MUL
MUL множитель
F6 /4MUL r/m8
F7 /4MUL г/П11б
F7 /4MUL r/m32
Целочисленноеумножение без учета знака.
Действие: команда выполняет умножение без учета знаков. Явно задается один из множителей. Второй множитель задается неявно в регистре AL\AX\EAX (это местоположение фиксировано). Местоположение результата умножения определяется кодом операции и размером множителей (табл. П.11).
Таблица П. 1 "1. Местоположение множителей и результата при выполнении команды MUL
Размер операндов
Первый множитель
Второй множитель
Результат
Байт
AL
r/m8
АХ
Слово
АХ
г/т 16
DX:AX
Двойное слово
ЕАХ
г/т32
EDX:EAX
Флаги: если старшая половина результата нулевая: OF = CF = О SF = ? ZF = ? AF != ? PF ?, если старшая половина результата ненулевая: OF= CF= 1 SF= ? ZF = ? AF ? PF SX ?.
NEG
NEG приемник
F6/3NEG г/т8
F7/3NEG г/т16
F7 /3NEG r/m32
Изменение знака.
Действие: команда вычисляет двоичное дополнение операнда приемник.
Флаги:
选если приемник равен нулю: CF О OF = г SF -г ZF = г AF=r PF =г;
'> если приемник не равен нулю: CF =lOF-rSF==rAF -rPF = r.
NOP
•NOP
90
Нет операции.
Действие: отсутствует. Единственный эффект от использования команды NOP — инкремент регистра EIP.
NOT
NOT приемник
F6/2F7 /2 F7/2F6/2F7 /2 F7/2N0Tr/m8
F6/2
F7 /2 F7/2
F6/2
F7 /2 F7/2
NOT r/ml6
NOT r/m32
Инвертирование всех битов операнда приемник.
OR
OR приемник, маска
ОС ibOR AL,imm8
OD iwOR AX,imml6
OD idOR EAX,imm32
80/libOR r/m8,imm8
81 /1 iwOR r/ml6,imml6
81 /1 idOR r/m32,imm32
83 /1 ibOR r/ml6,imm8
83 /1 ibOR r/m32,imni8
08 /rOR r/m8,r8
09 /r0Rr/ml6,rl6
09 /rOR r/m32,r32
OA /rOR r8,r/m8
OB /rOR rl6j/ml6
OB /rOR r32,r/m32
Логическое включающее ИЛИ.
Действие: команда выполняет операцию логического ИЛИ над соответствующими парами битов операндов приемник и маска, то есть приемник = приемник OR маска.
Флаги: CF = OF = О SF = r ZF = r AF = ? PF=r
OUT
OUT номер_порта, аккумулятор
E6ib0UTimm8, AL
E7 ibOUT imm8, AX(EAX)
ЕЕOUT DX, AL
EFOUT DX,AX(EAX)
Вывод значения в порт ввода-вывода.
Действие: вывод значения из регистра AL/AX/EAX (аккумулятор) в порт ввода-вывода, номер которого определяется операндом номер_порта.
OUTS/OUTSB/OUTSW/OUTSD
OUTS порт, источник
OUTSB/OUTSW/OUTSD
•6ЕOUTS DX, DS:(E)SI
6FOUTS DX, DS:SI(ESI)
6FOUTS DX, DS:
6EOUTSB
6FOUTSW\OUTSO
Вывод строки байтов (слов, двойных слов) в порт.
Действие: команда копирует элементы размером байт (слово, двойное слово) из источника в порт ввода-вывода. Номер порта ввода-вывода загружается в регистр DX. Адресячейки памяти, из которой копируются данные, содержится по адресу DS: ESI/SI (допускается замена сегмента). По результатам команды значение регистров ESI/SI изменяется на длину элемента. Направление изменения зависит от состояния флага DF:
驟 если DF = 0, то содержимое регистров ESI/SI увеличивается на длину элемента последовательности;
緻 если DF = 1, то содержимое регистров ESI/SI уменьшается на длину элемента последовательности.
Для пересылки последовательности элементов необходимо использовать префикс REP.
PAUSE
PAUSE
F3 90
Улучшение выполнения циклов ожидания-занятости.
Действие: улучшить выполнение циклов ожидания-занятости (spin-wait loops). При выполнении подобных циклов процессор Pentium 4 испытывает трудности в завершении цикла, обнаруживая возможное нарушение доступа к памяти. Команда PAUSE подсказывает процессору, что данная кодовая последовательность — цикл ожидания-занятости. Процессор использует эту подсказку, чтобы игнорировать возможную ситуацию нарушения доступа к памяти в большинстве случаев. Это улучшает работу процессора вплоть до того, что значительно уменьшает его энергопотребление. По этой причине рекомендуется включать команду PAUSE во все циклы ожидания-занятости.
POP
POP приемник
■ 8F/0Р0РШ16
8F/0POP m32
58 + rwPOPПб
58 + rdPOP r32
IFPOP DS
07POP ES
17POPSS
OF AlPOPFS
0FA9POP GS
Извлечение значения из стека.
Действие: команда восстанавливает содержимое вершины стека в регистр, ячейку памяти или сегментный регистр, после чего содержимое ESP/SP увеличивается на четыре байта для use32 и на два байта для usel6. Недопустимо восстановление значения в сегментный регистр CS.
POPA/POPAD
POPA/POPAD
61
Восстановление содержимого регистров общего назначения из стека.
Действие: команда POPA/POPAD восстанавливает содержимое всех регистров общего назначения (DI/EDI, SI/ESI, ВР/ЕВР, SP/ESP, BX/EBX, DX/EDX, СХ/ЕСХ, АХ/ЕАХ) из стека, после чего значение указателя стека SP/ESP увеличивается на 16(32). Содержимое DI/EDI восстанавливается первым. Содержимое SP/ESP при этом не восстанавливается. Какие именно регистры — 16- или 32-раз- рядные — извлекаются из стека, зависит от установленного размера операнда. При необходимости изменения размера операнда для инструкции POPA/POPAD можно использовать префикс 66h.
POPF/POPFD
POPF/POPFD
9D
Извлечение регистра флагов из стека.
Действие: команда POPF/POPFD восстанавливает из стека содержимое регистра флагов FLAGS/ EFLAGS, после чего увеличивает значение регистра-указателя стека SP/ESP на 2(4). Размерность выталкиваемого регистра зависит от установленного размера операнда. Действие команды POPF/ POPFD зависит от режима работы процессора.
В защищенном режиме на уровне привилегий 0 (или в реальном режиме) в регистре FLAGS/ EFLAGS могут быть изменены любые незарезервированные флаги (за исключением флагов VIP, VIF иVM). Флаги VIP и VIFочищаются, а флагVM не изменяется.
В защищенном режиме на уровне привилегий, большем чем 0, но меньшем (или равным) значения в поле IOPL, все флаги могут быть изменены за исключением поля IOPL и флагов VIP, VIF и VM. При этом поле IOPL и флаг VM не изменяются, а флаги VIP и VIF очищаются. Флаг IF изменяется, если выполнение производится на уровне, равном или меньшем значению поля IOPL, в противном случае исключения не происходит, но и привилегированные биты не изменяются.
В режиме виртуального процессора 8086 для использования команды POPF/POPFD уровень привилегий I/O (IOPL) должен быть равен 3 (при этом флаги VM, RF, IOPL, VIP и VIF не изменяются). Если уровень привилегий меньше 3, то команда POPF/POPFD вызывает исключение общей защиты (#GP).
Флаги: изменяются все флаги, кроме зарезервированных и флага VM.
PREFETCHTO/PREFETCHT1/PREFETCHT2/PREFETCHNTA
PREFETCHTO источник
PREFETCHT1 источник
PREFETCHT2 источник
PREFETCHNTA источник
OF,18,/1 PREFETCHTO m8
OF,18,/2PREFETCHT1 m8
OF,18,/3PREFETCHT2 m8
OF,18,/0PREFETCHNTA m8
• Предварительная запись в кэш-память данных из оперативной памяти.
Действие: команда проверяет, есть ли строка байтов в кэш-памяти — если есть, то никаких перемещений не производится. Если данных в кэш-памяти нет, то производится перемещение их в кэш-память того уровня, который определяется конкретной командой:
舞 PREFETCHTO — загрузка строки в кэш-память всех уровней;
* PREFETCHT1 — загрузка строки в кэш-память всех уровней, за исключением уровня 0;
篇 PREFETCHT2 — загрузка строки в кэш-память всех уровней, за исключением уровней 0 и 1;
第 PREFETCHNTA — загрузка строки в кэш уровня 1.
Количество записываемых байтов зависит от конкретного процессора, но минимальное количество составляет 32 байта.
PUSH
PUSH источник
FF /бPUSHr/ml6
FF /6PUSHr/m32
50 + rwPUSH rl6
50 + rdPUSH r32
6APUSHimni8
68PUSHimml6/imm32
OEPUSHCS
16PUSH SS
IEPUSH DS
06PUSH ES
OF АОPUSH FS
0FA8PUSH GS
Размещение операнда в стеке.
Действие: команда уменьшает значение регистра-указателя стека SP/ESP на 2(4) и затем записывает значение источника в вершину стека.
PUSHA/PUSHAD
PUSHA/PUSHAD
60
Записьвсех регистров общего назначения в стек.
Действие: команда уменьшает значение указателя стека ESP/SP на 16(32) (в зависимости от значения атрибута размера адреса — usel6 или use32), после чего размещает в стеке регистры общего назначения в следующей последовательности: АХ/ЕАХ, СХ/ЕСХ, DX/EDX, ВХ/ЕВХ, SP/ESP, BP/ EBP, SI/ESI, DI/EDI (содержимое DI/EDI будет на вершине стека). В стек помещается содержимое SP/ESP, которое было до выполнения команды.
PUSHF/PUSHFD
PUSHF/PUSHFD
9С
Размещение регистра флагов встеке.
Действие: команда уменьшает значение указателя стека SP/ESP на 2(4), после чего помещает в вершину стека содержимое регистра FLAGS/EFLAGS. При этом флаги VM и RF (биты 16 и 17) не копируются, вместо этого значения для этих флагов в образе EFLAGS, сохраненном на стеке, устанавливаются равными нулю.
RCL/RCR
RCL/RCRоперанд, количество_сдвигов
DO/2RCLr/m8,l
02/2RCL г/т8Д'
СО /2 ibRCL r/m8,imm8
DI/2RCLr/пИбД
D3 /2RCLr/ml6fCL
Cl /2 ibRCL r/ml6jmm8
DI/2RCL r/m32,l
D3 /2RCL r/m32,CL
Cl /2 ibRCL r/m32,imm8
DO /3RCRr/m8,l
D2 /3RCR r/m8,CL
CO /3 ib RCR r/m8,imm8
D1 /3RCR г/т16Д
D3 /3RCR r/ml6,CL
Cl /3 ibRCR r/miejmmS
DI/3RCR г/т32Д
D3 /3RCR r/m32,CL
Cl /3 ibRCR r/m32jmm8
Циклический сдвиг операнда влево (вправо) через флагпереноса CF.
Действие: каждый раз при циклическом сдвиге разрядов операнда влево (вправо) старший (младший) его бит становится значением флага переноса CF. Старое содержимое CF вдвигается в операнд справа (слева) и становится значением его младшего (старшего) бита.
Флаги: CF= r OF=
Флаг OF представляет интерес, если сдвиг осуществляется па один разряд. При сдвиге на несколько разрядов его значение не определено (поэтому обозначен ?r). По значению флага OF можно судить о факте изменения знакового (старшего) разряда операнда:
緻 OF - 1, если после операции сдвига значения флага CF и старшего бита операнда различны; 堂谖 OF = 0, если после операции сдвига влево значения флага CF и старшего бита операнда совпадают.
RDMSR
RDMSR
OF 32
Чтение из регистра MSR.
Действие: команда проверяет то, что уровень привилегий нулевой и что в регистре ЕСХ находится значение, адресующее один из регистров MSR. Если хотя бы одно из этих условий не выполняется, то выполнение команды заканчивается. Если выполняются оба условия, то значение регистра, адресуемого содержимым регистра ЕСХ, помещается в пару 32-разрядных регистров EDX:EAX.
RDPMC
RDPMC
OF 33
Чтение счетчиков производительности процессора.
Действие: команда проверяет то, что регистр ЕСХ содержит 0 или 1 и бит CR4.PCE工 1 и что бит CR4.PCE = 0 и CPL = 0. Если одно из этих условий выполнено, то содержимое счетчика, идентифицированного содержимым регистра ЕСХ (0 или 1), пересылается в пару регистров EDX:EAX и выполнение команды заканчивается. Если ни одно из этих условий не выполнено, то выполнение команды заканчивается с возбуждением исключения общей защиты.
RDTSC
RDTSC
OF 31
Чтение64-разрядногосчетчикаметок реальноговремени(TimeStampCounter,TSC).
Действие: команда проверяет состояние второго бита регистра CR4.TSD (Time Stamp Disable — отключить счетчик меток реального времени):
經 если CR4.TSD - 0, то выполнение команды RDTSC разрешается на любом уровне привилегий;
緻; если CR4.TSD = 1, то выполнение команды RDTSC разрешается только на нулевом уровне привилегий.
Если выполнение команды разрешено на текущем уровне привилегий, то значение 64-раз- рядного MSR-счетчика TSC сохраняется в паре 32-разрядных регистров EDX:EAX. Если выполнение команды на текущем уровне привилегий запрещено, работа заканчивается.
REP/REPE/REPZ/REPNE/REPNZ
REP/REPE/REPZ/REPNE/REPNZ
REP/REPE/REPZ — f3
REPNE/REPNZ-f2
Повторение цепочечной операции.
Действие: команды REP/REPE/REPZ/REPNE/REPNZ вызывают повторение цепочечной команды столько раз, сколько указано в регистре ЕСХ, или до тех пор, пока не выполнится обозначенное флагом ZF условие. Префикс REP может быть добавлен к командам INS, OUTS, M0VS, LODS и STOS. Префиксы REPE, REPNE, REPZ и REPNZ могут быть добавлены к командам CMPS и SCAS. Префиксы REPZ и REPNZ — синонимы префиксов REPE и REPNE. Поведение префикса REP не определено при его использовании с командами, отличными от цепочечных.
В табл. П. 12 приведены условия завершения повторения цепочечных команд при использовании с ними определенных префиксов.
Таблица П. 12. Условия завершения повторения цепочечных команд
Префикс повторения
Условие завершения 1
Условие завершения 2
REP
ЕСХ = 0
Нет
REPE/REPZ
ЕСХ = 0
ZF = 0
REPNE/REPNZ
ЕСХ-0
ZF = 1
Выяснить, какое именно условие привело к завершению выполнения цепочечной команды, можно, анализируя значение в регистре ЕСХ (командой JECXZ) или проверяя флаг ZF (командой 32, JNZmmJNE).
Флаги: не изменяются (за исключением случая использования префиксов с командами CMPS и SCAS, которые устанавливают флаги в регистре EFLAGS).
RET
•RET
RET число
СЗ (ret — близкий возврат в вызывающую процедуру);
СВ (ret — дальний возврат в вызывающую процедуру);
С2 il6 (ret 116 — близкий возврат с выталкиванием il6 байт из стека);
СА il6 (ret116 — дальний возврат с выталкиванием il6 байт из стека)
Ближний (дальний) возврат из процедуры.
Действие: передача управления по адресу, расположенному на вершине стека. Этот адрес обычно помещается в стек командой CALL, его значение соответствует команде, следующей за командой CALL. Необязательный операнд число определяет количество байтов стека, которые будут вытолкнуты после выталкивания адреса возврата. Команда RET используется для выполнения трех вариантов возврата управления.
魏 Близкий возврат — возврат управления вызывающей процедуре в пределах текущего сегмента кода (внутрисегментный возврат). При этом из вершины стека выталкивается значение в регистр EIP. Регистр CS не изменяется. Процессор продолжает выиолнение кода в том же сегменте, но по другому смещению.
鸵 Дальний возврат — возврат управления вызывающей процедуре, расположенной в отличном от текущего сегменте кода (межсегментный возврат). При этом из вершины стека последовательно выталкиваются значения в регистры EIP и CS. Процессор продолжает выполнение кода в другом сегменте.
Я Дальний возврат между уровнями привилегий — дальний возврат управления коду на уровне привилегий, отличном от текущего. Этот вид возврата может быть выполнен только в за- щитенном режиме. Его механизм подобен механизму дальнего возврата за исключением того,
что процессор следит за уровнями привилегий и правами доступа к сегментам кода и стека, которым возвращается управление для определения возможности подобной передачи. Команда RET очищает регистры DS, ES, FS и GS, если они ссылаются на соответствующие сегменты, недоступные на новом уровне привилегий. Так как при межуровневом возврате управления производится переключение стека, то команда RET также производит загрузку этих регистров из стека. Если в вызываемую процедуру передавались параметры, то при возврате в команде RET необходимо указать параметрчисло для их удаления.
ROL/ROR
ROL/ROR операнд, количество_сдвигов
DO /ОROL г/т8Д
D2 /ОROL r/m8,CL
СО /0 ibR0Lr/m8,imm8■
01/0R0Lr/ml6,l
D3 /0ROL r/ml6,CL
Cl /0 ibROL r/ml6,imm8
DI/0ROL r/m32,l
D3 /0ROL r/m32,CL
Cl /0 ibROL r/m32,imm8
DO /1RORr/m8,l
D2 /1ROR r/m8,CL
CO/lib ROR r/m8,imm8
01 /1ROR r/ml6,l
03/1R0Rr/ml6,CL
Cl/libROR r/ml6Jmm8
Dl /1ROR r/m32fl
D3/1R0Rr/m32,CL
Cl/libROR r/m32,imm8
Циклический сдвиг операнда влево (вправо).
Действие: каждый раз при циклическом сдвиге разрядов операнда влево (вправо) его старший выдвигаемый бит вдвигается в операнд справа (слева) и становится одновременно значением младшего (старшего) бита операнда и флага переноса CF.
Флаги: CF == ?rSF = rZF=rPF= rAF = ?
Флаг OF представляет интерес, если сдвиг осуществляется на один разряд. При сдвиге на несколько разрядов его значение не определено (поэтому обозначен?г)По значению флага OF можно судить о факте изменения знакового (старшего) разряда операнда:
OF = 1, если после операции сдвига значения флага CF и старшего бита операнда различны;
憑 OF = 0, если после операции сдвига значения флага CF и старшего бита операнда совпадают.
RSM
•RSM
OFAA
Возврат процессора из S-режима с восстановлением контекста.
Действие: команда проверяет контекст прерванной программы на предмет выполнения следующих условий:
в регистре CRO недопустимаякомбинация значений битов;
установлен любой из зарезервированных битов в регистре CR4;
начало области памяти, с которой работает процессор в S-режиме, не выровнено на границу, кратную 32 Кбайт.
Если хотя бы одно из условий выполняется, процессор переходит в состояние ожтгдания. Если ни одно из условий не выполняется, происходит возврат из S-режима и восстановление контекста
из определенной области памяти. Данная операция напоминает восстановление контекста при переключении из вложенной задачи.
Флаги: изменяются в соответствии с содержимым регистра EFLAGS восстанавливаемого контекста.
SAHF
SAHF
9Е
Загрузка регистра флагов EFLAGS/FLAGS изрегистра АН.
Действие: команда загружает значение из младшего байта регистра АН в регистр EFLAGS/FLAGS. При этом флаги SF, ZF, AF, PF и CFинициализируются битами 7, 6, 4, 2, и 0 регистраАН. Биты 1, 3 и 5 регистров EFLAGS/FLAGS не изменяются, то есть остаются равными значениям 1, 0 и 0 соответственно.
Флаги: SF rZF= rAF= r PF rCF r
SAL/SAR
• SAL/SAR операнд, количество_сдвигов
DO /4
SAL r/m8,l
D2 /4
SAL r/m8,CL
C0/4ib
SAL r/m8,imm8
DI/4
SAL r/ml6,l
D3/4
SALr/ml6,CL
Cl /4ib
SAL r/ml6,imm8
DI/4
SAL r/m32,l
D3 /4
SAL r/m32,CL
Cl /4 ib
SAL r/m32,imm8
DO /7
SAR r/m8,l
D2/7
SAR r/m8,CL
CO/7ib
SAR r/m8,imm8
Dl/7
SARr/ml6,l
D3 /7
SAR r/ml6,CL
Cl/7ib
SAR r/ml6zinini8
Dl /7
SAR r/m32,l
D3 /7
SAR r/m32,CL
Cl /7 ib
SAR r/m32,imm8
Арифметический сдвиг операнда влево (вправо).
Действие: сдвиг всех битов операнда операнд влево (вправо) на количество разрядов, указанных операндом количество_сдвигов, при этом выдвигаемый слева (справа) бит становится значением флага переноса CF. Одновременно для команды SAL справа в операнд вдвигается нулевой бит. Для команды SAR по мере сдвига вправо освобождающиеся места заполняются значением знакового разряда.
Флаги: CF = r0F = ?rSF= "F = rPF=「AF= ?
Флаг OF представляет интерес только для команды SAL в случае, если сдвиг осуществляется на один разряд. При сдвиге на несколько разрядов его значение не определено (поэтому обозначен ?「)• По значению флага OF можно судить о факте изменения знакового (старшего) разряда операнда:
您 если OF = 1,то текущее значение флага CF и значения выдвигаемого слева бита операнда различны;
気 если OF = 0, то текущее значение флага CF и значения выдвигаемого слева бита операнда совпадают.
В отличие от других команд сдвига, команда SAR всегда сбрасывает в ноль флаг OF в операциях сдвига на один разряд.
SBB
SBB операнд_1, операнд_2
IC ibSBB AL,imm8
lDiwSBBAXzimml6 ■
ID idSBB EAX,imm32
80 /3 ibSBB r/m8,imm8
81/3 iw SBB r/ml6,imml6 81 /3 idSBB r/m32zimm32
83 /3 ibSBB r/ml6,imm8
83/3ibSBB г/е32/ее8
18 /гSBB r/m8,r8
19 /rSBB r/ml6jl6
19 /rSBB r/m32,r32
1A /rSBB r8,r/m8
IB /rSBB rl6j/ml6
IB/rSBB r32zr/m32
Вычитание сзаемом.
Действие: операнд_1 = операнд_1 - (операнд_2 + CF). Состояние флага CF представляет собой заем от предыдущего вычитания. Команда SBB пе различаетзнаковоперандов. Вместо этого процессор устанавливает флаги OF и CF, чтобы идентифицировать фактзаемадлязнакового ибеззна- кового операндов. Флаг SF отражает знак результата (состояние его старшего бита).
Флаги: CF = r OF = r SF = r ZF r PF = r AF r
SCAS/SCASB/SCASW/SCASD
SCAS приемник scasb/scasw/scasd
АЕSCAS ES:(E)DI/SCASB
AFSCAS ES:(E)DI/SCASW/SCASD
Сканирование строки байтов (слов, двойных слов).
Действие: команда сканирования SCAS сравнивает путем вычитания значение в регистре ЕАХ/ AX/AL и значение в ячейке памяти, локализуемой парой регистров ES:EDI/DI. По результату вычитания устанавливаются флаги (в том числе ZF). Размер сравниваемых элементов зависит от применяемой команды. Чтобы эту команду можно было использовать для поиска значения в ПОСЛС' довательности элементов, имеющихразмерность байт, слово или двойное слово, необходимо указать один из префиксов REPE или REPNE. Эти префиксы пе только заставляют циклически выполняться команду поиска пока ЕСХ/СХоО, но и отслеживаютсостояние флага ZF (см. описание команд REP/ REPE/REPNE). Направление просмотра задается флагом DF:
•龙图DF = 0 — просмотр от начала цепочки к ее концу;
趣DF = 1 — просмотр от конца цепочки к ее началу.
Флаги: CF r OF = r SF = r ZF = r PF = r AF = r
SETcc
SETcc операнд
Установка байта по условию.
Действие: команда проверяет условие, заданное модификатором в коде операции сс (фактически, состояние определенных флагов, как показано в табл. П.13), и устанавливает операнд логическим значением Olh или OOh в зависимости от истинности этого условия.
SFENCE
SFENCE
0FAE/7
18 Зак. 256
Гарантированная запись информации из кэш-памяти всех уровней.
Действие: команда сохраняет данные из кэш-памяти по соответствующим адресам оперативной памяти. Выполняется сериализация (сохранение в долговременную намять) всех операций записи в память, инициированных перед выдачей команды SFENCE.
Таблица П.13. Условия установки байта командой SETcc
Машинный код
Мнемокод
Флаги
Условие установки байта
OF 90
SETO r/m8
OF = 1
Переполнение
OF 91
SETNO r/m8
OF = 0
Нет переполнения
OF 92
SETB/SETNAE/SETC r/m8
CF = 1
Ниже (не выше или равно) или перенос
OF 93
SETAE/SETNB/SETNC r/m8
CF = 0
Выше или равно (не ниже) или не перенос
OF 94
SETE/SETZ r/m8
ZF = 1
Равно или нуль
OF 95
SETNE/SETNZ r/m8
ZF = 0
Не равно или не нуль
OF 96
SETNA/SETBE r/m8
CF =1|ZF= 1 i
Не выше (нижеили равно)
OF 97
SETA/SETNBE r/m8
CF = O&ZF 立 0
Выше (не ниже или равно)
OF 98
SETS r/m8
SF=t
Знак
OF 99
SETNS r/m8
SF = 0
Нет знака
OF 9A
SETP/SETPE r/m8
PF = 1
Паритет (четное количество единичных битов)
0F9B
SETNP/SETPO r/m8
PF = 0
Нет паритета (нечетное количество единичных битов)
0F9C
SETL/SETNGE r/m8
SF<>OF
Меньше (не больше или равно)
0F9D
SETGESETNLr/m8
SF OF
Больше или равно (не меньше)
0F9E
SETNG/SETLE r/m8
ZF= 1|SF<>OF
Не больше (меньше или равно)
0F9F
SETG/SETNLE r/m8
ZF = 0|SF=OF
Больше (неменьшеилиравно)
SGDT/SIDT
SGOT/SIDT источник
OF 01/0SGDTm
0F01 /1SIDTm
Сохранение регистра глобальной таблицы дескрипторов (таблицы дескрипторов прерываний).
Действие: команда сохраняет содержимое системного регистра GDTR/IDTR в области памяти размером 48 битов. Структурно эти 48 битов представляют 16 битов размера и 32 бита значения базового адреса начала таблицы GDT/IDT в памяти. Если установлен атрибут размера операнда 32 бита, то 16 битов поля предела помещаются в младшее слово области памяти, а 32 бита базового адреса — в старшее двойное слово 48-разрядной области памяти. Если установлен атрибут размера операнда 16 битов, то 16 битов предела помещаются в младшее слово области, и 24 бита базового адреса — в 3-й, 4-й и 5-й байты (6-й байт равен нулю).
SHL/SHR
SHL/SHR операнд, количество_сдвигов
DO /4SHL r/m8,l
D2/4SHL r/m8,CL
C0/4ibSHL r/m8,imm8
D1 /4
SHL r/ml6」
D3 /4
SHL r/ml6,CL
Cl/4ib
SHLr/ml6,imm8
DI /4
SHL r/m32,l
D3 /4
SHL r/m32,CL
Cl/4ib
SHL r/m32zimm8
DO /5
SHR 17m8,1
D2 /5
SHRr/m8,CL
CO /5 ib
SHR r/m8,imm8
DI/5
SHRr/ml6,l
D3 /5
SHR r/ml6,CL
Cl /5ib
SHR r/ml6,imm8
Dl /5
SHR r/m32,l
D3 /5
SHR r/m32,CL
Cl/5ib
SHR r/m32jmm8
Логический сдвиг операнда влево (вправо).
Действие: сдвиг всех битов операнда операнд влево (вправо) на количество разрядов, указанное операндом количество_сдвигов, при этом выдвигаемый слева (справа) бит становится значением флага переноса CF. Одновременно слева (справа) в операнд вдвигается нулевой бит.
Флага: CF = г OF = ?r SF = г ZF = г PF - г AF = ?
Для команды SHL флаг OF представляет интерес, если сдвиг осуществляется на один разряд. При сдвиге на несколько разрядов его значение не определено (поэтому обозначен ?г). По его значению можно судить об изменении знакового разряда операнда:
綴если OF - 1, то текущее значение флага CF и значение выдвигаемого слева бита операнда различны;
Я если OF = 0, то текущее значение флага CF и значение выдвигаемого слева бита операнда совпадают.
Для команды SHR флаг CF содержит значение последнего вдвинутого в операнд бита. Флаг OF устанавливается равным старшему значащему биту исходного операнда.
SHLD
• SHLD приемник, источник, количество_сдвигов
А4
SHLD r/ml6,rl6,imm8
OF A5
SHLD r/ml6,rl6,CL
OF A4
SHLD r/m32,r32jmm8
OF A5
SHLD r/m32,r32,CL
• Сдвиг двойного слова влево.
Действие: команда сдвигает операнд приемник влево на число битов, указанных операндом количество_сдвигов. Операнд источник обеспечивает биты, которые вдвигаются в приемник справа (начиная с бита 0 приемника). Операнд количество_ сдвигов — целое число без знака, которое может быть непосредственным 8-разрядным значением или содержимым регистра CL.
Флаги: CF = rOF =?r SF = = rPF=rAF =?
Флаг CF заполняется последним битом, сдвинутым из операнда приемник. Флаг OF устанавливается в 1 для одноразрядного сдвига, если изменился знаковый разряд приемника, иначе он ра- веннулю. Еслиоперандколичество_сдвиговравеннулю,тофлагинеизменяются.
SHRD
• SHRD приемник, источник, количество_сдвигов
•OF ACSHRD r/ml6,rl6,imm8
OF ADSHRDr/ml6frl6,CL
OF ACSHRD r/m32,r32,imm8
OFADSHRD r/m32,r32,CL
Сдвиг двойного слова вправо.
Действие: сдвиг операнда приемник вправо на число битов, указанное операндом количество^ сдвигов. Операнд источник обеспечивает биты, которые вдвигаются в приемник слева (начиная со старшего бита приемника). Операндколичество_сдвигов — целое число без знака, которое может быть непосредственным 8-разрядным значением или содержимым регистра CL.
Флаги: CF «г OF « ?г SF = г ZF = г PF « г AF « ?
Флаг CF заполняется последним битом, сдвинутым из операнда приемник. Флаг OF устанавливается в 1 для одноразрядного сдвига, если изменился знаковый разряд приемника, иначе он равен нулю.
SLDT
SLDT приемник
OF)0/0
Сохранение регистра LOTR локальной таблицы дескрипторов в ячейке памяти или регистре.
SMSW
SMSW приемник
OF01/4 SMSWПб(32)/т16
Сохранение слова состояния машины.
Действие: команда сохраняет значение младших 16 битов регистра CRO в слове памяти или в 16(32)-разрядном регистре общего назначения.
STC
•STC
•F9
Установка флага переноса CF в единицу.
Флаги: CF = 1
STD
•STD
•FD
Установка флага направления DF в единицу.
Флаги: DF = 1
STI
•STI
•FB
Установка флага прерывания IF вединицу.
Флаги: IF = 1
STOS/STOSB/STOSW/STOSD
STOS приемник
STOSB/STOSW/STOSD
АА
STOS ES:(E)DI
АА
STOSB
АВ
STOS ES:(E)DI
АВ
STOSW/STOSD
Сохранение строки байтов (слов, двойных слов).
Действие: команда записывает элемент из регистра AL/AX/EAX в ячейку памяти, адресуемую нарой ES:DI/EDI. После этого значение регистра DI/EDI изменяется на величину, равную длине элемента цепочки. Знак этого изменения зависит от состояния флага DF:
Л если DF = 0, то DI/EDI увеличивается;.
戀если DF = 1, то DI/EDIуменьшается.
STR
STR приемник
0F00/lSTRr/ml6
Сохранение регистра задачи TR.
Действие: сохранение регистра задачи TR в слове памяти или 16-разрядном приемнике (регистре или ячейке памяти).
SUB
SUB операнд_1, операнд_2
• 2CibSUB AL,imm8
2D iwSUB AX,imml6
2D idSUB EAX,imm32
80 /5ibSUB r/m8,imm8
81/5iwSUB r/ml6,imml6
81 /5 idSUB r/m32,imm32
83 /5 ibSUB r/miejmmS
83/5ibSUB r/m32,imm8
28 /rSUB r/m8,r8
29 /rSUB r/ml6,rl6
29 /rSUB r/m32,r32
2A/rSUB r8,r/m8
2B/rSUB rl6,r/ml6
2B/rSUBr32,r/m32
Вычитание.
Действие: команда выполняет целочисленное вычитание: операнд_1 =- операнд_1.
Команда SUB не различает знаков операндов. Вместо этого она соответствующим образом устанавливает флаги.
Флаги: OF=rSF=rZF=rAF=rPF=rCF=г
SYSENTER
SYSENTER
OF 34
Быстрый переход к точке входа в коде па уровне 0.
Действие: команда SYSENTER предназначена для максимально эффективного перехода к коду нанулевомкольцезащиты(CPL=0). КомандаустанавливастрсгистрыCS, EIP, SS,ESPзначениями, указанными операционной системой в определенных моделыю-зависимых регистрах:
殺 CS - SYSENTER_CS_MSR;
搜 EIP - SYSENTER_EIP_M5R;
總 SS - 8 + SYSENTER_CS_MSR);
験 ESP - SYSENTER_ESP_MSR.
Команда SYSENTER всегда передает управление коду защищенного режима на CPL = 0. Команда может быть вызвана из всех режимов за исключением реального. Команда требует выполнения следующих условий со стороны операционной системы:
целевой селектор CS должен соответствовать 32-разрядному сегменту кода на нулевом кольце, отображенному на плоское пространство адресовГбайт с доступом по чтению и вы
полнению;
№ целевой селектор SS должен соответствовать 32-разрядному сегменту стека кольца0, отображенному на плоское адресное пространствоГбайт с доступом по чтению, записи, причем
величина этого селектора (селектора SS кольца 0) должна бытьсумме (CS) + 8.
Операционная система посредством указанных ранее модельно-зависимых регистров внутри процессора должна обеспечить необходимые значения для регистров CS,SS и ESP с тем,
чтобы корректно передать управление точке входа кода в кольце защиты 0. Эти регистры доступны по чтению и записи посредством командиАдреса
регистровв табл.
ТаблицаАдреса модельно-зависимых регистров
Имя
Описание
Адрес
SYSENTER CS MSR
Целевой селектор CS на кольце 0
174h
SYSENTER ESP MSR
Целевой регистр ESP на кольце 0
175h
SYSENTER EIP MSR
Целевой регистр EIP точки входа на кольце 0
176h
SYSEXIT
SYSEXIT приемник, источник
35
Быстрый возврат из кода на уровне 0.
Действие: команда SYSEXIT предназначена для максимально эффективного перехода к коду на кольце защиты 3 (CPL = 3) из кода на кольце защиты О (CPL = 0). Команда SYSEXIT устанавливает регистры CS, EIP, SS, ESP значениями, указанными операционной системой в определенных мо- делыю-зависимых регистрах или регистрах общего назначения:
CS - 16 +
EIP — значение из регистра EDX;
SS - 24 +
ESP — значение из регистра ЕСХ.
Таблица15. Описание модельно-зависимых регистров
Имя
Описание
СелекторCS
Селектор целевого сегмента кода на кольце 3. Его значение равно сумме 16 +
Селектор SS
Селектор целевого сегмента стека SS на кольце 3. Его значение равно 24 +
Регистр EIP
Адрес возврата в коде на кольце 3. Это целевая точка входа, и она значением, содержащимся в регистре EDX
Регистр ESP
ESP возврата на кольце защиты 3. Назначается значением, содержащимся в регистре ЕСХ
Команда SYSEXIT всегда передает управление коду на кольце 3 (CPL = 3) защищенного режима в рамках плоской модели памяти. Команда может быть вызвана только в защищенном режиме при CPL = 0. Команда требует выполнения следующих условий со стороны операционной системы:
целевой селектор CS должен соответствовать 32-разрядному неподчиненному сегменту кода на кольце защиты 3, отображенному на адресное пространство Гбайт, с атрибутами доступа по выполнению и по чтению;
齡 целевой селектор SS должен соответствовать 32-разрядному сегменту стека на кольце 3, отображенном на адресное пространство 0...4 Гбайт с атрибутами расширяемости вверх и доступа по чтению и по записи.
Операционная система посредством указанных модельно-зависимых регистров внутри процессора должна обеспечить необходимые значения для регистров CS, EIP, SS и ESP с тем, чтобы корректно передать управление точке входа кода в кольце защиты 3. Эти регистры доступны по чтению и записи посредством команд RDMSR и WRMSR (табл. П.15).
TEST
TEST приемник, источник
A8ibTEST AL,imm8
A9 iwTEST AX,imml6
A9 idTEST EAX,imm32
F6 /0 ibTEST r/m8,imm8
F7/Oiw TEST "ml6jmml6
F7 /Oid TEST r/m32,imm32
84 /r TEST r/m8,r8
85 /rTEST r/ml6,rl6
85 /rTEST r/m32,r32
Логическое сравнение.
Действие: команда выполняет операцию логического умножения над операндами. Результат операции формируется во временной памяти, сами операнды не изменяются: бит результата равен 1, если соответствующие биты операндов равны 1, в остальных случаях бит результата равен 0. Для анализа результата используется флаг ZF, который равен 1, если результат логического умножения равен нулю.
Флаги: OF = О CF = ОSF=rZF= r PF=?
UD2
•UD2
■ OF OB
Генерация исключения недействительного кода операции.
VERR/VERW
VERR/VERW селектор
OF 00 /4VERRr/ml6
Определение возможности чтения (записи) из сегмента или всегмент, заданный операндом селектор.
Действие: команда проверяет выполнение следующих условий:
曬 определен ли селектор в таблицах GDT или LDT;
翔 указывает ли дескриптор, адресуемый селектором, на сегмент кода или данных (и ни на какой другой);
懲 является ли сегмент считываемым (для VERW — записываемым).
Затем проверяется уровень привилегий:
落 если сегмент кода является подчиненным, то поле DPL его дескриптора может иметь любое значение;
盡 если сегмент кода не является подчиненным, поле DPL дескриптора должно быть больше (или равно) полей CPL и RPL селектора.
Если проверка по приведенным выше условиям положительна, то флаг ZF устанавливается в 1, иначе一b 0.
Флаги: ZF = r
WAIT
WAIT
9В
Приостановка работы процессора до поступления сигнала от сопроцессора обокончании обработки последней команды.
Машинный код: 9b
Флаги: СО = С1 = С2 = СЗ = ?
WBINVD
WBINVD
•OF09
Очистка кэш-памяти.
Действие: команда очищает кэш-память первого уровня, записывает содержимое кэш-памя- ти второго уровня в основную память и очищает содержимое кэш-памяти второго уровня.
WRMSR
WRMSR
OF30
Запись в MSR-регистр.
Действие: команда проверяет то, чтоуровень привилегий нулевой и что в регистре ЕСХ содержится значение, идентифицирующее один из MSR-регистров. Если хотя бы одно из этих условий не выполняется, выполнение команды завершается. Если выполняются оба условия, то значение пары 32-разрядных регистров EDX:EAX пересылается в 64-разрядный MSR-регистр, номер которого задан в регистре ЕСХ.
XADD
XADD приемник, источник
OF СО/гXADD 17 m 8 j8
OF Cl/rXADD r/ml6,rl6
OF Cl/rXADD r/m32,r32
Обмен и сложение операндов.
Действие: команда XADD обменивает содержимое операнда приемник с операндом источник, затем формирует сумму их значений в операнде приемник. Эта команда может использоваться с префиксом LOCK.
Флаги: CF - г PF r AF ■ r SF »r ZF = r OF « r
XCHG
XCHG операнд_1, операнд_2
90 + rw
XCHG АХД6
90 + rw
XCHG rl6,AX
90 + rd
XCHG EAX,r32
90 + rd
XCHG r32,EAX
86 /r
XCHG r/m8,r8
86 /r
XCHG r8,r/m8
87/r
XCHG r/ml6zrl6
87 /r
XCHG 「16”ml6
87/r
XCHG r/m32,r32
87 /r
XCHG r32,r/m32
XLAT/XLATB
XLAT адрес_таблицы_байтов
XLATB
•D7
Преобразование байта.
Действие: команда вычисляет адрес в памяти: DS:BX + (AL). Далее команда извлекает байт по этому адресу и помещает его в регистр AL. Несмотря на наличие операнда адрес_табл и цы_ба йтов в команде XLAT, адрес последовательности байтов, из которой будет осуществляться выборка байта для подмены в регистре AL, должен быть предварительно загружен в пару DS:BX(EBX). Команда XLAT допускает замену сегмента.
XOR
XOR приемник, источник
• 34 ibXOR AL,imm8
35 iwXOR AX,imml6
35 idXOR EAXjmm32
80 /6 ibXOR r/m8,imm8
81 /6 iwXOR r/ml6,imml6
81 /6 idXOR r/m32,imm32
83 /6 ibXOR r/ml6jmm8
83 /6 ibXOR r/m32,imm8
30 /rXOR r/m8,r8
31 /rXOR r/ml6,rl6
31 /rXOR r/m32,r32
32 /rXOR r8,r/m8
33 /rXOR rl6,r/m!6L
33 /rXOR r32,r/m32
Логическое исключающее ИЛИ.
Действие: выполнение операции логического исключающего ИЛИ над парами битов двух операндов: бит результата равен 1, если значения соответствующих битов операндов различны, в остальных случаях бит результата равен 0. Результат помещается в операнд приемник.
Флаги: OF = r CF = r SF二r ZF = r PF = r AF = ?
Команды сопроцессора
F2XM1
F2XM1
D9 FO
Вычисление2* - 1.
Действие: исходный операнд (значение степени х в диапазоне -1 < х < 1)хранится в регистре STO. Команда вычисляет значение 2* - 1, а результат помещает в вершину стека сопроцессора.
Флаги (SWR): C1 устанавливается в 1 при переполнении стека или в 0 при антипереполнении (установлены биты SWR.IE и SWR.SF). При возникновении исключения сопроцессора #P (неточный результат) состояние флага показывает направление выравнивания (1 — в большую сторону); СО, С2, СЗ не определены.
FABS
FABS
D9E1
Получение абсолютного значения (модуля) числа в STO.
Действие: знаковый бит sro устанавливается в 0.
Флаги (SWR): C1 устанавливается в 1 при переполнении стека или в 0 при антипереполнении (установлены биты SWR.IE и SWR.SF); CO, C2, СЗ не определены.
FADDP/FADD/FIADD
FAD DP
FADD/FIADD слагаемое,.!
FADD/FADDP слагаемое„1слагаемое_2
D8 /ОFADD m32real
DC /0FADD m64real
D8 СО + i FADD ST⑼,ST⑴
DC CO + i FADD ST(i)5T(0)
DE C0 + i FADDP引⑴, ST(0)
DE ClFADDP
DA /0FIADD m32int
DE/0FIADD ml6int
Сложение двух значений.
Действие: команды имеют несколько вариантов расположения операндов/
犠 Для команды без операндов первое слагаемое располагается в регистре ST(0), второе — в регистре ST⑴. Команда выполняет сложение: ST⑴ =+ q(l). Последнее действие — вытал
кивание значения из регистра ST(O). Результат сложения — в ST(O).
趣 Для команды с одним операндом первое слагаемое (операнд слагаемое_1) располагается в ячейке памяти m32real/m64real или ml6(32)int, второе слагаемое — в регистре ST(O). Команда выполняет сложение: ST(O) = ST(0) + слагаемое_1.
幾; Для команды с двумя операндами первое слагаемое (операнд слагаемое_1) сохраняется в регистре ST(O)/ST(i), второе слагаемое (операнд слагаемое_2) — в регистре ST(i)/ST(O). Результат сложения помещается в регистр ST(O) для команды FADD 5T(0)5T(i) или регистр ST① для команды FADD sr ⑴ 51(0). Последнее действие команды FADDP — выталкивание значения из регистра ST(O), после чего результат сложения оказывается в регистре ST(i - 1).
Флаги (SWR): см. описание команды F2XM1.
FBLD
FBLD источник
DF /4 FBLD m80dec
Загрузка десятичного числа в стек сопроцессора.
Действие: команда преобразует операнд источник, который содержится в памяти в формате двоично-десятичного числа, в расширенный формат и помещает его регистр ST(0).
Флаги (SWR): см. описание команды FABS.
FBSTP
FBSTP приемник
DF/6FBSTP m80bcd
Сохранение десятичного значения в памяти с выталкиванием,
Действие: исходный операнд приемник находится в регистре ST(0). Команда преобразует исходный операнд в двоично-десятичный формат и записывает его в область памяти приемника размером 10 байт (18 двоично-десятичных цифр). Последнее действие команды — выталкивание исходного операнда из вершины стека.
Замечание: Если в стеке было не целое число, то оно округляется в соответствии со значением в поле CWR.RC.
Флаги (SWR): см. описание команды F2XM1.
FCHS
FCHS
D9 ЕО
Инвертирование знака значения вST(O).
FCLEX/FNCLEX
FCLEX/FNCLEX
9BDBE2FCLEX
DB E2FNCLEX
Сброс флагов исключений в регистре SWR.
Действие: команда сбрасывает флаги в регистреSWR: все флаги исключений — РЕ, UE, OE, ZE, DE, IE (биты 0...5); флаг суммарной ошибки работы сопроцессора ES (бит 6); флаг ошибки работы стека сопроцессора SF (бит 7); флаг занятости В (бит 15).
Перед обнулением флагов команда FCLEX выполняет проверку условий ошибки с плавающей точкой. Команда FNCLEX перед обнулением флагов не выполняет проверки условий ошибки с плавающей точкой.
Флаги (SWR): Cl, CO, C2, СЗ не определены.
FCMOVcc
FCMOVcc приемник, источник
Условная пересылка данных между регистрами сопроцессора.
Действие: команда проверяет состояние флагов в регистре EFLAGS (табл. II. 16). Если состояние флагов соответствует условиям выполнения команды, то пересылает значение из источника (ST(i)) в приемник (ST(O)). В противном случае выполнение команды заканчивается без пересылки.
Таблица п. 16. Состояние флагов для пересылки значение из источника в приемник
Машинный код
Мнемокод
Состояние флагов
DA CO + i
FCMOVB ST(0), ST(i)
Cl 1
DA C8 + i
FCMOVE ST(0), ST(i)
ZF-1
DA DO + i
FCMOVBE ST(0), ST(i)
CF - 1 или ZF - 1
DA D8 + i
FCMOVU ST(0), ST(i)
PF = 1
DB CO + i
FCMOVNB ST(0), ST(i)
CF = 0
DBC8+ i
FCMOVNE ST(0), ST(i)
ZF = 0
DB DO + i
FCMOVNBE ST(0), ST(i)
CF = 0 и ZF = 0
DB D8 + i
FCMOVNU ST(0), ST(i)
PF = 0
Флаги (SWR): см. описание команды FABS.
FCOM/FCOMP/FCOMPP
FCOM/FCOMP/FCOMPP
FCOM/FCOMP операнд
■ D8 /2FCOM m32real
DC /2FCOM m64real
D8 DO + iFCOM ST(i)
D8 DIFCOM
D8 /3FCOMP m32real
DC /3FCOMP m64real
D8D8+FCOMP ST(i)
D8 D9FCOMP
DED9FCOMPP
Сравнение вещественных чисел.
Действие: сравнение значения в регистре ST(O) и значения операнда, указанного в команде (mem32/64/ST(i))или принимаемого но умолчанию (регистр ST(1)). Результатсравнения определяется состоянием битов СЗ, С2 и СО регистра SWR (табл. П.17). Последняя олерациядля FCOMP — выталкивание значения из ST(O). Последняя операция FCOMPP — выталкивание значений из灯(0) и ST(1).
Таблица п. 17. Результат сравнения операндов по состоянию битов СЗ, С2 и СО регистра SWR
Результат сравнения
Биты СЗ, С2 и СО регистра SWR
Операнды несравнимо1
СЗ = 1,С2= 1,С0 = 1
Второй операнд больше первого
СЗ 0, С2 = 0, СО = 1
Второй операнд меньше первого
СЗ 0, С2 = 0, СО = 0
Второй операнд равен первому
СЗ=1,С2 = 0,С0= 0
Флаги (SWR): Бит С1 устанавливается в 1 при пере1юлнснии стека или в 0 при антиперспол- нении (установлены биты SWR.IE и SWR.SF); бит С2 устанавливается в 1, если операнды несравнимы; СО, СЗ — результат сравнения.
FCOMI/FCOMIP/FUCOMI/FUCOMIP
FCOMI/FCOMIP/FUCOMI/FUCOMIP операнд_1л операнда
DB F0 + i FCOMIST, ST(i)
DF FO + iFCOMIP ST, ST⑴
DB E8 + iFUCOMI ST, ST(i)
DF E8 + i FUCOMIP ST, ST(i)
Сравнение вещественных значений иустановка EFLAGS.
Таблица П. 18. Установка флагов ZE PF, CF в регистре EFLAGS по результатам сравнения
Результат сравнения
ZF
PF
CF
ST(0)>ST(i)
0
0
0
ST(0) < ST(i)
0
0
1
ST(0) ST(i)
1
0
0
He поддерживаемые форматы
1
1
1
Действие:
Команда проверяет, поддерживаются ли форматы чисел в операндах операнд_1 и операнд_2: □ для команд FCOMI/FCOMIP если один или оба операнда — псчисла (NaN) или числа в не поддерживаемом сопроцессором формате, то возбуждается исключение недействитель- пой операции сопроцессора, в результате чего управление передается соответствующему обработчику. Если бит CWR.IM = 1, то устанавливаются флаги ZF = PF = CF = 1. Если оба операнда — корректные вещественные числа, выполнение команды продолжается;
П для команд FUCOMI/FUCOMIP если один или оба операнда — нечисла (Q№N, но не SNaN) пли числа 8 не поддерживаемом сопроцессором формате, то устанавливаются флаги ZF = PF = CF = 1. Иначе (один или оба операнда — печисла SNaN или числа в не поддерживаемом формате) возбуждается исключение недействительной операции и, если CWR.IM - 1, устанавливаются флагиZF=PF“F = 1. Если оба операнда — числа, выполнение команды продолжается.
Выполнение вычитания-
По результатам вычитания установка флагов ZF, PF, CF в регистре EFLAGS (табл.После
дняя операция для FCOMIP/FUCOMIP — выталкивание значения из ЩО).
Флаги (EFLAGS):-= r
Флаги (SWR): см. описание команды FABS.
FCOS
FCOS
•
Вычисление косинуса.
Действия: значение угла в радианах х (-263 < х М +263) хранится в регистре ST(0). Команда преобразует число из ST⑼ в значение косинуса и записывает его обратно в регистр ST(0).
Флаги (SWR):С1 устанавливается в 1 при переполнении стека или в 0 при
(биты SWR.IE и SWR.SF в регистре установлены). При возникновении исключения
состояние флага С1 показывает направление выравнивания (1 — в большую сторону). Если С2 =
тоС1 не определен. Флаг С2 устанавливается в если значение исходного операнда
выходит за границу \х\ W2&', иначе — С2 = 0. Флаги СО, СЗ не определены.
FDECSTP
•FDECSTP
•
Уменьшение указателя вершины стека на единицу.
Действия: если поле- 0, то выполняется присваивание = 7, вслучае вы
полняется декремент ST = ST - 1.
Флаги (SWR): С1 устанавливается в 0; СО, С2, СЗ не определены.
FDIV/FDIVP/FIDIV
FDIVP
делитель
FDIV/FDIVP делимое, делитель
•
DC /б
D8 FO + iFDIVST(i)
DC F8 + iFDIV
DEiFDIVP
DE F9FDIVP
DA /6FIDIV m32int
DE/бFIDIV ml6int
Деление двух чисел.
Действия: команды FDIV/FDIVP/FIDIV имеют несколько вариантов расположения операндов.
ii Для команды без операндов делимое находится в регистре ST⑴, делитель — в регистре ST(O). Команда выполняет деление: ST(1) = ST(1)/SI(O). Последнее действие — выталкивание значения из ST(0). Окончательный результат — в регистре ST(O).
в Для команды с одним операндом (делитель) делимое находится в регистределитель —
в ячейке памятиили ml6(32)int. Команда выполняет деление=
тель.
ii Для команды с двумя операндами делимое и делитель хранятся в двух регистраходин
из которых — ST(0). Выполняется деление делимое - (делимое/делитель).
Флаги (SWR): см. описание команды
FDIVR/FDIVRP/FIDIVR
FDIVRP
FDIVR/FIDIVR делимое
FDIVR/FDIVRP делитель, делимое
D8 /7FDIVR m32real
DC /7FDIVR m64real
D8 F8 + iFDIVR 51(0),51(1)
DC FO + iFDIVRST(i),ST ⑼
DE FO+iFDIVRPST(asr(o)
DE FlFDIVRP
DA /7FIDIVR E32int
DE/7FIDIVRml6int
Деление двух чисел в обратном порядке.
Действия: команды имеют три варианта расположения операндов.
齡; Для команды без операндов делимое находится в регистреT(0), делитель — в регистреT⑴.
Команда выполняет деление: ST⑴ - ST(O)/ST(1). Последнее действие - выталкивание значения из регистра ST(O). Результат операции — в регистре ST(0).
滋: Для команды с одним операндом делимое находится в ячейке памяти m32(64)realили ml6(32)int, делитель — в регистре ST(O). Команда выполняет деление: ST(O) =
齡 Для команды с двумя операндами делимое и делитель хранятся в двух регистрах стека, один из которых — ST(O). Команда выполняет деление делитель = (делимое/делитель).
Флаги (SWR): см. описание команды FDIV.
FFREE
FFREE регистр_сопроцессора
DD CO +FFREE ST(i)
Освобождение регистра стека сопроцессора.
Действия: команда выбирает в регистре тегов TWR двухразрядное поле tt, соответствующее регистру SRi), и устанавливает в нем значение lib.
Флаги (SWR): Cl, CO, C2, СЗ не определены.
FICOM/FICOMP
FICOM/FICOMP операнд
DE /2FICOM ml6int
DA /2FICOM m32int
DE/3FICOMP ml6int■
DA /3FKOMP m32int
Сравнение целого и вещественного значений.
Действия: сравнение значения в регистре ST(O) и целочисленного операнда в ячейке памяти ml6/32int. Результат сравнения определяется состоянием битов СЗ, С2 и СО регистра SWR сопроцессора (табл. П. 19). Последнее действие FICOMP — выталкивание значения из вершины стека.
Таблица П. 19.Результат сравнения операндов по состоянию битов СЗ, С2 и СО регистра SWR
Результат сравнения
Биты СЗ, С2 и СО регистра SWR
Операнды несравнимы
C3=1,C2 = 1, CO = 1
Второй операнд больше первого
СЗ - 0, С2 -0, СО- 1
Второй операнд меньше первого
СЗ - 0, С2 - 0, СО = 0
Второй операндравен первому
C3h1,C2 = 0, C0 = 0
Флаги (SWR): см. описание команды FCOM.
FILD
FILD источник
DF/0FILD ml6int
DB /ОFILD m32int
DF /5FILD m64int
Целочисленная загрузка.
Действия: преобразование целого значения из операнда источник в вещественное расширенное представление, после чего уменьшение на 1 указателя вершины стека сопроцессора (поле SWR.TOP). Результат преобразования помещается в регистр ST(O).
Флаги (SWR): С1 устанавливается в 1 при переполнении стека; СО, С2, СЗ не определены.
FINCSTP
FINCSTP
D9 F7
Увеличение величины указателя вершины стека на единицу.
Действия: если поле SWR.TOP 7, выполняется присваивание SWR.TOP 0, в противном случае выполняется инкремент SWR.TOP = SWR.TOP + 1.
Флаги (SWR): С1 устанавливается в 0; СО, С2, СЗ не определены.
FINIT/FNINIT
FINIT/FNINIT
9BDBE3FINIT
DB ЕЗFNINIT
Инициализация сопроцессора.
Действия: команда проверяет наличие установленных битов исключений в регистре SWR. Если какие-то из них установлены, то инициирует вызов процессором обработчиков соответствующих исключений (только FINIT); устанавливает регистры сопроцессора: CWR = 03 7f; SWR = 00 00; TWR - ffff;устанавливает в 0 регистры указателей данных DPR и команд IPR.
Флаги (SWR): С1 = CO = С2 СЗ О
FIST/FISTP
FIST/FISTP приемник
DF /2FIST ml6int
DB/2FIST m32int
DF/3FISTPml6int
DB/3FISTP m32int
DF /7FISTP m64int
И Целочисленное сохранение.
Действия: исходя из значения в поле RC (управление округлением) регистра CWR, команда округляет число в вершине стека до соответствующего целого значения:
^ если RC= ООЬ, то округление доближайшего целого;
если RC = Olb, то округление до ближайшего меньшего целого;
求 если RC = 10Ь, то округление до ближайшего большего целого;
犷* если RC = lib, то дробная часть числа отбрасывается.
Результат преобразования помещается в ячейку памяти, адрес которой указан операндом приемник. Если величина преобразованного значения превышает по модулю максимально представимое число в операнде приемник, то в нем формируется наибольшее отрицательное число — 80 00 или 80 00 00 00. Дополнительное действие для FISTP — выталкивание значения из вершины стека.
Флаги (SWR): см. описание команды F2XML
FLD1/FLDL2T/FLDL2E/FLDLG2/FLDLN2/FLDPI/FLDZ
FLD1/FLDL2T/FLDL2E/FLDLG2/ FLDLN2/FLDPI/ FLDZ
D9E8FLD1
D9 Е9FLDL2T
D9 ЕАFLDL2E
D9 ЕВFLDPI
D9 ЕСFLDLG2
D9 EDFLDLN2
D9 ЕЕFLDZ
Загрузка константы.
Действия:
FLD1 - загрузка вещественной единицы: SWR.TOP = SWR.TOP - 1; ST(0) - +1.0.
FLDL2T - загрузка двоичного логарифма десяти: SWR.TOP - SWR.TOP - 1; ST⑼ =0.
FLDL2E - загрузка двоичного логарифма числа с: SWR.TOP = SWR.TOP - 1; *⑼=1咤2ё
FLDLG2 (LoaDing) - загрузка десятичного логарифма числа 2: SWR.TOP = SWR.TOP - 1;=
=1倜2
ш FLDLN2 (LoaDing) — загрузка натурального логарифма числа 2: SWR.TOP = SWR.TOP - 1;
ST(0) =ln(2)..
a FLDPI - загрузка числа л: SWR.TOP SWR.TOP - 1; ST(O)-兀
国進 FLDZ (LoaDing 0) - загрузка нуля: SWR.TOP = SWR.TOP - 1; "(0) = +0,0.
Флаги (SWR): Cl устанавливается в 1 припсреполнсниистека; СО, С2, СЗ не определены.
FLD
FLD источник
D9 /0FLDm32real
DD /0FLDm64real
DB /5FLDm80real
09 CO +FLD ST(i)
И Загрузка в стек вещественного значения.
Действия: операнд источник — значение в вещественном формате в памяти m32/m64/m80 или в регистре ST(i). Команда уменьшает значение поля SWR.TOP наединицу; проверяет тегдля регистра стека, физический номер которого находится в поле SWR.TOP. Если содержимое тега не равно 11b (регистр ле пустой), то устанавливаются биты SWR.SF и SWR.IE и выполнение команды заканчивается. Если содержимое тега равно 11b, то анализируется тип операнда и выполняются следующие действия:
:變: если операнд источник является вещественным значением в памяти размером 32 или 64 бита, то опо преобразуется в BeniecTBCHHoe число в расширенном (формате, после чего записывается в вершину стека;
統 если операнд источник является вещественным значением в памяти размером 80 бит или регистром стека, то он копируется в вершину стека.
Флаги (SWR): C1 устанавливается в 1 при переполнении стека или в 0 в противном случае; СО, С2, СЗ не определены.
FLDCW
FLDCW источник
D9 /5FLDCW m2byte
Загрузкарегистра управления CWR содержимым ячейки памяти источник.
Флаги (SWR): C1 устанавливается в 1 при переполнении стека или в 0 в противном случае; СО, С2, СЗ не определены.
Команды сопроцессора561
FLDENV
FLDENV источник
D9/4FLDENV mi4/28byte
Загрузка рабочей среды сопроцессора.
Действия: команда определяет режим работы процессора (R, V или Р) и размер операнда для текущего сегмента кода, который задается значением атрибута use (use!6 или use32):
齡 если usel6, то размер буфера, из которого будет производиться восстановление среды сопроцессора, равен 14 байт;
Я если use32, то размер буфера, из которого будет производиться восстановление среды сопроцессора, равен 28 байт.
Далее из области памяти формата ml4/m28, начальный адрес которой указан операндом источник, производится запись информации в регистры CWR, SWR, TR, IPR, DPR сопроцессора. Структура области памяти, содержащей образ рабочей среды сопроцессора, показана на рис. 17.21.
Флаги (SWR): C1 устанавливается в 1 при переполнении стека или в 0 в противном случае; СО, С2, СЗ не определены.
FMUL/FMULP/FIMUL
FMULP
FMUL/FIMUL множитель_1
FMUL/FMULP множитель_1, множитель_2
/1 С8 /1 88 A DD DD/1 С8 /1 88 A DD DDFMULm32real
/1 С8 /1 88 A DD DD
/1 С8 /1 88 A DD DD
FMUL m64real
FMULST(O), ST(i) FMULST(i),ST(O) FMULPST(i), 51(0)
FIMULm32int
FIMULml6int
Умножение.
Действия: команда имеет три варианта расположения операндов.
Я Для команды без операндов (только FMULP) первый множитель находится в регистре "⑴, второй — в регистре ST(O). Команда выполняет умножение ST(1) = ST(1) • sr(o). Последнее действие — выталкивание значения из регистра ST⑼. Результат умножения — в ST(0).
Я Для команды с одним операндом первый множитель (операнд) находится в ячейке памяти т32(64) или ml6(32)int, второй — в регистре ST(O). Команда выполняет умножениеsr(o)= =множмтель_1 • ST(O);
в Для команды с двумя операндами первый множитель (операнд множитель_1) хранится в ST(O)/ ST(i), второй множитель (операнд множитель_2) — в ST(i)/ST(O)・Команда выполняет умножение (множитель_1 • множитель_2). Результатумножения помещается либо в регистр ST(0) для команды FMULST(O),ST(i), либо в регистр ST(i) для команды FMUL/FMULP ST(i),ST(0).
Флаги (SWR): см. описание команды F2XM1.
FNOP
FNOP
D9 DO
Нет операции.
Действия: команда не выполняет никаких действий. Эффект ее применения заключается в том, что она занимает место (два байта) в потоке команд.
Флаги (SWR): С1 СО С2 = СЗ = О
FPATAN
FPATAN
D9 F3
Вычисление частичного арктангенса.
Действие: исходные операнды: значение х в регистре ST(0); значение у в регистре ST(1). Команда вычисляет значение арктангенса частного %/?/; выталкивает значения х и у из стека сопроцессора; записывает результат вычисления арктангенса в вершину стека — регистр ST(O).
Исходные значения х и у должны отвечать условию 0 < у < х <十8. Если это не так, необходимо использовать следующие формулы приведения:
arctg(x) =
arctg(^) =
Флаги (SWR): см. описание команды F2XM1.
FPREM
FPREM
D9 F8
Вычисление частичного остатка от деления ST(0) наST⑴.
Действия: делимое хранится в ST(0), делитель — в ST(1). Команда вычитает значения полей порядков в ST(O) и sr⑴: D = ST(O) - ST⑴.
И Если полученная разность d меньше 64, выполняется деление /=результат кото
рого округляется путем усечения к ближайшему меньшему целому; в现0) записывается новое значение, равное ST(0) = ST(O) - (ST⑴ • Г); бит SWR.C2 устанавливается в 0 (это означает, что в ST(O) получен истинный остаток, удовлетворяющий требованию: остаток < делителя); биты co, С1, СЗ в SWR устанавливаются равными значениям трех младших битов значения частного /-Wo-
綏 Если полученная разность d больше 64, то бит SWR.C2 устанавливается в 1 (это означает, что в ST(O) получен пока только частичный остаток, не удовлетворяющий требованию: остаток < < делителя, и потребуется повторить обращение к команде FPREM для получения истинного остатка, удовлетворяющего указанному требованию); переменной я присваивается значение из диапазона 32...63; выполняется деление/= (ST(O)/ST(l))/2(,z-n); выполняется округление I путем усечения к ближайшему меньшему целому; в ST(0) записывается значение, равное ST(O) - -(STCO"・2<”">).
Флаги (SWR): СО устанавливается равным биту 2 частного; С1 устанавливается в 0 при переполнении стека или устанавливается равным биту 0 частного; С2 устанавливается в 0, если получен истинный остаток, или в 1, если остаток все еще частичный; СЗ устанавливается равным биту 1 частного.
FPREM1
FPREM1
D9 F5
Вычисление частичного остатка от деления ST(O) наST⑴ в соответствии со стандартом IEEE-
754.
Действие: делимое хранится в регистре ST(O), делитель — в регистре硏1). Команда вычитает значения полей порядков в ST(O) и ST(1): D^ST(O) - ST(1).
陰絲 Если полученная разность d меньше 64, то выполняется деление / = ST(O)/ST (1), результат которого округляется к ближайшему целому; в ST(0) записывается новое значение, равное ST(0) = - ST(O) - (ST⑴ • Г); бит SWR.C2 устанавливается в 0 (это означает, что в ST(O) получен истинный остаток, удовлетворяющий требованию остаток < делителя); биты СО, С1, СЗ в SWR устанавливаются равными значениям трех младших битов частного /—
瀰 Если полученная разность d больше 64, то бит SWR.C2 устанавливается в 1 (это означает, что в ST(O) получен пока только частичный остаток, не удовлетворяющий требованию: остаток < < делителя, и потребуется повторить обращение к команде FPREM1 для получения истинного остатка, удовлетворяющего указанному требованию); переменной п присваивается значение из диапазона 32...63; выполняется деление/= (ST(O)/ST(1))/2W~">;выполняется округление / путем усечения к ближайшему меньшему целому; в ST(O) записывается значение, равное ST⑼ -
Команда FPREM1 используется для точного деления чисел в соответствии с требованиями стандарта на вычисления с плавающей точкой IEEE-754, согласно которому значение остатка должно быть меньше половины модуля (делителя).
Флаги (SWR): СОустанавливается равным биту 2 частного; С1 устанавливается в 0 при переполнении стека или устанавливается равным биту 0 частного; С2 устанавливается в 0, если получен конечный остаток, или в 1, если остаток все еще частичный; СЗ устанавливается равным биту 1 частного.
FPTAN
FPTAN
D9 F2
Вычисление частичного тангенса угла в радианах из регистра q(o)・
Действия: если значение в ST(0) находится в диапазоне -263...263f бит С2 устанавливается в О, вычисляется тангенс значения в ST(0), результат записывается в 9(0), а в стек записывается вещественная единица. Окончательное содержимое регистров: "(°) = 1,0, ST(1) — тангенс угла. Если значение в ST(O) не находится в диапазоне -263…2叱бит С2 устанавливается в 1.
Флаги (SWR): см. описание команды FCOS.
FRNDINT
FRNDINT
D9 FC
Округление значения в регистре "(0) до целого.
Действия: выполняется округление значения в ST(0) в соответствии со значением поля CWR.RC:
滋 если CWR.RC = ООЬ, округление до ближайшего целого;
если CWR.RC =01b, округление до ближайшего меньшего целого;
у если CWR.RC = 10b, округление до ближайшего большего целого;
если CWR.RC = иь, округление отбрасыванием дробной части числа.
Результат округления записывается в регистр ST(O).
Флаги (SWR): см. описание команды F2XML
FRSTOR
FRSTOR источник
DD/4
Восстановление полной среды сопроцессора.
Действия: выясняются режим работы процессора (R, V или Р) и размер операнда для текущего сегмента кода. Эти значения определяют размер буфера (94 или 108 байт), из которого будет производиться восстановление полной среды сопроцессора. После этого производится запись информации в следующие регистры сопроцессора из области памяти т94/108, начальный адрескоторойуказаноперандомисточник:
綴регистр управления CWR;
с; регистр состояния SWR;
регистр теговTR;
регистр указателя команд IPR;
регистр указателя данных DPR;
幾 регистры стека сопроцессора ST(0)...ST(7).
Структура области памяти, формируемая командой FRSTOR, аналогична таковой для команды FNSAVE (см. далее описание команды FNSAVE).
Флаги (SWR): Cl, CO, С2, СЗ формируются в соответствии с новым содержимым регистра CWR.
FSAVE/FNSAVE
FSAVE/FNSAVE приемник
9BDD/6FSAVE m94/108byte
DD/6FNSAVE m94/108byte
Сохранение полной среды сопроцессора.
Действия: проверяется наличие незамаскированных исключений в регистре SWR и (только для команды FNSAVE) ожидается окончание обработки незамаскированных исключений. Далее выясняются режим работы процессора (R, V или Р) и размер операнда для текущего сегмента кода. Эти значения определяют размер буфера (94 или 108 байт), в который будет производиться сохранение полной среды сопроцессора. Запись информации в область памяти П194/108, начальный адрес которой указан операндом приемник, производится из следующих регистров сопроцессора:
регистр управления CWR;
修регистр состояния SWR;
義 регистр тегов TR;
扌总 регистр указателя команд IPR;
регистр указателя данных DPR;
滋 регистры стека сопроцессораST(O)...ST(7).
Сопроцессор возвращается в начальное состояние путем установки следующих значений регистров сопроцессора: CWR 037fh;SWR = 00 00;TWR =節;регистрыуказателейданныхDPR иIPR команд устанавливаются в 0.
Структура области памяти, формируемая командой FSAVE/FNSAVE, показана на рис. 17.20.
Флаги (SWR): Cl, CO, C2, СЗ сохраняются и очищаются.
FSCALE
FSCALE
D9 FD
Масштабирование значения в ST(O).
Действия: исходные значения: "(0) = ST(1) = у. Значениеу округлить к ближайшему меньшему целому (обозначим это значение ^0 и вычислить выражение ST(O) = x • 24 Команда не очищает регистр ST⑴.
Флаги (SWR): см. описание команды F2XML
FSIN
FSIN
D9 FE
Вычисление синуса значения угла в радианах из регистраk(0)・
Действия: если значение х в регистре ST(O) находится в диапазоне -263<x< +2吗то присвоить значения CWR.C2 = О, ST(0) - sin(T). В противном случае оставить значение в вершине стека без изменений и установить в 1 бит CWR.C2.
Флаги (SWR): см. описание команды FCOS.
FSINCOS
FSINCOS
D9 FB
Вычисление синуса и косинуса значения угла в радианах из регистра ST(O).
Действия: если значение х находится в диапазоне -26;!<x< 十2吗то присвоить значения CWR.C2 = 0; ST(0) =sin(^); CWR.TOP = CWR.TOP - 1; ST(O) =Если х не иахо/1ится в указанном
диапазоне, оставить значение в вершине стека без изменений и установить в 1 бит CWR.C2. Конечный результат сохраняется в регистрах: ST⑼ =ST(1) = sin(x).
Флаги (SWR): см. описание команды FCOS.
FSQRT
FSQRT
D9 FA
Вычисление корня квадратного из содержимого регистра ST(0) и запись результата в ST(0).
Флаги (SWR): см. описание команды F2XM1.
FST/FSTP
FST/FSTP приемник
D9 /2FST m32real
DD /2FST m64real
DD DO + FSTST⑴
D9/3FSTP т32гез[
OD/3FSTP m64real
DB /7FSTP m80「eal
DD D8 + iFSTP ST(i)
Сохранение вещественного значения из регистра W)-
Действия: если приемник является ячейкой памяти, то перед сохранением производится округление числа в вершине стека до значения, соответствующего размеру мантиссы приемника. Порядок числа также при необходимости приводится к размеру порядка приемника. Режим округления выбирается, исходя из значения в поле CWR.RC. После этого округленное значение из регистра ST(0) копируется в место (ячейку памяти или регистр), указанное операндом приемник. Последнее действие FSTP — выталкивание значения из вершины стека, то есть увеличение его указателя вершины SWR.TOP SWR.TOP + 1.
Флаги (SWR): см. описание команды F2XM1.
FSTCW/FNSTCW
FSTCW/FNSTCW приемник
9В D9 /7FSTCW m2 byte
D9 /7FNSTCW m2byte
Сохранение управляющего слова сопроцессора.
Действия: управляющее слово сопроцессора CWR сохраняется в слове памяти, указанном операндом приемник, с проверкой (только для команды FSTCW) наличия нсзамаскировшш ых исключений врегистреSWR.
Флаги (SWR): Cl, CO, C2, СЗ не определены.
FSTENV/FNSTENV
FSTENV/FNSTENV приемник
9BD9/6 FSTENV ml4/28byte
D9/6 FNSTENVml4/28byte
Сохранение среды сопроцессора в памяти.
Действия: проверяется (только для команды FSTENV) наличие незамаскированных исключений в регистре SWR, затем выясняются режим работы процессора (R, V или Р) и размер операнда для текущего сегмента кода. Эти значения определяют размер буфера (14 или 28 байт), в который будет производиться запись среды сопроцессора. В область памяти ml4/28, начальный адрес которой указан операндом приемник, производится запись информации из следующих регистров сопроцессора:
橙 регистр управления CWR;
鑼 регистр состояния SWR;
趣 регистр тегов TR;
鑑 регистр указателя команд IPR;
緻 регистр указателя данных DPR.
Маски всех исключений устанавливаются в единицу (маскирование исключений). Команда FNSTENV не проверяет наличие незамаскированных исключений в регистре SWR и не ожидает окончания их обработки.
Структура области памяти, сформированная командой, показана на рис. 17.20.
Флаги (SWR): Cl, CO, С2, СЗ не определены.
FSTSW/FNSTSW
FSTSW/FNSTSW приемник
9B DD/7 FSTSW m2byte
9В DF ЕОFSTSW АХ
DD/7FNSTSW m2byte
DF ЕОFNSTSW АХ
Сохранение слова состояния сопроцессора.
Действия: сохранение слова состояния сопроцессора (регистра SWR) в слове памяти или регистре АХ с проверкой (только для команды FSTSW) наличия незамаскированных исключений в регистре SWR.
Флаги (SWR): Cl, CO, C2, СЗ не определены.
FSUB/FSUBP/FISUB
FSUBP
FSUB/FISUB вычитаемое
FSUB/FSUBP уменьшаемое, вычитаемое
D8 /4FSUBm32real
DC /4FSUBm64real
D8 EO + iFSUBST(O),ST(i)
DC E8 + iFSUBWST(O)
DE E8+ iFSUBPST(i),ST(O)
DEE9FSUBP
DA/4FISUB m32int
DE/4FISUB ml6int
Вычитание вещественных значений.
Действия: команды имеют варианты расположения операндов.
Ж Для команды без операндов (FSUBP) в регистре现O)находится вычитаемое, в регистре ST(1) — уменьшаемое. Команда выполняет вычитание ST⑴ =- ST(O). Последнее действие — вы
талкивание значения из регистра ST(0). Результат вычитания заносится в регистр Я(0).
^ Для команды с одним операндом (вычитаемое) в регистре ST(0) находится уменьшаемое, в области памяти m32(64)real илиml6(32)int— вычитаемое. Команда выполняетвычитание ST(O) = - ST⑼ - вычитаемое.
轡 Для команды с двумя операндами (уменьшаемое и вычитаемое) уменьшаемое находится в ST(O)/ 贞⑴,вычитаемое — в ST(i)/ST(O).Командавыполняетвычитание(уменьшаемое -вычитаемое)
и помещает результат в регистр ST(0) для команды fsub ST(0),ST(i) или в ST(i) для команды fsub ST(i),ST(O).
Флаги (SWR): см. описание команды F2XM1.
FSUBR/FSUBRP/FISUBR
FSUBRP
FSUBR/FISUBRуменьшаемое
FSUBR/FSUBRP вычитаемое, уменьшаемое
D8 /5 FSUBR m32real
ОС /5 FSUBR m64real
D8 E8 +1 FSUBRST(O),ST(i)
DC E0+ i FSUBR ST⑴,ST(O)
DE EO + i FSUBRPST(i),ST(O)
DE El FSUBRP
DA /5 FISUBR m32int
DE/5 FISUBRml6int
Обратное вычитание вещественных значений.
Действия: команды имеют варианты расположения операндов:
幾: Длякоманды без операндов (FSUBRP) вычитаемое находится в регистре ST(1), уменьшаемое — в регистре ST(0). Команда выполняет вычитание ST⑴ = ST(0) - ST(1). Последнее действие — выталкивание значения из ST(O), после чего результат вычитания оказывается в регистре ST(0).
寻詡Для команды с одним операндом (уменьшаемое) уменьшаемое находится в области памяти m32(64)real или ml6(32)int, вычитаемое — в регистре硏0)・Команда выполняет вычитание ST(0) н уменьшаемое - ST(O).
綴Для команды с двумя операндами (вычитаемое и уменьшаемое) уменьшаемое помещается в регистрST(i)/ST(O),вычитаемое — в регистр ST(O)/ST(i). Команда выполняетвычитанне (уменьшаемое - вычитаемое) и помещает результат в регистр ST(O) для команды fsubr ST(O),ST(i) или в ST(i) для команды fsubr ST(i),ST(0).
Флаги (SWR): см. описание команды FSUB.
FTST
FTST
D9 Е4
Сравнение значения в регистре ST(0) с нулем.
Действия: значение в вершине стека сравнивается с нулем. По результатам работы устанавливаются биты СЗ, С2, СО в регистре CWR (табл. П.20).
Флаги (SWR): C1 устанавливается в 1 при переполнении стека, иначе сбрасывается в 0; СО, С2, СЗ устанавливаются в соответствии с результатами работы команды.
Таблица П.20. Установка битов СЗ, С2 и СО в регистре SWR по состоянию регистра ST(0)
Состояние регистра ST(0)
Состояние битов СЗ, С2, СО в регистре CWR
Не определено
C3 = 1,C2= 1,CO= 1
ST(0) = 0
C3= l,C2 = 0, C0 = 0
ST(0) < 0
СЗ - 0, C2 - 0, СО - 1
ST(0) > 0
СЗ = 0, С2 - 0, СО - 0
FUCOM/FUCOMP/FUCOMPP
■ FUCOM/FUCOMP/FUCOMPP
FUCOM источник
■ DDЕО+
DD El
DDE8+i
DD E9
DA E9
FUCOM ST(i)
FUCOM
FUCOMPST(i)
FUCOMP
FUCOMPP
Неупорядоченное сравнение вещественных значений.
Действия: команда имеет два варианта расположения операндов. В команде FUCOM/FUCOMP/ FUCOMPP без операндов сравниваемые значения находятся в регистрах стека ST(O) и ST(1). В команде FUCOM с одним операндом сравниваемые значения находятся в регистрах стека ST(O) и ST(i). Командавыполняетсравнениезначений,порезультатамкоторогоустанавливаютсяфлагиСЗ, С2, СО в регистре CWR (табл. П.21).
Таблица П.21.Установка битов СЗ, С2 и СО в регистре SWR по результатам сравнения регистров ST(0) и ST(1 )/ST(i)
Значения в регистрах ST(0)
nST(1)/ST(i)
Состояние битов C3, С2, CO в регистре CWR
ST(0) и ST(l)/ST(i) несравнимы
C3=lfC2=l,C0^1
ST(0)= ST(l)/ST(i)
C3- l,C2 = 0tC0 = 0
ST(0) V ST⑴/ST(i)
СЗ - 0, C2 = 0, CO - 1
ST(0) > ST(l)/ST(i)
C3 = 0, C2 0, CO - 0
Флаги (SWR): Cl устанавливается в 1 при переполнении стека; СО, С2, СЗ устанавливаются в соответствии с результатами работы команды.
FWAIT/WAIT
FWAIT/WAIT
9Ь
Останов процессора.
Действия: команда приостанавливает работу основного процессора до поступления сигнала от сопроцессора об окончании обработки последней команды.
Флаги (SWR): CO, Cl, C2, СЗ не определены.
FXAM
FXAM
D9 E5
Определение типа операнда в регистре ST(0).
Действия: команда определяет тип операнда в "(0) и, исходя из него, устанавливает биты СЗ, С2, С1, СО (табл. П.22). В бит С1 помещается знак операнда в ST(0).
Таблица П.22. Определение типа операнда по состоянию битов СЗ, С2 и СО в регистре SWR
Состояние битов СЗ, С2, СО
ТипоперандаврегистреCWR
СЗ = 0, С2 -0, СО - 0
Неизвестный тип
СЗ - 0, С2 = 0, СО =1
Нечисло
С3 = 0,C2 = l,C0 = 0J
Корректное вещественное число
C3 = 0,C2= 1,CO = 1
Бесконечность
СЗ - 1, С2 - 0, СО二0
Нуль
СЗ = 1, С2 = 0, СО = 1
Пусто
C3= 1,C2= l.CO-O
Денормал изованное число
FXCH
FXCH
FXCH источник
D9 C8 + iFXCH ST(i)
D9 С9FXCH
Обмен содержимым регистров стека.
Действия: команда имеет два варианта расположения операндов. В команде FXCH без операндов исходные значения находятся в регистрах стека ST(O) и ST(1). В команде FXCH с одним операндом исходные значения находятся в регистрах стека ST(O) и ST(i). Команда выполняет обмен значениями между содержимым регистров ST(O) и ST(l)/ST(i).
Флаги (SWR): C1 устанавливается в 1 при переполнении стека, иначе сбрасывается в 0; СО, С2, СЗ не определены.
FXTRACT
FXTRACT
D9 F4
Выделение порядка и мантиссы значения вsr(o).
Действия: значение порядка выделяется и записывается в регистр ST⑴; значение мантиссы выделяется и помещается на вершину стека сопроцессора — регистр 5T(0).
Флаги (SWR): см. описание команды FABS.
FYL2X
FYL2X
D9 Fl
Вычисление выражения у • log2(x).
Действия: значение у хранится в ST(1) = у,х — в ST(O). Команда помещает результат вычисления выражения у • log2(x)в регистр ST(0).
Флаги (SWR): см. описание команды F2XM1.
FYL2XP1
FYL2XP1
D9 F9
Вычисление выражения у •log2(r+ 1).
Действия: значение у хранится в ST(1), х — в ST(O). Значение в STQ должно находиться в диапазоне от —oo до十8, а в ST(O) — в диапазоне от —(1 -何2) до (1 - v2/2) . Если значение в ST(O) лежит вне указанного диапазона, то результат операции не определен. Команда записывает результат вычисления выражения у • bg2a+ 1) в регистр ST(O).
Флаги (SWR): см. описание команды F2XM1.
Команды блока ММХ
EMMS
EMMS
OF 77
Подготовка сопроцессора к исполнению команд.
Действие: команда переводит все поля регистра тегов TWR сопроцессора в единичное состоя-
ние.
MASKMOVQ
MASKMOVQ источник, маска
•
Запись байтов в память изпо маске (выборочная запись).
Действие: последовательно проверяются знаковые биты всех байтов маски, и если какой-то из этих битов равен 1, то соответствующий байт источника копируется в область памяти, начальный адрес которой содержится в регистрах DS:DI/EDI. Если знаковый бит равен нулю, то соответствующий байт в приемнике будет нулевым.
maskmovq источник, маска
Пример: maskmovq
Источник (ХММ-регистр)
I I I I I иЗ I I I I
Маска (ХММ-регистр)У
ИГ И,I
V V V V V V V V
Приемник I I00I I00I00IиЗIи2I00I I
Старшие адреса оперативной памяти
ds:di/edi
Особенность работы команды — в том, что при копированиииспользуется кэш-па
мять.
MOVD
MOVD приемник, источник
MOVDr/m32
MOVD
66 OF 6E /r MOVDr/m32
66 OF 7E /r MOVD r/m32, rxmm
Перемещение двойного
Действие: у команды есть несколько вариантов действий:
если приемник является ММХ- илив битыприемника записывается
источника, остальные биты приемника обнуляются;
№ если приемник является 32-разрядной ячейкой памяти или регистром общего назначения, то в приемник записывается значение битовисточника (ММХ- или
MOVNTQ
приемник, источник
OF,E7,/rrmmx
Запись 64 бит в память из(без использования кэш-памяти).
MOVQ
MOVQ приемник, источник
•
OF 7F/rMOVQ
rxmm2/m64
MOVQ
учетверенного слова.
Действие: у команды есть несколько вариантов действий:
黛 если приемник является ММХ-регистром, туда помещается значение источника — ММХ-ре- гистра или 64-разрядной ячейки памяти;
-. если приемник является 64-разрядной ячейкой памяти, то в разряды 0...63 приемника помещается содержимое разрядов 0...63 источника.
если приемник и источник являются ХММ-регистрами, то в разряды 0...63 приемника помещается содержимое разрядов 0...63 источника, а разряды 64...127 приемника не изменяются.
— если источник является 64-разрядной ячейкой памяти, а приемник — ХММ-регистром, то в разряды 0...63 приемника помещается содержимое разрядов 0...63 источника, а в разряды 64...127 приемника - значение ООООООООООООООООН.
PACKSSWB/PACKSSDW
PACKSSWB приемник, источник
OF 63 /гPACKSSWB mm, mm/m64
66 OF 63 /r PACKSSWB rxmml, rxmm2/ml28
Упаковка слов в байты со знаковым насыщением.
PACKSSDW приемник, источник
0F6B/rPACKSSDW mm, mm/m64
66 OF 6B /r PACKSSDW rxmml, rxmm2/ml28
Упаковка двойных слов в слова со знаковым насыщением.
Packsswb приемник, источник
ИсточникИсточникПриемник (ХММ-регистр))
Источник
Источник
Приемник (ХММ-регистр)Приемник (ХММ-регистр)
Приемник (ХММ-регистр)
Приемник (ХММ-регистр)
Packssdw приемник, источник
ИсточникИсточникПриемник (ХММ-регистр))Приемник (ХММ-регистр))О 1279563О 127956312795633112795633131О31О12711195• 7963473115ОПриемник (ХММ-регистр)12711195• 7963473115ОПриемник (ХММ-регистр)
Источник
Источник
Приемник (ХММ-регистр))
Приемник (ХММ-регистр))
О 1279563
О 1279563
127956331
127956331
31О
31О
12711195• 7963473115О
Приемник (ХММ-регистр)
12711195• 7963473115О
Приемник (ХММ-регистр)
Действие: для MMX-расширения команды преобразуют восемь (четыре) элемента размером в слово (двойное слово) в восемь (четыре) элемента размером в байт (слово). Для ХММ-расши- рения команды преобразуют шестнадцать (восемь) элементов размером в слово (двойное слово) из источника и приемника в шестнадцать/восемь элементов в операнде приемник размером в байт (слово) (см. рис. на предыдущей стр.). Если значение элемента источника превышаетдопустимое значение элемента приемника, то в элементе приемника формируется предельный результат в соответствии с принципом знакового насыщения:
PACKSSWB — 07fhдля положительных чисел и 080h для отрицательных;
PACKSSDW一 07fffh для положительных чисел и 08000h для отрицательных.
PACKUSWB
PACKUSWB приемник, источник
Of67 /гPACKUSWB rmmxl, rmmx2/m64
66 OF 67 /r PACKUSWB rxmml, rxmm2/ml28
Упаковка слов в байты с беззнаковым насыщением.
Действие: команда преобразует восемь (шестнадцать для ХММ) элементов размером слово в восемь (шестнадцать для ХММ) элементов размером байт (см. рисунок ниже). Если пересыла- смос значение больше допустимого для поля приемника, то в нем формируется предельный результат по принципу беззнакового насыщения, что соответствует значениям Offli для положительных чисел и 00h для отрицательных.
packuswb приемник, источник
ИсточникПриемник (ММХ-регистр)
Приемник (ММХ-регистр)
Packsswb приемник, источник
ИсточникИсточникПриемник (ХММ-регистр)
Источник
Источник
127111 95796347127111 957963473115031150127111 957963473115О127111 957963473115О
127111 95796347
127111 95796347
31150
31150
127111 957963473115О
127111 957963473115О
127 119 111 103 95 87 79 71 63 55 47 39 31 23 15 7 0
Приемник (ХММ-регистр)
PADDB/PADDW/PADDD
PADDB приемник, источник PADDW приемник, источник PADDD приемник, источник
OF FC /г PADDB rmmxl,rmmx2/m64 66 OF FC /г PADDB rxmml,rxmm2/ml28 OF FD /rPADDW rmmxl,rmmx2/m64
66 OF FD /r PADDW rxmml,rxmm2/ml28
OF FE /rPADDD rmmxl,rmmx2/m64
66 OF FE /r PADDD rxmml#rxmm2/ml28
Сложение упакованных байтов (слов, двойных слов).
Действие: команда в зависимости от кода операции складывает соответствующие элементы операндов источника и приемника размером байт (слово, двойное слово). При возникновении переполнения результат формируется в соответствии с принципом циклического переполнения и помещается в операнд приемник.
PADDQ
PADDQ приемник, источник
OF D4/rPADDQ rmmxl,rmmx2/m64
66 OF D4/r PADDQ rxmml,rxmm2/ml28
Сложение учетверенных слов.
Действие: команда складывает 64-разрядные целые значения в источнике и приемнике. Исходя из типа источника, возможны две схемы умножения:
緻 если источник — ММХ-регистр или 64-разрядная ячейка памяти, то разряды...63 приемника складываются с разрядами 0...63 источника и результат помещается в приемник (ММХ-регистр);
Я если источник — ХММ-регистр или 128-разрядная ячейка памяти, то разряды 0...63 приемника складываются с разрядами 0…63 источника и результат помещается в разряды 0...63 приемника, разряды 64...127 приемника складываются с разрядами 64...127 источника и результат помещается в разряды 64...127 приемника.
В результате выполнения команды PADDQ регистр EFLAGS не отражает факта возникновения ситуации переполнения или переноса. Когда результат сложения слишком большой, чтобы быть представленным в 64-разрядном элементе приемника, то он «заворачивается» (перенос игнорируется). Для обнаружения подобных ситуаций программное обеспечение должно использовать другие методы.
Флаги: не изменяются.
PADDSB/PADDSW
PADDSB приемник, источник
PADDSW приемник, источник
OF ЕС/гPADDSB rmmxl, rmmx2/m64
66 OF EC/r PADDSB rxmml,. rxmm2/ml28
OF ED/rPADDSW rmmxl, rmmx2/m64
66 OF ED/г PADDSWrxmml, rxmm2/ml28
Сложение упакованных байтов (слов) со знаковым насыщением.
Действие: команда в зависимости от кода операции складывает соответствующие элементы операндов источник и приемник размером байт (слово) с учетом знака. При переполнении результат формируется в соответствии с принципом знакового насыщения:
It PADDSB — 07fhдля положительных чисел и 080h для отрицательных;
PADDSW — 07fffh для положительных чисел и08000h для отрицательных.
Результат помещается в операнд приемник.
PADDUSB/PADDUSW
PADDUSB прмемнмк, источник
PADDUSW приемник, источник
OF DC /rPADDUSB rmmxl,rmmx2/m64
66 OF DC /r PADDUSB rxmmVxmni2/nil28
OF DD /rPADDUSW rmmxl,rmmx2/m64
66 OF DD /r F%DDUSW гхттХгхтт2/т128
Сложение упакованных байтов (слов) с беззнаковым насыщением.
Действие: команда в зависимости от кода операции складывает без учета знака соответствующие элементы операндов источник и приемник размером байт/слово. При переполнении результат формируется в приемнике в соответствии с принципом беззнакового насыщения:
PADDUSB — Offh и 00h для результатов сложения, соответственно, больших или меньших мак- симально/минимально представимых значений в беззнаковом байте;
И PADDUSW — Offffb и OOOOh для результатов сложения, соответственно, больших или меньших максимально/минимально представимых значений в беззнаковом слове.
Результат помещается в операнд приемник.
PAND
PAND приемник, источник
OFDB/rPAND rmmxl, rmmx2/m64
66OFDB/r PAND rxmml, rxmm2/n)128
Упакованное логическое И.
Действие: команда выполняет поразрядную операцию логического И над всеми битами операндов источник и приемник. Результат помещается в операнд приемник.
PANDN
PANDN приемник, источник
OFDF/r PANDN rmmxl, rmmx2/m64
66 OF DF/r PANDN rxmml, rxmm2/ml28
Унакованное логическое И-НЕ.
Действие: команда выполняет поразрядную операцию логического И-НЕ над всеми битами операндов источник и приемник. Результат помещается в операнд приемник.
PAVGB/PAVGW
PAVGB приемник, источник
PAVGW приемник, источник
0Fz EO, /r PAVGB rmmxl, rmmx2/m64
OF,E3, /r PAVGW rmmxl, rmmx2/m64
66 OF EO, /r PAVGB rxmml, rxmm2/ml28
66 OF E3 /r PAVGW rxmml, rxmm2/ml28
Упакованное среднее.
Действие: команда выполняет параллельное сложение байтов (слов) источника и приемника и сдвигает результат сложения на один разряд вправо (деление на 2).
PCMPEQB/PCMPEQW/PCMPEQD
PCMPEQB приемник, источник
PCMPEQW приемник, источник
PCMPEQD приемник, источник
OF 74 /r PCMPEQB rmmxl,rmmx2/m64'
0F75/r PCMPEQW rmmxl,rmmx2/m64
OF 76/r PCMPEQD rmmxXrmmx2/m64
660F74/r PCMPEQB rxmml,rxmm2/ml28
66 OF 乃/r PCMPEQW rxmml,rxmm2/ml28
66 OF 76/r PCMPEQD rxmml,rxmm2/ml28
Сравнение на равенство упакованных байтов (слов, двойных слов).
Действие: команды сравнивают на равенство элементы источника и приемника и формируют элементы результата по следующему принципу:
霹 если элемент источника равен соответствующему элементу приемника, то элемент результата устанавливается в зависимости от применяемой команды равным значению Offh, Offffhили Offffffffh;
後 если элемент источника не равен соответствующему элементу приемника, то элемент результата устанавливается в зависимости от применяемой команды равным значению OOh, OOOOh или OOOOOOOOh.
Результат помещается в операнд приемник.
PCM PGTB/PCM PGTW/PCM PGTD
PCMPGTB приемник, источник
PCMPGTW приемник, источник
PCMPGTD приемник, источник
OF 64 /r PCMPGTB rmmxl,rmmx2/m64
OF 65 /rPCMPGTW rmmxl,rninix2/m64
OF 66 /rPCMPGTD rmmxl,rmmx2/m64
66 0F64 /r PCMPGTB rxmml,rxmni2/ml28
66 OF 65/r PCMPGTW rxmmlzrxmm2/ml28
66 OF 66 /r PCMPGTD rxmml,rxmm2/ml28
Сравнение по условию «больше чем» упакованных байтов (слов, двойных слов).
Действие: команда производит сравнение по условию «больше чем» элементов операндов источник и приемник и формирует элементы результата по следующему принципу:
Ж если элементприемника больше соответствующего элементаисточника, то элемент результата устанавливается в зависимости от применяемой команды равным значению Offh, Offffh или Offffffffh;
餒 если элемент приемника не больше соответствующего элемента источника, то элемент результата устанавливается, в зависимости от применяемой команды, равным значению OOh, OOOOh или OOOOOOOOh.
Результат помещается в операнд приемник.
PEXTRW
PEXTRW приемник, источник, маска
OF C5 /r ibPEXTRW r32, rmmx, i8
66 OF C5 /Г i8PEXTRW r32, rxmm, i8
Извлечение 16-разрядного слова из ММХ-регистра по маске.
Действие: команда выделяет два младших бита непосредственного операнда маска. Их значение определяет номер слова в операнде источник (ММХ-регистр). Данное слово перемещается в младшие 16 битов операнда приемник, представляющего собой 32-разрядный регистр общего назначения. Старшие 16 бит этого регистра обнуляются.
PINSRW
PINSRW приемник, источник, маска
0FC4 /ribPINSRWrmmx,r32/ml6,i8
66 OF C4/r 18 PINSRW rxmmj32/ml6,i8
Вставка 16-разрядного слова в ММХ- или ХММ-регистр.
Действие: команда выделяет два младших бита непосредственного операнда маска. Их значение определяет номер слова в операнде приемник, который представляет собой ММХ- или ХММ- регистр. В это слово будут перемещены младшие 16 битов операнда источник, который представляет собой 32-разрядный регистр общего назначения или 16-разряднуюячейку памяти.
PMADDWD
PMADDWD приемник, источник
OF F5 /г PMADDWD rmmxl,rmmx2/m64
66 OF F5 /r PMADDWD rxmmljxmm2/ml28
Улакованпое знаковое умножение знаковых словоперандов источник и приемник с последующим сложением промежуточных результатов в формате двойного слова.
Действие: команда производит умножение с учетом знака четырех знаковых слов источника начетырезнаковыхеловаприемника и формируетэлементырезультата.
При переполнении результат устанавливается равным 8000 0000 8000 OOOOh. Ситуация переполнения возникает, если все элементы обоих операндов равны 8000h.
PMAXSW
PMAXSW приемник, источник
OF ЕЕ /гPMAXSWrmmxl,rmmx2/m64
66 OF ЕЕ /г PMAXSW rxmml,rxmm2/ml28
Возврат максимальных упакованных знаковых слов.
Действие: команда определяет наибольшее слово для каждой пары упакованных слов источника и приемника с учетом знака и заменяет им соответствующие слова приемника.
PMAXUB
PMAXUB приемник, источник
OF DE/г PMAXUB rmmxVmmx2/m64
66 OF DE/г PMAXUB rxmml,rxmm2/ml28
Возврат максимальных упакованных беззнаковых байтов.
Действие: для каждой пары байтовых элементов источника и приемника определяется наибольший без учета знака и им заменяется соответствующий элемент приемника.
PMINSW
PMINSW приемник, источник
OFEA/rPMINSW rmmxl,rmmx2/m64
66 OF EA /r PMINSW rxmml,rxinm2/nil28
Возврат минимальных упакованных знаковых слов.
Действие: для каждой пары элементов (размером 16 бит) источника и приемника команда опре- дсляетнаименьший сучетомзнака изаменяетимсоответствующийэлементприемника.
PMINUB
PMINUB приемник, источник
OF DA /rPMINUB rmmxl,rmmx2/m64
66 OF DA /r PMINUB rxmmUxmm2/ml28
Возврат минимальныхупакованных беззнаковых байтов.
Действие: для каждой пары байтовых элементов источника и приемника команда определяет наименьший безучетазнакаизаменяетимсоответствующийэлементприемника.
PMOVMSKB
PMOVMSKB приемник, источник
OF D7/r PMOVMSKB r32,rmmx
66 OF D7 /r PMOVMSKB r32, rxmm
Перемещение байтовой маски в целочисленный регистр.
Действие: команда извлекает и копирует значения старшего бита каждого из упакованных байтов ММХ- или ХММ-регистра в младший байт (слово) 32-разрядного целочисленного регистра общего назначения. Остальныеразряды целочисленного регистра обнуляются.
pmovmskb приемник, источник
PMULHUWPMULHUW приемник, источникOF E4 /rPMULHUWrmmxl,rmmx2/m6466 OF E4 /r PMULHUW rxmml,rxmm2/m 128PMULHUWPMULHUW приемник, источникOF E4 /rPMULHUWrmmxl,rmmx2/m6466 OF E4 /r PMULHUW rxmml,rxmm2/m 128pmuihuwприемник, источникpmuihuwприемник, источникI di I cl I b,1 I al I ИсточникI di I cl I b,1 I al I Источник
PMULHUW
PMULHUW приемник, источник
OF E4 /rPMULHUWrmmxl,rmmx2/m64
66 OF E4 /r PMULHUW rxmml,rxmm2/m 128
PMULHUW
PMULHUW приемник, источник
OF E4 /rPMULHUWrmmxl,rmmx2/m64
66 OF E4 /r PMULHUW rxmml,rxmm2/m 128
pmuihuwприемник, источник
pmuihuwприемник, источник
I di I cl I b,1 I al I Источник
I di I cl I b,1 I al I Источник
Промежуточныерезультаты(32 бита)Промежуточныерезультаты(32 бита)
Промежуточныерезультаты(32 бита)
Промежуточныерезультаты(32 бита)
Приемник (ММХ-регистр)
pmulhuw приемник, источник
и7иби5и4иЗи2и1иОи7иби5и4иЗи2и1иО127111 957963473115 0127111 957963473115 0ИсточникИсточникп7пбп5п4пЗп2п1пОп7пбп5п4пЗп2п1пО®®©®®®©®127111 957963473115О®®©®®®©®127111 957963473115ОПриемник (ХММ-регистр)Приемник (ХММ-регистр)
и7
иб
и5
и4
иЗ
и2
и1
иО
и7
иб
и5
и4
иЗ
и2
и1
иО
127111 957963473115 0
127111 957963473115 0
Источник
Источник
п7
пб
п5
п4
пЗ
п2
п1
пО
п7
пб
п5
п4
пЗ
п2
п1
пО
®®©®®®©®
127111 957963473115О
®®©®®®©®
127111 957963473115О
Приемник (ХММ-регистр)
Приемник (ХММ-регистр)
127111 957963473115 О
Приемник (ХММ-регистр)
Умножение упакованных беззнаковых слов с возвратомстарших слов результата.
Действие: команда производит умножение упакованных слов источника и приемника без учета знака и формирует элементы результата в соответствии с приведенной на предыдущсп странице схемой. Как видно из нее, в результате умножения слов операндов источник и приемник получаются промежуточные результаты размером 32 бита. Далее старшее слово (16бит) из каждого промежуточного результата умножения исходных элементов помещается в 16-6итннй элемент окончательного результата. Результат помещается в операнд приемник.
19 Зак. 256
PMULHW
PMULHW приемник, источник
OF Е5PMULHW
PMULHW
Упакованное знаковое умножение слов с возвратом старшего слова результата.
Действие: команда производит умножение упакованных слов источника и приемника с учетом знака и формирует элементы результата в соответствии со схемой,при описании
команды PMULHUW.
PMULLW
PMULLW приемник, источник
PMULLW PMULLW
Упакованное знаковое умножение слов с возвратом младшего слова результата.
Действие: команда производит умножение с учетом знака упакованных слов источника и приемника и формирует элементы результата в соответствии с приведенной далее схемой.
pmullw приемник, источник
I d,1 I IИсточник
Промежуточныерезультаты(32 бита)
Приемник (ММХ-регистр)
pmullw приемник, источник
127 111 9579 63 473115
и7иби5и4иЗи2и1иОи7иби5и4иЗи2и1иОИсточникИсточник
и7
иб
и5
и4
иЗ
и2
и1
иО
и7
иб
и5
и4
иЗ
и2
и1
иО
Источник
Источник
Приемник (ХММ-регистр)Приемник (ХММ-регистр)
Приемник (ХММ-регистр)
Приемник (ХММ-регистр)
1271119579 63 473115
127111957963473115 О
Приемник (ХММ-регистр)
Как видно из этой схемы, в результате умножения слов источника и получаются промежуточные результаты размером 32 бита. Далее младшее слово битов) из каждого 32- разрядного элемента промежуточного результата умножения исходных элементов помещается в16-разрядныйэлементрезультата(операндприемник).
Команды блока MMX579
PMULUDQ
PMULUDQ приемник, источник
0FF4/rPMULUDQ rmmxlzrmmx2/m64
66 OF F4 /r PMULUDQ rxmml,rxmm2/ml28
Умножение 32-разрядных целых значений без учета знака и сохранение результата вMMX- или ХММ-регистре.,
Действие: 32-разрядные целые значения со знаком в источнике и приемнике умножаются. Исходя из типа источника, возможны две схемы умножения:
舞啰 если источник — ММХ-регистр или 64-разрядная ячейка памяти, то разряды 0...31 источника и приемника перемножаются и помещаются в разряды 0…63 приемника;
綫 если источник — ХММ-регистр или 128-разрядная ячейка памяти, то разряды 0...31 источника и приемника перемножаются и помещаются в разряды 0...63 приемника, а разряды 64...95 источника и приемника перемножаются и помещаются в разряды 64...127 приемника.
Когда результат умножения слишком большой, чтобы быть представленным в приемнике, то он «заворачивается» (перенос игнорируется).
Флаги: не изменяются.
POR
POR приемник, источник
OF ЕВ/г POR rmmxl,rmmx2/m64 66 OF ЕВ /г POR rxmml,rxmm2/ml28
Упакованное логическое ИЛИ.
Действие: команда производит побитовую операцию логического ИЛИ над всеми битами операндов источник и приемник. Результат помещается в операнд приемник.
psadbw приемник, источник Источник
127119 111103 9587797163554739127119 111103 9587797163554739и15 и14 и13 и12и15 и14 и13 и12И11 иЮ и9и8И11 иЮ и9и8I1и7иби5и4I I 1I1и7иби5и4I I 13134157 ОиЗи2и1иО3134157 ОиЗи2и1иОС)С)q) С > с)q) С > с)《 )《 )Приемник (ХММ-регистр;127119 111103 95Приемник (ХММ-регистр;127119 111103 9587 7987 79716355473971635547393131п1 5 п14 п13 п12п1 5 п14 п13 п12п11 nIO п9п8п11 nIO п9п8п7 ,пб п5п4пЗIlliп7 ,пб п5п4пЗIlli341п2341п2п1п17; пf0 I7; пf0 I|и15 -п15||и15 -п15|V|и9-п9|V|и9-п9|L…G|и7-п7|L…G|и7-п7|V|и1-п1|V|и1-п1|\|иО-пОI\|иО-пОI|и8-п8||и8-п8|............1271119579 63473115 ОПриемник (ХММ-регистр)1271119579 63473115 ОПриемник (ХММ-регистр)
127119 111103 9587797163554739
127119 111103 9587797163554739
и15 и14 и13 и12
и15 и14 и13 и12
И11 иЮ и9и8
И11 иЮ и9и8
I1
и7иби5и4
I I 1
I1
и7иби5и4
I I 1
3134157 О
иЗи2и1иО
3134157 О
иЗи2и1иО
С)
С)
q) С > с)
q) С > с)
《 )
《 )
Приемник (ХММ-регистр;
127119 111103 95
Приемник (ХММ-регистр;
127119 111103 95
87 79
87 79
7163554739
7163554739
31
31
п1 5 п14 п13 п12
п1 5 п14 п13 п12
п11 nIO п9п8
п11 nIO п9п8
п7 ,пб п5п4пЗ
Illi
п7 ,пб п5п4пЗ
Illi
34
1
п2
34
1
п2
п1
п1
7
; пf0 I
7
; пf0 I
|и15 -п15|
|и15 -п15|
V|и9-п9|
V|и9-п9|
L…
G
|и7-п7|
L…
G
|и7-п7|
V
|и1-п1|
V
|и1-п1|
\
|иО-пОI
\
|иО-пОI
|и8-п8|
|и8-п8|
...
...
...
...
1271119579 63473115 О
Приемник (ХММ-регистр)
1271119579 63473115 О
Приемник (ХММ-регистр)
PSADBW
PSADBW приемник, источник)
OF F6 /гPSADBW rmmxl,rmmx2/m64
66 OF F6 /г PSADBW rxmml,rxmm2/ml28
Суммарная разница значений пар беззнаковых упакованных байтов.
Действие: для каждой пары байтов упакованных учетверенных слов операндов источник и приемник вычисляется модуль разности, после чего полученные модули складываются. Результат записывается в младшее слово упакованного учетверенного слова приемника, старшие три слова приемника обнуляются. Принцип работы команды (ХММ) поясняет схема на предыдущей странице.
PSHUFW
PSHUFW приемник, источник, маска
OF 70 /rib PSHUFW rmmxl,rniiTix2/m64j8
Перераспределение упакованных слов.
Действие: каждая пара битов маски определяет номер слова источника, которое будет перемещено в приемник следующим образом:
D 00 приемник[00...16] ~ источник[00..,15];
D 01 приемник[00...16]источник[16...31];
D 10 приемник[00...16] —источник[32...47];
11 приемник[00...16] <— источник[48...63].
it Маска[3:2]:
П 00 приемник[16...31] <-источник[00..,15];
П 01 приемник[16...31]источник[16...31];
D 10 приемник[ 16...31] —источник[32...47];
О 11 приемник[16...31] <-источник[48...63].
綫 Маска[5:4]:
П 00 приемник[32...47] 一 источник[00...15];
Р 01 приемник[32...47] <-источник[16...31];
10 приемник[32...47] <-источник[32...47];
О И приемник[32...47]一 источник[48...63].
戀 Маска[7:6]:
П 00 приемник[47...63] — источник[00...15];
D 01 приемник[47...63] <— источник[16,..31];
D 10 приемник[47...63]<-источник[32„.47];
О 11 приемник[47...63] j источник[48...63].
Работа команды PSHUFW показана на следующей схеме.
pshufwприемник, источник, маска
Источник rmmx/m64Приемник rmmx
I и3..иО I и3..иО I и3..иО IмЗмО 1 Приемник rmmx
PSLLW/PSLLD/PSLLQ
• PSLLWприемник, источник
PSLLD приемник, источник PSLLQприемник, источник
OF Fl /r
PSLLWrmmxl, rmmx2/m64
66 OF Fl /r
PSLLW rxmml,rxmm2/ml28
OF 71/6, i8
PSLLW「mmxLi8
66 0F71/618
PSLLW rxmml,i8
OF F2 /r
PSLLD rmmxl, rmmx2/m64
66 OF F2 /r
PSLLD rxmml, rxmm2/ml28
OF 72 /6 18
PSLLD rmmxl,i8
66 0F72 /6 ib
PSLLD rxmml,i8
OF F3 /r
PSLLQ rmmxl, rmmx2/m64
66 OF F3 /r
PSLLQ Txmmi, rxmm2/ml28
OF 73 /6 i8
PSLLQ rmmxl,i8
660F73 /6 i8
PSLLQ rxmml,i8
Логический сдвиг влево упакованных слов (двойныхслов, учетверенных слов).
Действие: команда сдвигает влево биты в элементах размером слово (двойное слово, учетве- рснноеслово) приемника па количеств разрядов, опрсделяемоеопсрандомисточник. Одновременно справа в каждый элемент вдвигаются нулевые значения. Результат помещается воперандпри- емник. Если значение, указанное в операнде источник, больше чем 15 (для слов), 31 (для двойных слов) или 63 (для учетверенного слова), то значение операнда приемник устанавливается равным 0.
PSRAW/PSRAD
PSRAW приемник, источник
PSRAD приемник, источник
OF El /r66 OF El /r0F71/4ib66 0F71 /418OF E2 /r66 OF E2 /rOF 72 /4ib66 OF 72/418OF El /r66 OF El /r0F71/4ib66 0F71 /418OF E2 /r66 OF E2 /rOF 72 /4ib66 OF 72/418PSRAW rmmxl, rmmx2/m64
OF El /r
66 OF El /r
0F71/4ib
66 0F71 /418
OF E2 /r
66 OF E2 /r
OF 72 /4ib
66 OF 72/418
OF El /r
66 OF El /r
0F71/4ib
66 0F71 /418
OF E2 /r
66 OF E2 /r
OF 72 /4ib
66 OF 72/418
PSRAW rxmml/xmm2/ml28
PSRAW rmmxl, i8
PSRAWrxmmlzi8
PSRAD rmmxl, rmmx2/m64
PSRAD rxmmljxmm2/ml28
PSRAD rmmxl, i8
PSRAD rxmml,i8
Арифметический сдвиг вправо упакованных слов (двойных слов).
Действие: команда сдвигает вправо биты в элементах приемника размером слово (двойное слово) на количество разрядов, определяемое операндом источник. Одновременно слева в разряды каждого элемента вдвигаются биты, количество которых определяется операндом источник. Значение вдвигаемых слева битов равно знаковому биту соответствующего элемента. Результат помещается воперандприемник.Еслизначение,указанноевисточникебольшечем 15(для слов), 31 (для двойных слов), то каждый элемент данных приемника заполняется начальным значением знакового разряда элемента.
PSRLW/PSRLD/PSRLQ
PSRLW приемник, источник
PSRLD приемник, источник
PSRLQприемник, источник
OFDl/r бб OF D1 /гOF 71/2 18 66 0F71/2 i8OFDl/r бб OF D1 /гOF 71/2 18 66 0F71/2 i8PSRLW rmmxl,rmmx2/m64
OFDl/r бб OF D1 /г
OF 71/2 18 66 0F71/2 i8
OFDl/r бб OF D1 /г
OF 71/2 18 66 0F71/2 i8
PSRLW rxmml,rxmm2/ml28
PSRLW rmmxl,i8
PSRLW rxmml,i8
OF D2/гPSRLD rmmxl,rmmx2/m64
66 OF D2 /гPSRLD rxmml,rxmm2/ml28
OF 72 /2 18PSRLD rmmxlj8
66 OF 72 /2 18PSRLD rxmml,imm8
OF D3/rPSRLQ rmmxl, rmmx2/m64
66OF D3 /rPSRLQ rxmml, rxmm2/ml28
OF 73 /2 i8PSRLQ rmmxl,i8
66 OF 73 /2 i8PSRLQ rxmml,i8
Логический сдвиг вправо упакованных слов (двойных слов, учетверенных слов).
Действие: команда производит сдвиг вправо битов элементов размером слово (двойное слово, учетверенное слово) приемника на количество разрядов, определяемое операндом источник. Одновременно слева в каждый элемент вдвигаются нулевые биты. Результат помещается в операнд приемник. Если значение, указанное в операнде источник, больше чем 15 (для слов), 31 (для двойных слов) или 63 (для учетверенных слов), то значение операнда приемник устанавливается равным 0.
PSUBB/PSUBW/PSUBD
PSUBB приемник, источник
PSUBW приемник, источник
PSUBD приемник, источник
0FF8/rPSUBB rmmxVmmx2/m64
66 OF F8 /r PSUBB rxmml, rxmm2/m!28
0FF9 /r . PSUBW rmmxl, rmmx2/m64
66 OF F9 /r PSUBW rxmml, rxmm2/ml28
OF FA /rPSUBD rmmxl, rmmx2/m64
66 OF FA /r PSUBD rxmml, rxmm2/ml28
Вычитание упакованных байтов (слов, двойных слов).
Действие: команда вычитает из элементов источника элементы приемника размером байт (слово, двойное слово). При переполнении результат формируется в соответствии с принципом циклического переполнения. Результат помещается в операнд приемник.
PSUBQ
PSUBQ приемник, источник
OF FB /г PSU8Q rmmxl,rmmx2/m64
66 OF FB /r PSUBQ rxmrnVxmm2/m 128
Вычитание учетверенных слов.
Действие: выполняется вычитание 64-разрядных целых значений в источнике и приемнике. Исходя из типа источника, возможны две схемы умножения:
Я если источник — ММХ-регистр или 64-разрядная ячейка памяти, то из разрядов 0...63 приемника вычитаются разряды 0...63 источника и помещаются в разряды 0...63 приемника (MMX- регистра);
綴 если источник — ХММ-регистр или 128-разрядная ячейка памяти, то из разрядов 0...63 приемника вычитаются разряды 0...63 источника и помещаются в разряды 0...63 приемника, а из разрядов 64...127 приемника вычитаются разряды 64...127 источника и помещаются в разряды 64...127.
В результате выполнения команды PSUBQ регистр E FLAGS не отражает факта возникновения ситуации переполнения или переноса. Когда результат умножения слишком большой, чтобы быть представленным в 64-разрядном элементе приемника, то он «заворачивается» (перенос игнорируется). Для обнаружения подобных ситуаций программное обеспечение долж-
но использовать другие методы.
PSUBSB/PSUBSW
PSUBSB приемник, источник
PSUBSW приемник, источник*
OF E8/rPSUBSB rmmxljmmx2/Tn64
66 OF E8 /r PSUBSBrxmml(rxmm2/ml28
OF E9 /rPSUBSW rmmxl,rmmx2/m64
66 OF E9/r PSUBSW rxmml, rxmm2/ml28
Вычитание со знаковым насыщением упакованных байтов (слов).
Действие: команда вычитает элементы источника и приемника размером байт (слово) в зависимости от кода операции. Вычитание элементов производится с учетом их знака. При переполнении результат формируется в соответствии с принципом знакового насыщения:
綴 PSUBSB —7fh для положительных чисел и 080h для отрицательных;
綾 PSUBSW — 07fffhдля положительных чисел и 08000h для отрицательных.
Результат помещается в операнд приемник.
PSUBUSB/PSUBUSW
PSUBUSB приемник, источник
PSUBUSW приемник, источник
OF D8/rPSUBUSB rmmxX rmmx2/m64.
660FD8 /『 PSUBUSB rxmml, rxmm2/ml28
OF D9 /r PSUBUSW rmmxl,rmmx2/m64
66 OF D9 /г PSUBUSW rxmml, rxmm2/ml28
Вычитание с беззнаковым насыщением упакованных байтов (слов).
Действие: команда вычитает без учета знака элементы операндов источник и приемник размером байт (слово) в зависимости от кода операции. При переполнении результат формируется в соответствии с принципом беззнакового насыщения:
舞 PSUBUSB — OOh для результатов вычитания меньших нуля;
扌 PSUBUSW — OOOOh для результатов вычитания меньших нуля.
Результат помещается в операнд приемник.
PUNPCKHBW/PUNPCKHWD/PUNPCKHDQ/PUNPCKHQDQ
PUNPCKHBW приемник, источник
PUNPCKHWD приемник, источник
PUNPCKHDQ приемник, источник
PUNPCKHQDQ приемник, источник
OF 68 " PUNPCKHBW rmmxl,rmmx2/m64
660F68 /г PUNPCKHBW rxmml, rxmm2/ml28
OF69/r PUNPCKHWD rmmxl,rmmx2/m64
660F69 /r PUNPCKHWDrxmml,rxmm2/ml28
OF 6A/rPUNPCKHDQ rmmxl/rmmx2/m64
66 OF 6A/r PUNPCKHDQ rxmml, rxmm2/ml28
66 OF6D/r PUNPCKHQDQ rxmml, rxmm2/ml28
Распаковка старших упакованных байтов (слов, двойных слов, учетверенных слов) в слова (двойные слова, учетверенные слова, сдвоенные учетверенные слова).
Действие: ММХ-команды PUNPCKHBW/PUNPCKHWD/PUNPCKHDQ производят размещение элементов из операндов источник и приемник согласно схем, представленных на рисунках (см. следующую страницу).
Результат помещается в операнд приемник. ХММ-комавды PUNPCKHBW/PUNPCKHWD/PUNPCKHDQ/ PUNPCKHQDQ производят размещение с чередованием элементов из операндов источник и приемник согласно представленной далее схеме:
источник
Источник
Приемник (ММХ-регистр)
С1
Приемник!
приемник, источник
ИсточниксИсточниксПриемник (ММ>Приемник (ММ>^ч^芷^ч^芷Приемник (ММХ-регистр) \Приемник (ММХ-регистр) \
Источник
с
Источник
с
Приемник (ММ>
Приемник (ММ>
^ч^芷
^ч^芷
Приемник (ММХ-регистр) \
Приемник (ММХ-регистр) \
приемник, источник
ИсточникПриемник (ММХ-регистр)
I I I I I I 1
Приемник (ММХ-регистр) С
PUNPCKHBW:
приемник[7„.О]—叩 *емкмк[71 …64];
приемник[71...64] — приемник[103...96];
П
приемник[79...72] ♦ источник[103...96];
D
П
приемник[31...24] ~ источник[79...72];
П
П
D
П
D
D
П
D
Писточник[127...120].
PUNPCKHWO:
П
П• -
Р
Р•
Р-
口 приемник[111...96] —приемник[127...И2];
□ приемник[63...48] —источник[95...80];
П приемник[127...112] j источник[127.„112].
PUNPCKHDQ:
D приемник[31...0] — приемник[95...64];
П приемнкк[95 …64]—叩 мемник[127 …96];
D приемник[63...32] «-источник[95...64];
D приемник[127...96] — источник[127...96].
緩 PUNPCKHQDQ:
П приемник[63...0] ♦ приемник[127.„64];
П приемник[127...64] —источник[ 127...64].
PUNPCKLBW/PUNPCKLWD/PUNPCKLDQ/PUNPCKLQDQ
PUNPCKLBW приемник, источник
PUNPCKLWD приемник, источник
PUNPCKLDQ приемник, источник
PUNPCKLQDQ приемник, источник
OF 60 /гPUNPCKLBWrmmxl,rmmx2/m64
0F61 /г PUNPCKLWD rmmxljmmx2/ni64
OF 62/r PUNPCKLDQ rmmxljmmx2/m64
66 OF 60 /г PUNPCKLBWrxmml/rxmm2/ml28
66 OF 61 /rPUNPCKLWD rxmml, rxmm2/ml28
66OF 62 /rPUNPCKLDQ rxmml, rxmm2/ml28
66 OF6C/rPUNPCKLQDQ rxmml, rxmm2/ml28
punpcklbw приемник, источник
ИсточникИсточникПриемник (ММХ-регистр)Приемник (ММХ-регистр)punpcklwdприемник, источникpunpcklwdприемник, источникИсточникГТТЗ ЧИсточникГТТЗ ЧПриемник (ММХ-рмистр)1 Г IПриемник (ММХ-рмистр)1 Г IПриемник (ММХ-регистр)IlliLПриемник (ММХ-регистр)IlliLpunpckldqприемник, источникpunpckldqприемник, источникИсточникИсточникПриемник ММХ-регистр)ГТТП LШ1Приемник ММХ-регистр)ГТТП LШ1Приемник (ММХ-регистр) |Приемник (ММХ-регистр) |045045
Источник
Источник
Приемник (ММХ-регистр)
Приемник (ММХ-регистр)
punpcklwdприемник, источник
punpcklwdприемник, источник
Источник
ГТТЗ Ч
Источник
ГТТЗ Ч
Приемник (ММХ-рмистр)1 Г I
Приемник (ММХ-рмистр)1 Г I
Приемник (ММХ-регистр)
IlliL
Приемник (ММХ-регистр)
IlliL
punpckldqприемник, источник
punpckldqприемник, источник
Источник
Источник
Приемник ММХ-регистр)
ГТТП LШ1
Приемник ММХ-регистр)
ГТТП LШ1
Приемник (ММХ-регистр) |
Приемник (ММХ-регистр) |
045
045
Распаковка младших упакованных байтов (слов, двойных слов, учетверенных слов) в слова (двойные слова, учетверенные слова, сдвоенные учетверенные слова).
Действие: MMX-команды PUNPCKLBW/PUNPCKLWD/PUNPCKLDQпроизводят размещение элемсн- тов из операндовисточник иприемниксогласносхемам, представленным нарисунках (см. предыдущую страницу).
ХММ-команды PUNPCKLBW/PUNPCKLWD/PUNPCKLDQ/PUNPCKLQDQ производят размещение с чередованием элементов из операндов источник и приемник согласно представленной далее схеме:
纖 PUNPCKLBW:
D приемник[7...О] — приемник[7...О];
□ прмемнмк[71 …64] <- приемнмк]39…32];
Р прмемнкк[15…8] 一 источник[7...0];
П приемник[79...72] <—источник[39...32];
П приемник[23…16] <— приемник[15...8];
П приемник[87...8О] 一 приемник[47...4О];
П приемник[31...24] — источник[15...8];
D приемник[95...88] — источник[47...40];
D приемник[39...32] —叩 мемимк[23 …16];
D приемник[103.„96] — прмемнмк[55 …48];
О приемник[47...4О] — источник[23...16];
D приемник[111...1О4] <— источник[55...48];
Р приемник[55...48] —приемник[31,..24];
О приемник[119...И2] 一 прмемннк[63…56];
D приемник[63...5б]一 источник[31...24];
Р 叩 иемник[127 …120]— источник[63...56].
» PUNPCKLWD:
Р приемник[15...0]叩川 емнмк[15 …0];
П приемник[79...64] — приемник[47..,32];
Р приемник[31...16] — источник[15.„0];
Р приемник[95...80] —источник[47...32];.
Р приемнмк[47…32] <— приемник[31 …16];
П приемник[111...96] — приемник[63...48];
Р прмемнмк[63…48] 一 источник[31...16];
Р приемник[127...И2]— источник[63...48].
綫 PUNPCKLDQ:
Р приемник[31...0] —лриемник[31...О];
Р прмемник[95…64] 一 прмемннк[63…32];*
Р 叩мемнмк[63...32] j источник[31..,0];
Р приемник[127...96] — источник[63...32].
慧 PUNPCKLQDQ:: '
Р приемник[63...0] —приемник[63...О];
Р приемник[127...64] <— источник[63...0].
PXOR
PXOR приемник, источник
OF EF/rPXOR rmmxl,rmmx2/m64
66 OF EF /г PXOR rxmml/xmmZ/mlZS
Упакованное логическое исключающее ИЛИ.
Действие: команда производит поразрядную операцию логического исключающего ИЛИ над всеми битами операндов источник и приемник. Результат помещается в операнд приемник.
Команды блока ХММ (SSE и SSE2)
ADDPD
ADDPD приемник, источник
66 OF 58 /rADDPD rxmml, rxmm2/ml28
Сложение упакованных значений с плавающей точкой двойной точности.
Действие: пары упакованных значений с плавающей точкой двойной точности источника и приемника складываются (аналогично команде ADDPS), результат сложения сохраняется в соответствующих упакованных значениях с плавающей точкой двойной точностиприемника.
ADDPS
ADDPS приемник, источник
OF 58 /rADDPS rxmmlzrxmm2/ml28
Сложение упакованных значений в формате ХММ.
Действие: алгоритм работы команды показан на следующей схеме.
источник [
rxmm/m128
Приемникrmmx
cvttpd2pi приемник, источник i
Приемникrmmx
ADDSD
ADDSD приемник, источник
F2 OF 58/rADDSD rxmml, fxmm2/m64
Сложение скалярных упакованных значений с
Вещественные
иО
I п1 」旧
Отбр^итъ дробнуючасть 3 (качения
V
匚и1 」dUVЦелые
плавающей точкой двойной точности.
Действие: младшие упакованные значения с плавающей точкой двойной точности источника и приемника складываются (аналогично команде ADDSS), результат сложения сохраняется в млад- шемупакованнымзначениисплавающейточкойдвойнойточностиприемника.
ADDSS
• ADDSS приемник, источник
■ F3 OF 58 /г ADDSS rxmml, rxmm2/m32
■ Скалярное сложение значений в формате ХММ.
Действие: алгоритм работы команды показан на следующей схеме:
Вещественные
[и1 _IиО11764 63|0IП寸]пОI127[64 63|-[Преобрйэрвание float->int;<|00000000I 00000000] и1 IиОJ[и1 _IиО11764 63|0IП寸]пОI127[64 63|-[Преобрйэрвание float->int;<|00000000I 00000000] и1 IиОJИсточник rxmm/m128Приемник шптcvtpd2dq приемник, источникИсточник rxmm/m128Приемник шптcvtpd2dq приемник, источникПриемник rxmmПриемник rxmm12712764 63rx31^0Целые64 63rx31^0Целые
[и1 _IиО
11764 63
|0
IП寸]пО
I
127[64 63|
-
[Преобрйэрвание float
->int;
<
|00000000I 00000000] и1 I
иОJ
[и1 _IиО
11764 63
|0
IП寸]пО
I
127[64 63|
-
[Преобрйэрвание float
->int;
<
|00000000I 00000000] и1 I
иОJ
Источник rxmm/m128
Приемник шпт
cvtpd2dq приемник, источник
Источник rxmm/m128
Приемник шпт
cvtpd2dq приемник, источник
Приемник rxmm
Приемник rxmm
127
127
64 63rx31^0
Целые
64 63rx31^0
Целые
ANDPD
ANDPD приемник, источник
66 OF 54 /г ANDPD xmml, xmm2/ml28
Поразрядноелогическое И надуиаковапнылги значениями с плавающей точкой двойной точ
ности.
Действие: команда выполняет операцию поразрядного логического И над двумя упакованными значениями с плавающей точкой двойной точности. Результат помещается в приемник.
ANDPS
ANDPS приемник, источник
0F 54 /гANDPS rxmml,rxmm2/ml28
Поразрядное логическое И над каждой парой битов операндов.
ANDNPD
ANDNPO приемник, источник
66 OF 55 /г ANDNPD xmml, xmm2/ml28
Поразрядное логическое И-НЕ надупакованными значениями с плавающей точкой двойной точности.
Действие: команда выполняет операцию поразрядного логического И-НЕ над парами упакованных значений с плавающей точкой двойной точности в приемнике и источнике. Результат помещается в приемник.
ANDNPS
ANDNPS приемник, источник
OF 55/гANDNPS rxmml,rxmm2/ml28
Поразрядное логическое И-НЕ над упакованными значениями в формате ХММ.
Действие: команда инвертирует биты операнда приемник и после инвертирования над каждой парой битов операндов приемник и источник выполняет операцию логического И.
CLFLUSH
CLFLUSH адрес_байта
OFAE/7CLFLUSH m8
Сброс на диск строки кэша, содержащую адрес байта.
Действие: команда объявляет недействительной строку кэша, которая содержит линейный адрес байта, указанный операндом, на всех уровнях иерархии кэшей данных и команд процессора. Если на одном из уровней иерархии кэшей строка противоречит памяти (была изменена), то перед тем как быть объявленной недействительной, она записывается в память.
Возможность использования команды CLFLUSH на том или ином процессоре выясняется с помощью команды CPUID. Выровненный размер строки кэша, н a который воздействует CLFLUSH, также определяется командой CPUID.,
CMPPD
CMPPD приемник, источник, условие
66OFC2 /г i8 CMPPD xmml, xmm2/ml28,imm8
• Сравнение упакованных значений с плавающей точкой двойной точности.
Действие: команда сравнивает упакованные значения с плавающей точкой двойной точности в приемнике и источнике. Результат сравнения для каждой пары упакованных чисел представляется в виде маски: единичная маска fffffffffffffff — значения чисел равны, нулевая маска OOOOOOOOOOOOOOOOh — значения не равны. Условие сравнения задается непосредственным операндом условие, первые 3 бита которого определяют тип сравнения. Остальные биты зарезервированы. Соответствие значений операнда условие условию сравнения:
修0 — приемник - источник;
舞 1 — приемник < источник;
觴 2 — приемник < источник;
齡 3 — приемник и/или источник — нечисло (NaN) или имеет неопределенный формат;
懲 4 — приемник ? источник;
粉 51(приемник < источник);
鬱6——,(приемник< источник);
幾7 — упакованные значения приемника и источника — правильные значения с плавающей точкой двойной точности.
Для проверки остальных условий необходимо вначалепоменятьсодержимоеприемника и источника, а затем использовать команду СМРРО со следующими значениями операнда условие:
— приемник > источник;
後 2 — приемник > источник;
綬 51(приемник > источник);
樂6——|(приемник> источник).
CMPPS
CMPPS приемник, источник, условие
OF C2 /г ib CMPPS rxmml, rxmm2/ml28, i8
Сравнение упакованных значений в формате ХММ.
Действие: условие, в соответствии с которым производится сравнение каждой пары элементов операндов приемник и источник, задается явно в виде непосредственного операнда (табл. П.23). В результате сравнения в приемнике формируются единичные (если условие выполнено) или нулевые элементы (если условие не выполнено).
CMPSD
CMPSD приемник, источник, условие
F2 OF C2 /r 18 CMPSD xrnml, xmm2/m64, imm8
Сравнение скалярных значений с плавающей точкой двойной точности.
Действие: команда сравнивает упакованные значения с плавающей точкой двойной точности в разрядах [0...63] приемника и источника. Формирование проверяемого условия и результата выполнения команды те же, что и для команды CMPPD.
Таблица П.23. Допустимые значения условия в команде CMPPS
Условие
Описание условия
Отношение
Эмуляция
Код маски 18
Результат, если операнд не число(NaN)
Исключение #1, если опе- рандQNaN/ SNaN
Eq
Равно
xmml =xmm2
000b
False
Нет
Lt
Меньше чем
xmml < xmm2
001b
False
Да
Le
Меньше чем или равно
xmml < xmm2
010b
False
Да
Gt
Больше чем
xmml > xmm2
Перестановка с сохранением, It1
False
Да
Ge
Больше чем или равно
xmml>xmm2
Перестановка с сохранением, 1е
False
Да
Unord
Одноиз чисел - NaN
xmml ? xmm2
011b
True
Нет
Neq
Не равно
xmml *xmm2
100b
True
Нет
NJt
Не меньше чем
!(xmml < xmm2)
101b
True
Да
Nle
Не меньше чем илиравно
!(xmml<xmm2)
110b
True
Да
Ngt
Не больше чем
!(xmml > xmm2)
Перестановка с сохранением, nit1
True
Да
Nge
Не больше чем или равно
!(xmml>xmm2)
Перестановка с сохранением, nle 1
True
Да
Ord
Оба числа не NaN
xmml ? xmm2
111b
False
Нет
CMPSS
CMPSS приемник, источник, условие
F3 OF C2 /r ib CMPSS rxmmX rxmm2/m32,18
Скалярное сравнение значений в форматеXMM.
Действие: для пары значений операндов приемник и источник выполняется сравнение, в результате которого формируются единичные (если условие выполнено) или нулевые элементы (если условие не выполнено). Значение источника может быть расположено в 32-разрядной ячейке памяти или в младшем двойном слове ХММ-регистра. Значение приемника должно быть расположено в младшем двойном слове другого ХММ-регистра. Возможные значения условий приведены в описании команды CMPPS (см. табл. П.23).
COMISD
COMISD приемник, источник, условие
66 OF 2F/r COMISD xmml, xmm2/m64
Сравнение упорядоченных скалярных значений с плавающей точкой двойной точности и установка регистра E FLAGS.
Действие: упорядоченные скалярные значения с плавающей точкой двойной точности сравниваются в разрядах [0...63] приемника и источника. По результату сравнения устанавливаются флаги ZF, PF и CF в регистре EFLAGS:
приемник > источник (ZF = О, PF = 0, CF H 0);
総 приемник < источник (ZF = О, PF = О, CF 1);
приемник - источник (ZF = 1, PF - О, CF = 0);
添* приемник и/или источник — нечисло (NaN) или имеют неопределенный формат (ZF ss 1, PF = 1, CF-1).
Флаги OF, SF и AF устанавливаются в 0. В случае генерации немаскированного исключения с плавающей точкой регистр EFLAGS не модифицируется.
COMISS
COMISS приемник, источник
0F2F/rCOMISS rxmml, rxmm2/m32
Скалярное упорядоченное сравнение значений в формате ХММ с установкой регистра EFLAGS.
Действие: команда сравнивает пару значений операндов приемник и источник, в результате устанавливаются флаги в регистре EFLAGS (табл. П.24). Значение источника может быть расположено в 32-разрядной ячейке памяти или младшем двойном слове ХММ-регистра. Значение приемника должно быть расположено в младшем двойном слове другого ХММ-регистра.
При возникновении незамаскированных исключений значение EFLAGS не изменяется.ЦелыеПри возникновении незамаскированных исключений значение EFLAGS не изменяется.ЦелыеИсточник rxmm/m64Приемник rxmEcvtdq2pd приемник, источникПриемник толти1иО12764 63 I! 32 3101п1I пО11jПреобразрвёние int->flo£tVVи11иО1ВещественныеИсточник rxmm/m64Приемник rxmEcvtdq2pd приемник, источникПриемник толти1иО12764 63 I! 32 3101п1I пО11jПреобразрвёние int->flo£tVVи11иО1ВещественныеТаблица П.24. Допустимые значения флагов в регистре EFLAGS
При возникновении незамаскированных исключений значение EFLAGS не изменяется.
Целые
При возникновении незамаскированных исключений значение EFLAGS не изменяется.
Целые
Источник rxmm/m64
Приемник rxmE
cvtdq2pd приемник, источник
Приемник толт
и1иО
12764 63 I! 32 310
1п1I пО11
jПреобразрвёние int->flo£t
VV
и11иО1
Вещественные
Источник rxmm/m64
Приемник rxmE
cvtdq2pd приемник, источник
Приемник толт
и1иО
12764 63 I! 32 310
1п1I пО11
jПреобразрвёние int->flo£t
VV
и11иО1
Вещественные
Соотношение операндов
Значение флагов
Приемник > источник
OF - SF = AF = ZF - PF - CF - 0
Приемник < источник
OF - SF - AF = ZF - PF = 0; CF - 1
Приемник = источник
OF - SF - AF - PF - CF - 0; ZF = 1
Приемник или источник = QNaN или SNaN
OF = SF - AF = 0; ZF - PF = CF = 1
CVTDQ2PD
CVTDQ2PD приемник, источник
F3 OF Еб CVTDQ2PD rxmnil, rxmm2/m64
Преобразование двух упакованных 32-разрядных целых в два упакованные значения спла- вающей точкой двойной точности.
Действие: алгоритм работы команды показан на схеме (см. предыдущую страницу).
CVTDQ2PS
CVTDQ2PS приемник, источник
OF5B/r CVTDQ2PS гхтт1лхгпт2/т128
Преобразование четырех упакованных 32-разрядных целых со знаком в четыреупакованных значения с плавающей точкой одинарной точности.
Действие: алгоритм работы команды показан на следующей схеме.
Вещественные(32 бита)
В случае, когда не удается выполнить точное преобразование, значение округляется в соответствии с полем MXCSR.RC.
CVTPD2DQ
CVTPD2DQ приемник, источник
F2 OF E6 CVTPD2DQrxmml, rxmm2/ml28
Преобразование двух упакованныхзначений с плавающей точкой двойной точности в два упакованных 32-разрядных целых.
источник гхтт/т128Приемник rxmmcvtpd2dq приемник, источникПриемник rxmmисточник гхтт/т128Приемник rxmmcvtpd2dq приемник, источникПриемник rxmmВещественныеВещественныеиОиО117|64 630IП1|пО12764 630'Преобразование floatpint117|64 630IП1|пО12764 630'Преобразование floatpint[сюсюоосю | сюоо(ю(ю[ М I Zq |[сюсюоосю | сюоо(ю(ю[ М I Zq |127127
источник гхтт/т128
Приемник rxmm
cvtpd2dq приемник, источник
Приемник rxmm
источник гхтт/т128
Приемник rxmm
cvtpd2dq приемник, источник
Приемник rxmm
Вещественные
Вещественные
иО
иО
117|
64 63
0
IП1
|пО
127
64 63
0
'Преобразование float
pint
117|
64 63
0
IП1
|пО
127
64 63
0
'Преобразование float
pint
[сюсюоосю | сюоо(ю(ю[ М I Zq |
[сюсюоосю | сюоо(ю(ю[ М I Zq |
127
127
Действие: алгоритм работы команды показан на следующей схеме.
В случае, когда не удается выполнить точное преобразование, значение округляется в соответствии с полем MXCSR.RC. Если преобразованный результат больше чем максимально возможное целочисленное 32-разрядное значение, то возвращается значение 80000000b.
CVTPD2P1
CVTPD2PI приемник, источник
66 OF 2D /г CVTPD2PI rmmx, rxmm/ml28
Преобразование двух упакованных значений с плавающей точкой двойной точности в два упакованных 32-разрядных целых.
Действие: алгоритм работы команды показан на следующей схеме.
ВещественныеВещественныеЦелыеЦелые
Вещественные
Вещественные
Целые
Целые
источник rxmm/m128Приемник rxmmcvtpd2ps приемник, источникисточник rxmm/m128Приемник rxmmcvtpd2ps приемник, источникПриемник rxmmПриемник rxmm
источник rxmm/m128
Приемник rxmm
cvtpd2ps приемник, источник
источник rxmm/m128
Приемник rxmm
cvtpd2ps приемник, источник
Приемник rxmm
Приемник rxmm
В случае, когда не удается выполнить точное преобразование, значение округляется в соответствии с полем MXCSR.RC. Если преобразованный результат больше, чем максимально возможное целочисленное 32-разрядное значение, то возвращается значение 80000000b.
CVTPD2PS
CVTPD2PS приемник, источник
66 OF 5А/г CVTPD2PS rxmml, rxmm2/ml28
Преобразование двух упакованных значений с плавающей точкой двойной точности в два упакованных значения с плавающей точкой одинарной точности.
Действие: алгоритм работы команды показан на следующей схеме.
Вещественные (64 бита)
jи1IиО I
127| 64 63I 0
[ml|nO J I
127I 64 63I 0
4
I Преобразование float64->fldat32;
y-T-一,
I —0000000000000000 | и1 ! иО
12764 6 3^32 3lJ^о
В случае, когда не удается выполнить точное преобразование, значение округляется в соответствии с полем MXCSR.RC
CVTPI2PD
CVTPI2PD приемник, источник
бб OF 2А/гCVTPI2PD rxmm, rmmx/m64
Преобразование двух упакованных 32-разрядных целых в два упакованных значения с плавающей точкой двойной точности.
Действие: алгоритм работы команды показан на следующей схеме.
источник rmmx/m64Приемник rxmmcvtpi2pd приемник, источникПриемник rxmmисточник rmmx/m64Приемник rxmmcvtpi2pd приемник, источникПриемник rxmmIIЦелыеЦелые127127II 」 064 63J0J1i;Преобразование int-疝oa&II 」 064 63J0J1i;Преобразование int-疝oa&127127и1иОI64 63и1иОI64 63ВещественныеВещественные
источник rmmx/m64
Приемник rxmm
cvtpi2pd приемник, источник
Приемник rxmm
источник rmmx/m64
Приемник rxmm
cvtpi2pd приемник, источник
Приемник rxmm
I
I
Целые
Целые
127
127
II 」 0
64 63J0
J1i
;Преобразование int-疝oa&
II 」 0
64 63J0
J1i
;Преобразование int-疝oa&
127
127
и1иОI
64 63
и1иОI
64 63
Вещественные
Вещественные
CVTPI2PS
CVTPI2PS приемник, источник
OF 2A /rCVTPI2PS rxmm,rnimx/m64
Преобразование двух упакованных 32-разрядных целых в два упакованных вещественных значения.
ЦелыехчЦелыехчДействие: алгоритм работы команды показан на следующей схеме.
Целыехч
Целыехч
I И,1 I ИО IСЗП] п2 I ! [! IЛреобразов^ие; irtt->floatl i1 пЗ U п2 I и1 I иО II И,1 I ИО IСЗП] п2 I ! [! IЛреобразов^ие; irtt->floatl i1 пЗ U п2 I и1 I иО IИсточникrmmx/m64Приемникrxmm
I И,1 I ИО I
СЗП] п2 I ! [! I
Лреобразов^ие; irtt->floatl i
1 пЗ U п2 I и1 I иО I
I И,1 I ИО I
СЗП] п2 I ! [! I
Лреобразов^ие; irtt->floatl i
1 пЗ U п2 I и1 I иО I
cvtpi2ps приемник, источник
Приемник rxmm
Вещественные
В случае, когда не удается выполнить точное преобразование, результат округляется в соответствии с полем MXCSR.RC.
CVTPS2DQ
• CVTPS2DQ приемник, источник
• 66OF5B/rCVTPS2DQrxmml, rxmm2/ml28
Преобразование четырех упакованных значений с плавающей точкой одинарной точности в четыре упакованных 32-разрядных целых со знаком.
Действие: алгоритм работы команды показан на следующей схеме.
Вещественные (32 бита)Вещественные (32 бита)Источник rxmm/m128Приемник rxmmcvtps2dq приемник, источникИсточник rxmm/m128Приемник rxmmcvtps2dq приемник, источникм3 И2 I и1 I иОм3 И2 I и1 I иО127 j 95 i63|31 i 0iII127 j 95 i63|31 i 0iIII127!I二63|оI127!I二63|оПриемник rxmmПриемник rxmm
Вещественные (32 бита)
Вещественные (32 бита)
Источник rxmm/m128
Приемник rxmm
cvtps2dq приемник, источник
Источник rxmm/m128
Приемник rxmm
cvtps2dq приемник, источник
м3 И2 I и1 I иО
м3 И2 I и1 I иО
127 j 95 i63|31 i 0
iII
127 j 95 i63|31 i 0
iII
I127!I二63|о
I127!I二63|о
Приемник rxmm
Приемник rxmm
Целые со знаком
В случае, когда не удастся выполнить точное преобразование, значение округляется в соответствии с полем MXCSR.RC. Если преобразованный результат больше, чем максимально возможное целочисленное 32-разрядное значение, то возвращается значение 8000000011.
CVTPS2PD
CVTPS2PD приемник, источник
0F5A/r CVTPS2PD『xmmi, rxmm2/m64
Преобразование двухупакованных значений с плавающей точкой одинарной точности в два упакованных значения с плавающей точкой двойной точности.
Действие: алгоритм работы команды показан на следующей схеме.
Вещественные (32 бита)Вещественные (32 бита)Источник rxmm/m64Приемник rxmmcvtps2pd приемник, источникПриемник rxmmИсточник rxmm/m64Приемник rxmmcvtps2pd приемник, источникПриемник rxmm64 6364 6332 3132 3100127127п2127п2127г1г163ГNIПреобразованиеfloat32->flcjat64|」……一二产1—-163ГNIПреобразованиеfloat32->flcjat64|」……一二产1—-16464оои1 _ IиО12764 63и1 _ IиО12764 63Вещественные (64 бита)Вещественные (64 бита)
Вещественные (32 бита)
Вещественные (32 бита)
Источник rxmm/m64
Приемник rxmm
cvtps2pd приемник, источник
Приемник rxmm
Источник rxmm/m64
Приемник rxmm
cvtps2pd приемник, источник
Приемник rxmm
64 63
64 63
32 31
32 31
0
0
127
127
п2
127
п2
127
г1
г1
63
ГN
IПреобразованиеfloat32->flcjat64|
」……一二产1—-1
63
ГN
IПреобразованиеfloat32->flcjat64|
」……一二产1—-1
64
64
о
о
и1 _ IиО
12764 63
и1 _ IиО
12764 63
Вещественные (64 бита)
Вещественные (64 бита)
CVTPS2PI
CVTPS2PI приемник, источник
■ 0F2D/CCVTPS2PI rmmx, rxmm/m!28
Преобразование двухвещественных целых в два упакованных 32-разрядных целых.
Действие: алгоритм работы команды показан на схеме (см. следующую страницу),
Если преобразованный результат больше, чем максимально возможное целочисленное 32-разрядное значение, то возвращается значение 80000000b. В случае, когда не удается выполнить точное преобразование, значение округляется в соответствии с полем MXCSR.RC.
ВещественныеВещественныеИсточникrxmm/m128 I~ЙЗ
Вещественные
Вещественные
Приемник
rmmx
cvtps2piприемник, источник
Приемник rmmx
Целые
CVTSD2SI
CVTSD2SI приемник, источник
F2 OF 2D /гCVTSD2SI r32, rxmm/m64
Преобразование скалярного значения с плавающей точкой двойной точности в 32-разрядное целое.
Вещественные (64 бита)Вещественные (64 бита)Источник rxmm/m64Приемник г32Источник rxmm/m64Приемник г32127127cvtsd2siприемник, источникcvtsd2siприемник, источникПриемникr32Приемникr32и1и1пО31! ПреобразоЕание!! float64->irit32 j!i匚иО о31 тВещественное(32 бита)пО31! ПреобразоЕание!! float64->irit32 j!i匚иО о31 тВещественное(32 бита)В случае, когда не удается выполнить точное преобразование, значение округляется в соответствии с полем MXCSR.RC. Если преобразованный результат больше, чем максимально возможное целочисленное 32-разрядное значение, то возвращается значение 80000000h.В случае, когда не удается выполнить точное преобразование, значение округляется в соответствии с полем MXCSR.RC. Если преобразованный результат больше, чем максимально возможное целочисленное 32-разрядное значение, то возвращается значение 80000000h.Вещественные (64 бита)Вещественные (64 бита)Источник rxmm/m64Приемник rxmmcvtsd2ss приемник, источникИсточник rxmm/m64Приемник rxmmcvtsd2ss приемник, источник127630127630п1ПО j Iп1ПО j IПриемник rxmmПриемник rxmm127630! Преобразований■ _fl<>at64->flaiat32 JI 丨 IиО二产 v戸不rНе изменяются Вещественное (32 бита)127630! Преобразований■ _fl<>at64->flaiat32 JI 丨 IиО二产 v戸不rНе изменяются Вещественное (32 бита)Действие: алгоритм работы команды показан на следующей схеме.
Вещественные (64 бита)
Вещественные (64 бита)
Источник rxmm/m64
Приемник г32
Источник rxmm/m64
Приемник г32
127
127
cvtsd2siприемник, источник
cvtsd2siприемник, источник
Приемник
r32
Приемник
r32
и1
и1
пО
31
! ПреобразоЕание!
! float64->irit32 j
!i
匚иО о
31 т
Вещественное(32 бита)
пО
31
! ПреобразоЕание!
! float64->irit32 j
!i
匚иО о
31 т
Вещественное(32 бита)
В случае, когда не удается выполнить точное преобразование, значение округляется в соответствии с полем MXCSR.RC. Если преобразованный результат больше, чем максимально возможное целочисленное 32-разрядное значение, то возвращается значение 80000000h.
В случае, когда не удается выполнить точное преобразование, значение округляется в соответствии с полем MXCSR.RC. Если преобразованный результат больше, чем максимально возможное целочисленное 32-разрядное значение, то возвращается значение 80000000h.
Вещественные (64 бита)
Вещественные (64 бита)
Источник rxmm/m64
Приемник rxmm
cvtsd2ss приемник, источник
Источник rxmm/m64
Приемник rxmm
cvtsd2ss приемник, источник
127630
127630
п1ПО j I
п1ПО j I
Приемник rxmm
Приемник rxmm
127630
! Преобразований
■ _fl<>at64->flaiat32 J
I 丨 IиО二
产 v戸不r
Не изменяются Вещественное (32 бита)
127630
! Преобразований
■ _fl<>at64->flaiat32 J
I 丨 IиО二
产 v戸不r
Не изменяются Вещественное (32 бита)
CVTSD2SS
CVTSD2SS приемник, источник
F2 OF5A/rCVTSD2SS rxmml, rxmm2/m64
Преобразование скалярного значения с плавающей точкой двойной точности в скалярное значение с плавающей точкой одинарной точности.
Действие: алгоритм работы команды показан на схеме (см. предыдущую страицу).
В случае, когда не удается выполнить точное преобразование, значение округляется в соответствии с полем MXCSR.RC.
CVTSI2SD
CVTSI2SD приемник, источник
F2 OF 2A/гCVTSI2SD rxmm, r/m32
Преобразование 32-разрядного целого значения со знаком в упакованное значение с плавающей точкой двойной точности.
Действие: алгоритм работы команды показан на следующей схеме.
Целое со знакомЦелое со знакомИсточник Г32/ГП32Приемник rxmmcvtsi2sd приемник источникИсточник Г32/ГП32Приемник rxmmcvtsi2sd приемник источник310310ПриемникrxmmПриемникrxmmn1 JпО X]12763J 0! Преобразованиеi int32->flo^t64_!口1 I И1 I12763J 0Не изменяетсяВещественное(64 бита)n1 JпО X]12763J 0! Преобразованиеi int32->flo^t64_!口1 I И1 I12763J 0Не изменяетсяВещественное(64 бита)
Целое со знаком
Целое со знаком
Источник Г32/ГП32
Приемник rxmm
cvtsi2sd приемник источник
Источник Г32/ГП32
Приемник rxmm
cvtsi2sd приемник источник
310
310
Приемник
rxmm
Приемник
rxmm
n1 JпО X]
12763J 0
! Преобразование
i int32->flo^t64_!
口1 I И1 I
12763J 0
Не изменяетсяВещественное
(64 бита)
n1 JпО X]
12763J 0
! Преобразование
i int32->flo^t64_!
口1 I И1 I
12763J 0
Не изменяетсяВещественное
(64 бита)
CVTSI2SS
CVTSI2SS приемник, источник
• F3 OF 2А/г• F3 OF 2А/гCVTSI2SS rxmm, r/m32CVTSI2SS rxmm, r/m32
• F3 OF 2А/г
• F3 OF 2А/г
CVTSI2SS rxmm, r/m32
CVTSI2SS rxmm, r/m32
Скалярное преобразование знакового 32-разрядного целого в вещественное значение. Действие: алгоритм работы команды показан на следующей схеме.
Источник
Г32/П132Приемникrxmm
cvtsi2ss приемник, источник
Приемник
В случае, когда не удается выполнить точное преобразование, значение округляется в соответствии с полем MXCSR.RC.
CVTSS2SD
CVTSS2SD приемник, источник
F3 OF 5А /гCVTSS2SD rxmml,rxmm2/m32
Преобразование скалярного значения с плавающей точкой одинарной точности в скалярное значение с плавающей точкой двойной точности.
Действие: алгоритм работы команды показан на следующей схеме.
источник rxmm/m32Приемник rxmmcvtss2sd приемник, источникисточник rxmm/m32Приемник rxmmcvtss2sd приемник, источникПриемникrxmmПриемникrxmm
источник rxmm/m32
Приемник rxmm
cvtss2sd приемник, источник
источник rxmm/m32
Приемник rxmm
cvtss2sd приемник, источник
Приемник
rxmm
Приемник
rxmm
ВещественноеВещественноеПреобразование: fldat->int ;Преобразование: fldat->int ;ЦелоеЦелое
Вещественное
Вещественное
Преобразование: fldat->int ;
Преобразование: fldat->int ;
Целое
Целое
CVTSS2SI
CVTSS2SI приемник, источник
F3,OF,2D,/rCVTSS2SI r32,rxmm/m32
Скалярное преобразование вещественного целого в 32-разрядное знаковое целое.
Действие: значение источника хранится в младшем двойном слове ХММ-регистра или в 32-раз- рядной ячейке памяти. Приемник — один из 32-разрядных регистров. Алгоритм работы команды показан на следующей схеме.
Источникrxmm/m1281 ПЗ I п2 I
Приемник г32
cvtss2si приемник, источник
Приемник г32
Если преобразованный результат больше, чем максимально возможное целочисленное 32-разрядное значение, то возвращается значение 80000000h. В случае, когда не удается выполнить точное преобразование, значение округляется в соответствии с полем MXCSR.RC.
CVTTPD2PI
CVTTPD2PI приемник, источник
66 OF 2C /r CVTTPD2PI mm, xmm/ml28
Преобразование (путем отбрасывания дробной части) двух упакованных значений с плавающей точкой двойной точности в два упакованных 32-разрядных целых значения.
Действие: алгоритм работы команды показанДействие: алгоритм работы команды показанИсточник rxmm/m128Приемник rmmxприемник, источникИсточник rxmm/m128Приемник rmmxприемник, источникна следующей схеме.на следующей схеме.Отбросить дробную ! !часть|Отбросить дробную ! !часть|Приемник rmmxПриемник rmmxI и1 I ио II и1 I ио IЦелыеЦелые
Действие: алгоритм работы команды показан
Действие: алгоритм работы команды показан
Источник rxmm/m128
Приемник rmmx
приемник, источник
Источник rxmm/m128
Приемник rmmx
приемник, источник
на следующей схеме.
на следующей схеме.
Отбросить дробную ! !часть|
Отбросить дробную ! !часть|
Приемник rmmx
Приемник rmmx
I и1 I ио I
I и1 I ио I
Целые
Целые
Если преобразованный результат больше, чем максимально возможное целочисленное 32раз- рядное значение, то будет возвращено значение
CVTTPD2DQ
CVTTPD2DQ приемник, источник
•
Преобразование усечением двух упакованных значений с плавающей точкой двойной точности в два упакованных 32-разрядных
Действие: алгоритм работы команды показан на следующей схеме.
Источникrxmm/m128Источникrxmm/m128Приемник rxmmПриемник rxmm12712700п1127п1127ПО64 63ПО64 6300
Источник
rxmm/m128
Источник
rxmm/m128
Приемник rxmm
Приемник rxmm
127
127
0
0
п1
127
п1
127
ПО
64 63
ПО
64 63
0
0
cvttpd2dq приемник, источник Отбросить'
-! m1l T иО I-! m1l T иО I'
-
! m1l T иО I
-
! m1l T иО I
Приемник
rxmm
127(
Целые
В случае, когда не удается выполнить точное преобразование, округляется в сторону нуля. Если преобразованный результат больше, чем максимально возможное целочисленное 32-разрядное значение, то возвращается значение
CVTTPS2DQ
CVTTPS2DQ приемник, источник
Преобразование (путем отбрасывания дробной части) четырех упакованных значений с плавающей точкой одинарной точности в четыре упакованных 32-разрядных целых со
знаком.
Действие: алгоритм работы команды
Источник rxmm/m128
Приемник rxmm
cvttps2dq приемник, испкнник
Приемник rxmm
показан на следующей схеме.показан на следующей схеме.Вещественные (32 бита)Вещественные (32 бита)Целые со знаком
показан на следующей схеме.
показан на следующей схеме.
Вещественные (32 бита)
Вещественные (32 бита)
В случае, когда не удается выполнить точное преобразование, значение округляется в сторону нуля. Если преобразованный результат больше, чем максимально возможное целочисленное- 32-разрядное значение, то будет возвращено значение 80000000b.
CVTTPS2PI
CVTTPS2PI приемник, источник
0F2C/rCVTTPS2PI rrnmx, rxmm/m64
Преобразование (путем отбрасывания дробной части) двух вещественных целых в два упакованных 32-разрядпых целых значения.
Источник rxmm/m128 I иЗПриемник rmmxcvttps2pi приемник, источникИсточник rxmm/m128 I иЗПриемник rmmxcvttps2pi приемник, источникIIВещественныеВещественные"Й2~I n彳 「二I。I"Й2~I n彳 「二I。IПриемник rmmxПриемник rmmx1 而 】_: Go:бтбросйть дробную; часта значе!|ия ч ;w I и1 IЦелые1 而 】_: Go:бтбросйть дробную; часта значе!|ия ч ;w I и1 IЦелыеДействие: алгоритм работы команды показан на следующей схеме.
Источник rxmm/m128 I иЗ
Приемник rmmx
cvttps2pi приемник, источник
Источник rxmm/m128 I иЗ
Приемник rmmx
cvttps2pi приемник, источник
I
I
Вещественные
Вещественные
"Й2~I n彳 「二I。I
"Й2~I n彳 「二I。I
Приемник rmmx
Приемник rmmx
1 而 】_: Go
:бтбросйть дробную; часта значе!|ия ч ;
w I и1 I
Целые
1 而 】_: Go
:бтбросйть дробную; часта значе!|ия ч ;
w I и1 I
Целые
Если преобразованный результат больше, чем максимально возможное целочисленное 32-разрядное значение, то будет возвращено значение 80000000b.
CVTTSD2SI
CVTTSD2SI приемник, источник
F2 OF 2C /r CVTTSD2SI r32, rxmm/m64
Преобразование (путем отбрасывания дробной части) скалярного значения с плавающей точкой двойной точности в 32-разрядное целое.
Действие: алгоритм работы команды показан на схеме (см. следующую страницу).
В случае, когда не удается выполнить точное преобразование, значение округляется в сторону нуля. Если преобразованный результат больше, чем максимально возможное целочисленное- 32-разрядное значение, то будет возвращено значение 80000000h.
Вещественные (64 бита)Вещественные (64 бита)источникrxmm/m64Приемник r32источникrxmm/m64Приемник r32Г127Г127cvttsd2si приемник, источникcvttsd2si приемник, источникп131: a| Отброситьдробную;п131: a| Отброситьдробную;Приемникr32Приемникr32
Вещественные (64 бита)
Вещественные (64 бита)
источник
rxmm/m64
Приемник r32
источник
rxmm/m64
Приемник r32
Г
127
Г
127
cvttsd2si приемник, источник
cvttsd2si приемник, источник
п1
31: a
| Отброситьдробную;
п1
31: a
| Отброситьдробную;
Приемник
r32
Приемник
r32
Целое
CVTTSS2SI
CVTTSS2SI приемник, источник
F3 OF 2C /гCVTTSS2SI r32, rxmm/m32
Скалярное преобразование (путем отбрасывания дробной части) вещественного целого в знаковое целое.
Действие: значение источника хранится в младшем двойном слове ХММ*регистра или в 128-разрядной ячейке памяти. Приемник — один из 32-разрядных регистров. Алгоритм работы команды показан на следующей схеме.
Источник rxmm/m1281 пЗ I п2Приемник r32cvttss2si приемник, источникПриемник r32Источник rxmm/m1281 пЗ I п2Приемник r32cvttss2si приемник, источникПриемник r32ВещественноеI п1IВещественноеI п1IОтброситьдробнуючасть=значенияОтброситьдробнуючасть=значенияЦелоеЦелое
Источник rxmm/m1281 пЗ I п2
Приемник r32
cvttss2si приемник, источник
Приемник r32
Источник rxmm/m1281 пЗ I п2
Приемник r32
cvttss2si приемник, источник
Приемник r32
Вещественное
I п1I
Вещественное
I п1I
Отброситьдробнуючасть=значения
Отброситьдробнуючасть=значения
Целое
Целое
Если преобразованный результат больше, чем максимально возможное целочисленное 32- разрядное значение, то будет возвращено значение 80000000h.
DIVPD
DIVPD приемник, источник
66 OF 5E /r DIVPD xmml, xmm2/ml28
Деление упакованных значений с плавающей точкой двойной точности.
Действие: команда делит пары упакованных значений с плавающей точкой двойной точности источника и приемника по следующей схеме: разряды 0...63 приемника делятся на разряды 0...63 источника и результат помещается в разряды 0...63 приемника, разряды 64...127 приемника делятся на разряды 64...127 источника и результат помещается в разряды 64..Л27 приемника.
DIVPS
DIVPS приемник, источник
OF 5E/rDIVPS rxmmljxmmZ/m^e
Деление упакованных значений в форматехмм.
Действие: Алгоритм работы команды показан на следующей схеме.
divps приемник, источник
Приемник
rxmm,——
пЗI
Iп2I
п1
IпОI
Источник
©
rxmm/m128^—
иЗ
[и2I
и1
IиОI
Приемник
rxmm I
пЗ/иЗ
Iп2/и2i
П1/И1
IпО/иОI
DIVSD
OIVSD приемник, источник
F2 OF 5Е /г DIVSD rxmml, rxmm2/m64
Деление скалярных упакованных значений с плавающей точкой двойной точности.
Действие: команда делит младшие упакованные значения с плавающей точкой двойной точности источника и приемника по следующей схеме: разряды 0...63 приемника делятся на разряды 0...63 источника и результат помещается в разряды 0...63 приемника, разряды 64...127 приемника не изменяются.
DIVSS
DIVSS приемник, источник
F3 OF5E/rDIVSS rxmml, rxmm2/m32
Скалярное деление значений вформате ХММ.
Действие: Алгоритм работы команды показан на следующей схеме.
divss приемник, источник
Приемник
rxmm IпЗIп2,п1IпОI
Источник ①
rxmm/m32 IИЗ]и2Iи1IиОI
Приемник
rxmm IпЗ一] п2 I п1 IпО/иОI
FXRSTOR
FXRSTOR источник
OFAE/1FXRSTOR m512byte
Восстановление состояния FPU, ММХ и ХММ, а также регистраMXCSR.
Действие: восстановление состояния сопроцессора (без проверки незамаскированных исключений), целочисленного и потокового ММХ-расширенийиз 512-разрядной области памяти. Алгоритм работы команды показан на схеме (см. следующую страницу).
FXSAVE
FXSAVE приемник
OFAE/OFXSAVE m512byte
Сохранение состоянияFPU, ММХ и ХММ, а также регистраMXCSR.
Действие: сохранение состояния сопроцессора, целочисленного и потокового MMX-расширений в 512-разрядной области памяти (см. схему в описании команды FXRSTOR).
15 14 13 12 11 10 9 8
7 6
5 4
3 2 10
Резерв csipFOP
FTW
FSW FCW
0
РезервmxcsrРезерв
dsdp
16
Резерв
stO/rmmxO
32
Резерв
st1/rmmx1
48
Резерв
st2/immx2
64
Резерв
st3/rmmx3
80
Резерв
st4/rmmx4
96
Резерв
st5/rmmx5
112
Резерв
st6/rmmx6
128
Резерв
st7/rmmx7
144
rxmmO
160
rxmml
176
rxmm2
192
rxmm3
208
rxmm4
224
rxmm5
240
гхттб
256
rxmm7
272
Резерв
288
Резерв
Резерв
496
LDMXCSR
LDMXCSR источник
OF AE/2 LDMXCSR m32
Загрузка регистра состояния/управления MXCSR из 32-разрядной ячейки памяти.
Действие: Алгоритм работы команды показан на следующей схеме.
Регистр состояния/управления mxcsr
| резерв I fa |rc| rclpmlunnlomlzmldmliml -| pe| ue| oe| zejde | ie ]
3115105О
Замечание: по умолчанию регистр MXCSR загружается значением lf80.
LFENCE
LFENCE адрес_6айта
OFAE/5LFENCE
Упорядочение операций загрузки.
Действие: выполняется упорядочение (сериализация) команд загрузки из памяти, которые были инициированы перед вызовом команды LFENCE. Эта операция гарантирует, что каждая команда загрузки, за которой в программе следует команда LFENCE, будет глобально видима перед любой другой командой загрузки, за которой следует команда LFENCE. Команда LFENCE упорядочивается относительно команд загрузки, других команд LFENCE, команд MFENCE и любых команд сериализации (вроде команды CPUID). Она не упорядочивается относительно команд сохранения в памяти или команды SFENCE.
MASKMOVDQU
MASKMOVDQU источник, маска
66OFF7 /гMASKMOVDQUrxmml, rxmm2
Выборочная запись байтов из источника в память с использованием байтовой маскивприемнике.
Действие: команда сохраняет выбранные байты операнда источник в 128-разрядной ячейке памяти. Операнд маска определяет, какие байтыисточника сохраняются впамяти. Местоположение первого байта указанной операндом приемник ячейки памяти, в которой сохраняются байты, определяются парой DS:DI/EDI. Старший значащий бит каждого байта операндамаска определяет, будет ли сохранен в приемнике соответствующий байт источника:
綏 О — байт не сохраняется;
“ 1 — байт сохраняется.
Команда MASKMOVEDQU генерирует процессору указание не задействовать кэш (non-temporal hint). Это указание реализуется методом кэширования WC (Write Combining — память с комбинированной записью). Поскольку в методе WC применяется слабо упорядоченная модель совместимости памяти (weakly-ordered memory consistency model), то команды SFENCE или MFENCE, реализующие операции сериализации, необходимо использовать совместно с командами MASKMOVEDQU. Для многопроцессорной конфигурации это особенно важно, так как в различных процессорах могут применяться различные типы памяти для чтения/записи ячейки приемника.
MAXPD
MAXPD приемник, источник
66 0F5F/r MAXPDrxmml, rxmm2/ml28
Возврат максимальных упакованных значений с плавающей точкой двойной точности.
Действие: команда сравнивает упакованные значения с плавающей точкой двойной точности в источнике и приемнике, находит максимальное и заменяет им соответствующее упакованное значение в приемнике. Если в источнике находится значение SNaN (но нс QNaN), то оно помещается в приемник. Если только одно значение в приемнике или источнике — нечисло (SNaN или QNaN), то в приемник помещается содержимое источника, которое может быть либо нечислом, либо правильным значением с плавающей точкой.
MAXPS
MAXPS приемник, источник
OF,5F,/rMAX PS rxmml, rxmm2/ml28
Возврат максимального из упакованных значений в формате ХММ.
Действие: команда извлекает максимальные значения в каждой из четырех пар вещественных чисел в коротком формате. При этом происходит сравнение значений соответствующих эле- ментовисточникаиприемника,порезультатамкотороговыполняютсяследующиедействия: 梦 если элементприемника или источника является сигнальным нечислом (SNaN), то вэлемент приемника помещается значение источника;
если элемент источника больше элемента приемника, то в элемент приемника помещается элемент источника.
В остальных случаях значения источника и приемника не меняются.
MAXSD
MAX5D приемник, источник
F2 OF 5F/г MAXSD rxmml,rxmm2/m64
Возврат максимального скалярного значения с плавающей точкой двойной точности.
Действие: команда сравнивает значения с плавающей точкой двойной точности в разрядах 0...63 источника и приемника, находит максимальное из них и заменяет им значение в разрядах 0...63 приемника. Если значение в источнике - SNaN (но не QNaN), то оно помещается в приемник. Если только одно значение в приемнике или источнике - нечисло (SNaN или QNaN), то в приемник помещается содержимоеисточника, котороеможет бытьлибонечислом, либоправильнымзначени- ем числа с плавающей точкой. Значение в разрядах 64...127 приемника не меняется.
MAXSS
MAXSS приемник, источник
F3 0F5F/r MAXSSrxmml, rxmm2/m32
Скалярный возврат максимального значения в формате ХММ.
Действие: команда извлекает максимальное из двух вещественных чисел в коротком формате. При этомпроисходитсравнениезначениймладшейпары элементовисточника иприемника, по результатам которого выполняются действия, аналогичные рассмотренным в описании команды MAXPS. Старшие три элемента источника и приемника не меняются.
MFENCE
MFENCE
OFAE/6 . MFENCE
Упорядочение операций загрузки и сохранения.
Действие: команда выполняет упорядочение (сериализацию) команд загрузки из памяти и сохранения в памяти, которые были инициированы перед вызовом команды MFENCE. Эта операция гарантирует, что каждая команда загрузки и сохранения, за которой в программе следует команда MFENCE, будет глобально видима перед любой другой командой загрузки и сохранения, за которой следует команда MFENCE. Команда MFENCE упорядочивается относительно команд загрузки и сохранения, других команд LFENCE, MFENCE, SFENCE и любых команд сериализации (вроде команды CPUID).
MINPD
MINPD приемник, источник
66 OF5D/r MINPDxrnml, xmm2/ml28
Возврат минимальных упакованных значений с плавающей точкой двойной точности.
Действие: команда сравнивает упакованные значения с плавающей точкой двойной точности в источнике и приемнике, обнаруживает минимальное и заменяет им соответствующее упако- ванноезначение вприемнике. Если значение висточнике — SNaN (но не QNaN), то оно помещается в приемник. Если только одно значение в приемнике или источнике — нечисло (SNaN или QNaN), то в приемник помещается содержимое источника, которое может быть либо нечислом, либо правильным значением с плавающей точкой.
MINPS
MINPS приемник, источник
OF 50 /г MINPS rxmml, rxmm2/ml28
Возврат минимального упакованного значения в формате ХММ.
Действие: команда извлекает минимальные значения в каждой из четырех пар вещественных чисел в коротком формате. При этом происходит сравнение значений соответствующих элементов источника и приемника, по результатам которого выполняются действия, аналогичные рассмотренным в описании команды MAXPS.
MINSD
MINSD приемник, источник
F2 0F5D/r MINSD xmml, xmm2/m64
Возврат минимального скалярного значения с плавающей точкой двойной точности.
Действие: команда сравнивает значения с плавающей точкой двойной точности в разрядах 0...63 источника и приемника, обнаруживает минимальное и заменяет им значение в разрядах 0...63 приемника. Если значение в источнике — SNaN (но не QNaN), то оно помещается в приемник. Если только одно значение в приемнике или источнике — нечисло (SNaN или QNaN), то в прием-
ник помещается содержимое источника, которое может быть либо нечислом, либо правильным значением с плавающей точкой. Значение в разрядах 64...127 приемника не меняется.
MIMSS
MINSS приемник, источник
F3 OF 5D/r MINSS rxmml, rxmm2/m32
Скалярный возврат минимальногозначения в формате ХММ.
Действие: команда извлекает минимальное из двух вещественных чисел в коротком форма- те.Приэтомпроисходитсравнениезначениймладшейпарыэлементовисточникаиприемника,по результатам которого выполняются действия, аналогичные рассмотренным в описании команды MAXPS. Старшие три элемента источника и приемника не меняются.
MOVAPD
MOVAPD приемник, источник
OF 28 /гMOVA PS rxmml,rxmm2/ml28
OF 29 /r MOVAPS rxmmZ/mlZS/xmml
Перемещение упакованныхвыровненных значений с плавающей точкой двойной точности.
Действие: команда перемещает два двойных учетверенных слова (содержащих два упакованных значения с плавающей точкой двойной точности) из источника в приемник. Операнд в памяти должен быть выровнен по 16-разряднойгранице.
MOVAPS'
MOVAPS приемник, источник
OF 28/гMOVAPS rxmmlz xmm2/ml28
OF 29 /r MOVAPS rxmm2/ml28, rxmml
Перемещение 128 выровненных битов источника в соответствующие биты приемника.
MOVD
MOVD приемник, источник
См. описание команды MOVD в разделе «Команды блокаММХ».
MOVDQA
MOVDQA приемник, источник
66OF6F/r MOVDQA rxmml, rxmm2/ml28
66 0F7F/r MOVDQA rxmm2/ml28, rxmml
Перемещение 128 выровненных битов из источника в приемник.
Действие: команда перемещает содержимое источника в приемник. Операнд в памяти должен быть выровнен по 16-разрядной границе.
MOVDQU
MOVDQU приемник, источник
F3 OF 6F/r MOVDQU xmml, xmm2/ml28
F3 OF7F/r MOVDQU xmm2/ml28, xmml
Перемещение 128 невыровненных битов из источника в приемник.
Действие: команда перемещает содержимое источника в приемник.
MOVDQ2Q
MOVDQ2Q приемник, источник
F2 OF D6MOVDQ2Q rmmx, rxmm
Перемещение младшего учетверенного словаХММ-регистра в ММХ-регистр.
Действие: команда перемещает разряды 0...63 источника в приемник.
MOVHLPS
MOVHLPS приемник, источник
OF 12/г MOVHLPSrxmml, rxmm2
Копирование содержимого старшей половины ХММ-регистра (источника) в младшую половину другого ХММ-регистра (приемника).
Действие: команда перемещает разряды 64...127 источника в разряды 0...63 приемника. Разряды 64...127 приемника не меняются.
MOVHPD
MOVHPD приемник, источник
66 0F16/r MOVHPD rxmm, m64
66 OF 17/r MOVHPD m64, rxmm
Перемещение старшего упакованного значения с плавающей точкой двойной точности.
Действие: команда перемещает учетверенное слово (содержащее упакованное значение с плавающей точкой двойной точности) из источника в приемник. Источник и приемник могут быть либо ХММ-регистром, либо 64-разрядной ячейкой памяти (но не одновременно). Для регистрового операнда перемещению подвергается старшее учетверенное слово (разряды 64...127). Младшее учетверенное слово ХММ-регистра (разряды 0...63) не меняется.
MOVHPS
MOVHPS приемник, источник
OF 16/r MOVHPSxmm, m64
OF 17/гMOVHPS m64, xmm
Перемещение верхних упакованных значений в формате ХММ из источника в приемник.
Действие: команда имеет несколько вариантов действий:
齡, если источник — 64-разрядный операнд в памяти, то команда MOVHPS перемещает его содержимое в старшую половинуприемника, представляющего собой ХММ-регистр;
Ж если источник — ХММ-регистр, то команда MOVHPS перемещает содержимое его старшей половины в приемник, который представляет собой 64-разрядный операнд в памяти.
MOVLHPS
MOVLHPS приемник, источник
OF 16 /г MOVLHPSxmml, xmm2 .
■ Перемещение нижних упакованных значений в формате ХММ в верхние.
Действие: команда копирует содержимое младшей половины ХММ-регистра (источника) в старшую половину другого ХММ-регистра (приемника). После операции изменяется только содержимое старшей половины приемника.
MOVLPD
MOVLPD приемник, источник
66 OF 12/r MOVLPD rxmm, m64
66 OF 13 /r MOVLPD m64, rxmm
Перемещение младшего упакованного значения с плавающей точкой двойной точности.
Действие: команда перемещает учетверенное слово (содержащее упакованное значение с плавающей точкой двойной точности) из источника в приемник. Источник и приемник могут быть либо ХММ-регистром, либо 64-разрядной ячейкой памяти (но не одновременно). Для регистрового
операнда перемещению подвергается младшее учетверенное слово (разряды 0...63). Старшее учетверенное слово ХММ-регистра (разряды 64...127) не меняется.
MOVLPS
MOVLPS приемник, источник
OF 12/гMOVLPS rxmm, m64
OF 13/г MOVLPS m64, rxmm
Перемещение невыровненных нижних упакованных значений в форматеXMM.
Действие: команда копирует содержимое младшей половины ХММ-регистра в 64-разряд^ ную ячейку памяти или из нее:
Я если источник — 64-разрядный операнд в памяти, то его содержимое перемещается в младшую половину приемника, представляющего собой ХММ-регистр;
犠 если источник — ХММ-регистр, то содержимое его младшей половины перемещается в приемник, который представляет собой 64-разрядный операнд в памяти.
MOVMSKPD
MOVMSKPD приемник, источник
66 OF 50/г MOVMSKPD r32, rxmm
Извлечение 2-разрядной знаковой маски упакованных значений с плавающей точкой двойной точности.
Действие: команда извлекает знаковые разряды из упакованных значений с плавающей точкойдвойнойточностиоперандаисточник(ХММ-регистр)исохраняетполученнуюзнаковую маску в двух младших битах операнда приемник (32-разрядный общий регистр).
MOVMSKPS
MOVMSKPS приемник, источник
OF,50,/rMOVMSKPS r32, rxmm
Перемещение знаковой маски в целочисленный регистр.
Действие: команда формирует маску из знаковых разрядов четырех чисел с плавающей точкой вкоротком формате,упакованныхвХММ-регистр(источник). Послеоперациисодержимое всего 32-разрядного регистра (приемника) изменяется следующим образом: в его младшую тетраду заносится знаковая маска, остальные разряды регистра обнуляются.
movmskps приемник, источник
Приемник.Приемник.Приемникг32 I?.?!?-?!?.?!?.?!
Приемник.
Приемник.
Источник (ХММ-регистр) rxmm
MOVNTDQ
MOVNTDQ приемник, источник
66 OF E7/r M0VNTDQml2& rxmm
Сохранение двойного учетверенного слова из ХММ-регистра в памяти без использования
кэша.
MOVNTI
• MOVNTI приемник, источник
OF СЗ /г MOVNTI m32z г32
Сохранение двойного слова из 32-разрядного регистра общего назначения впамяти без использования кэша.
MOVNTPD
MOVNTPD приемник, источник
66 OF 2В /г MOVNTPD ml28, rxmm
Сохранениеупакованных значений с плавающей точкой двойной точности изХММ-регист- ра в памяти без использования кэша.
MOVQ
M0VQ приемник, источник
См.описание команды MOVQ в разделе«Команды блокаммх».
MOVQ2DQ
MOVQ2DQ приемник, источник
F3 OF D6MOVQ2DQ rxmm, rmmx
Перемещениеучетверенного слова из ММХ-регистра в младшее учетверенное слово ХММ- регистра.
Действие: команда перемещает содержимое источника в разряды 0...63 приемника, а в разряды 64... 127 приемника заносит значение OOOOOOOOOOOOOOOOOh.
MOVNTPS
MOVNTPS приемник, источник
OF 2B/rMOVNTPS ml28,rxmm
Запись в намять 128 бит изХММ-рсгистра, минуя кэш.
MOVSD
MOVSD приемник, источник
F2 OF 10/г MOVSD rxmml, rxmm2/m64
F2 OF 11/г MOVSD rxmm2/m64, rxmml
Перемещение скалярного значения с плавающей точкой двойной точности.
Действие: команда перемещает скалярное значение с плавающей точкой двойной точности из разрядов 0...63 источника в разряды 0...63 приемника. Если операнды — ХММ-регистры, то разряды 64...127 приемника не меняются. Если источник — ячейка памяти, то разряды 64...127 приемника обнуляются.
MOVSS
MOVSS приемник, источник
F3 OF 10 /г MOVSS rxmml, rxmm2/m32
F3 OF 11 /г MOVSS rxmm2/m32, rxmml
Перемещение скалярныхзначений в формате ХММ.
Действие: команда копирует младшие 32 бита источника в младшие 32 бита приемника. Если используется операнд в памяти, то в команде указывается адрес, соответствующий адресу младшего байта данных в памяти. Если операнд в памяти используется в качестве источника, то эти 32 бита копируются в младшее двойное слово 128-разрядного приемника — ХММ-регистра, остальные 96 бит этого регистра устанавливаются в 0.
20 Зак. 256
ИсточникИсточник
rxmm I ИЗ IR\ и1 IИО I сточнти3к2IИО I
Приемник.: Приемник
rxmm | пЗ | й Iп1IПОI rxmm IПЗiП2IП1IПОI
movss приемник, источник
0 I 0 б0 I 0 биО IиО IПриемник * Поиемник
0 I 0 б
0 I 0 б
иО I
иО I
rxmm | пЗ I n2 I п1 I иО I rxmm I 0
а
и1 I иО IL#6~lи1 I иО IL#6~lИсточник
и1 I иО I
L#6~l
и1 I иО I
L#6~l
rxmm I иЗ -J и2
Приемникт32
movss приемник, источник
Приемникт32
e
MOVUPD
MOVUPD приемник, источник
66 OF 10 /r MOVUPD xmml, xmm2/ml28
66 OF 11/r MOVUPD xmm2/ml28, xmml
Перемещение невыровненных упакованных значений с плавающей точкой двойной точности.
Действие: команда перемещает два двойных учетверенных слова (содержащих два упакованных значения с плавающей точкой двойной точности) из источника в приемник. Выравнивания операнда в памяти по 16-разрядной границе не требуется.
MOVUPS
MOVUPS приемник, источник
OF,10,/rMOVUPSxmml, xmm2/ml28
OF,ll,/r MOVUPS ХГПП12/П1128, xmml
Перемещение невыровненных упакованныхзначений в формате ХММ.
Действие: команда перемещает 128 бит источника в соответствующие биты приемника.
MULPD
MULPD приемник, источник
66 OF 59 /г MULPDrxmml, rxmm2/ml28
Умножение упакованных значений с плавающей точкой двойной точности.
Действие: команда умножает пары упакованных значений с плавающей точкой двойной точности источника и приемника по следующей схеме: разряды 0...63 приемника и источника перемножаются и помещаются в разряды 0...63 приемника, разряды 64...127 приемника и источника перемножаются и помещаются в разряды 64...127 приемника.
MULPS
MULPS приемник, источник
OF,59,/rMULPS xmml, xmm2/ml28
Умножение упакованных значений в формате ХММ.
Действие: команда умножает четыре пары вещественных чисел в коротком формате. Схема работы команды MULPS показана на следующем рисунке.
mulp приемник, источник
Приемникrxmm IпЗIп2I п1 I ПО I
Источник rxmm/m128Приемник rxmmИсточник rxmm/m128Приемник rxmm©©©©厂иЗIи2Iи1IиО1====С一口3%3I ■ п2*и2iП1*И1IпО*иОI©©©©厂иЗIи2Iи1IиО1====С一口3%3I ■ п2*и2iП1*И1IпО*иОI
Источник rxmm/m128
Приемник rxmm
Источник rxmm/m128
Приемник rxmm
©
©
©
©
厂иЗ
Iи2I
и1
IиО1
=
=
=
=
С一口3%3
I ■ п2*и2i
П1*И1
IпО*иОI
©
©
©
©
厂иЗ
Iи2I
и1
IиО1
=
=
=
=
С一口3%3
I ■ п2*и2i
П1*И1
IпО*иОI
MULSD
MULSD приемник, источник
F2 OF 59 /r MULSD rxmml, гхтт2/тб4
Умножение скалярных упакованных значений с плавающей точкой двойной точности.
Действие: команда умножает младшие упакованные значения с плавающей точкой двойной точности источника и приемника по следующей схеме: разряды 0...63 приемника и источника перемножаются и помещаются в разряды 0...63 приемника, разряды 64...127 приемника не меняются.
MULSS
MULSS приемник, источник
F3 OF 59/r MULSS rxmml, rxmm2/m32
Умножение скалярных значений в формате ХММ.
Действие: операнды источник и приемник находятся в ХММ-регистре, кроме того, операнд источник может находиться в 32-разрядной ячейке памяти. Команда умножает вещественные значения в младших парах операндов в формате ХММ по следующей схеме: разряды 0...31 приемника и источника перемножаются и помещаются в разряды 0...31 приемника, разряды 32...127 не меняются.
ORPD
ORPD приемник, источник
66 OF 56 /r ORPD xmml, xmm2/ml28
Поразрядное логическое ИЛИ над упакованными значениями с плавающей точкой двойной точности.
Действие: команда выполняет операцию поразрядного логического ИЛИ над парами упакованных значений с плавающей точкой двойной точности в разрядах 0...127 приемника и источника и помещает результат в разряды 0...127 приемника.
ORPS
ORPS приемник, источник
OF 56/rORPS rxmml,rxmm2/ml28
Поразрядное логическое ИЛИ над каждой парой битов упакованных операндов источник и приемник в формате ХММ.
PACKSSWB/PACKSSDW
PACKSSWB приемник, источник
PACKSSDW приемник, источник
См. описание команд PACKSSWB/PACKSSDW в разделе «Команды блокаММХ».
PACKUSWB
PACKUSWB приемник, источник
См. описание команды PACKUSWB в разделе «Команды блока ММХ».
PADDB/PADDW/PADDD
PADDB приемник, источник
PADDW приемник, источник
PADDD приемник, источник
См. описание команд PADDB/PADDW/PADDD в разделе «Команды блокаммх».
PADDQ
PADDQ приемник, источник
См. описание команды PADDQ в разделе «Команды блока ММХ».
PADDSB/PADDSW
PADDSB приемник, источник
PADDSW приемник, источник
См. описание команд PADDSB/PADDSW в разделе «Команды блока ММХ».
PADDUSB/PADDUSW
PADDUSB приемник, источник
PADDUSW приемник, источник
См. описание команд PADDUSB/PADDUSW в разделе «Команды блока ММХ».
PAND
PAND приемник, источник
См. описание команды PAND в разделе «Команды блока ММХ».
PANDN
PANDN приемник, источник
См. описание команды PANDN в разделе «Команды блока ММХ».
PAVGB/PAVGW
PAVGB приемник, источник
PAVGW приемник, источник
См. описание команд PAVGB/PAVGW в разделе «Команды блока ММХ».
PCMPEQB/PCMPEQW/PCMPEQD
PCMPEQB приемник, источник
PCMPEQW приемник, источник
PCMPEQD приемник, источник
См. описание команд PCMPEQB/PCMPEQW/PCMPEQD в разделе «Команды блока ММХ»
PCMPGTB/PCMPGTW/PCMPGTD
PCMPGTB приемник, источник
PCMPGTWприемник, источник
PCMPGTD приемник, источник
См. описание команд PCMPGTB/PCMPGTW/PCMPGTD в разделе «Команды блока ММХ».
PEXTRW
PEXTRW приемник, источник, маска
См. описание команды PEXTRW в разделе «Команды блока ММХ».
PINSRW
PINSRW приемник, источник, маска
См.описание команды PINSRW в разделе «Команды блока ММХ».
PMADDWD
PMADDWD приемник, источник
См.описание команды PMADDWD в разделе «Команды блока ММХ».
PMAXSW
PMAXSW приемник, источник
См.описание команды PMAXSW в разделе «Команды блока ММХ».
PMAXUB
PMAXUB приемник, источник
См.описание команды PMAXUB вразделе «Команды блока ММХ».
PMINSW
PMINSW приемник, источник
См.описание команды PMINSW в разделе «Команды блока ММХ».
PMINUB
PMINUB приемник, источник
См. описание команды PMINUB в разделе«Команды блока ММХ».
PMOVMSKB
PMOVMSKB приемник, источник
См.описание команды PMOVMSKB в разделе «Команды блока ММХ».
PMULHUW
PMULHUW приемник, источник
См. описание команды PMULHUW в разделе «Команды блока ММХ».
PMULHW
PMULHW приемник, источник
См. описание команды PMULHW вразделе «Команды блока ММХ».
PMULLW
PMULLW приемник, источник
См.описание команды PMULLW в разделе «Команды блока ММХ».
PMULUDQ
PMULUDQ приемник, источник
См.описание команды PMULUDQ в разделе «Команды блока ММХ».
POR
POR приемник, источник
См. описание команды POR в разделе «Команды блока ММХ».
PSADBW
PSADBW приемник, источник
См. описание команды PSADBW в разделе «Команды блока
PSHUFD
PSHUFD приемник, источник, маска
i8PSHUFD
• Копирование двойных слов из операнда источникв операнд приемник (ХММ-
регистр).
Источник
127 96 95 64 63 32 31О
1 и3и2
и1
и0
I I I
МаскаМаскаhVI hV i hV iH*IhVI hV i hV iH*IКодированиебитов маски:
Маска
Маска
hVI hV i hV iH*I
hVI hV i hV iH*I
00
01 Ml
10 —м2
11 —иЗ
иЗ и1
иЗ и1
иЗ иО
иЗ иО
127 96 95 64 63 32 31О
Приемник
Действие: на основе значения пар битов маски команда копирует двойные слова из источника в приемник. Каждая пара битов маски определяет номер слова источника для перемещения в приемник следующим образом:
麵 Маска[1:0]:
00 - приемник[0...31]
D 01 -
П 10 —
D 11 -источник[96...127].
缠 Маска[3:2]:
D 00 -
П 01 - приемник[32...63]
D 10 - приемник[32...63]
D - приемник[32...63]источник[96...127].
犠 Маска[5:4]:
D 00 — приемник[64...95]
П 01 - приемник[64...95]
П 10 - приемник[64...95]
D - приемник[64...95]источник[96...127].
綴 Маска[7;6]:
П 00 - приемник[96...127]
D 01 - приемник[96...127] —источник[32...63];
□ 10 - прнемник[96 …127] — источник[64.„95];
D 11 - приемник[96...127]— источник[96...127].
Если использовать один и тот же ХММ-регистр в качестве источника и приемника, то можно выполнять любые перестановки двойных слов в пределах одного ХММ-регистра, в том числе и инициализацию значением одного двойного слова других двойных слов. Работу команды PSHUFD поясняет схема (см. предыдущую страницу).
PSHUFHW
PSHUFHW приемник, источник, маска
F3 OF 70 /г i8PSHUFHWrxmml,rxmm2/ml28,i8
Копирование слов из старшего учетверенного упакованного слова операнда источник (ХММ- регистр) в старшее учетверенное упакованное слово операнда приемник (ХММ-регистр).
Действие: на основе значения пар битов маски команда копирует слова из старшего упакованного учетверенного слова источника в старшее учетверенное упакованное слово приемника. Каждая пара битов маски определяет номер слова источника для перемещения в приемник следующим образом:
Источник rxmn/m128
127 112 111 96 95 80 79 64 63О
127 112 111 96 95 80 79 64 63
Приемник rxmm
犠 Маска[1:0]:
Р 00 — приемник[64...79] —источник[64...79];
П 01 — приемник[64....79] 一 источник[80...95];
П 10 - приемник[64..,79] — источник[96,..1И];
П 11 — приемхмк[64…79] 一 источник[112…127]. 粱 Маска[3:2]:
П 00 —приемник[80....95] —источник[64...79];
П 01 — приемник[80....95] <-источник[80...95];
D 10— приемник[80...95] 一 источник[96..,111];
П 11 — приемник[80...95] — источник[112...127].
Маска[5:4):
D 00 — приемник[96...1П] 一 источиик[64..,79];
D 01 —叩 иемннк[96 …Ш] — источник[80...95];
П 10 — приемник[96...1И] — источник[96...1И];
D И -
№ Маска[7:6]:
00 - приемник[112...127]
01 -
D10-
П И - приемник[112...127]112...127].
Если использовать один и тот же ХММ-регистр в качествеисточника иприемника, то можно выполнять любые перестановки слов в пределах старшего учетверенного слова одного
в том числе и инициализацию значением одного слова других слов. Работу схема (см. предыдущую страницу).
PSHUFLW
PSHUFLW приемник, источник, маска
i8PSHUFLW rxmml,rxmm2/ml28,i8
Копирование слов из младшего учетверенного упакованного слова операнда источник (ХММ- регистр) в младшее учетверенное упакованное слово операнда приемник (ХММ-регистр).
Действие: на основе значения пар битов маски команда копирует слова из младшего учетверенного слова источника в младшее учетверенное слово приемника. Каждая пара битов маски деляетслова источника для перемещения в приемник следующим образом:
Источник rxmm/n128
12763 4847 3231
и3и2и1иО
Кодирование битов маски:Кодирование битов маски:ПП uQ 01 — и1 10 — и2 11 — изПП uQ 01 — и1 10 — и2 11 — изI I I I
Кодирование битов маски:
Кодирование битов маски:
ПП uQ 01 — и1 10 — и2 11 — из
ПП uQ 01 — и1 10 — и2 11 — из
63 4847 3231
Приемник
潜 Маска[1:0]:
П 00 -.15]
П 01 —
О.15]
11 —.15]источник[48...63].
急 Маска[3:2]:
D00 —.31]
П 01 —
П
П 11 —.31] источник[48...63].
獴 Маска[5:4]:
47]
П 01 —47]
D 10 —
- приемник[32...47]источник[48...63].
瓶 Маска[7:6]:
D 00 -
П 01 -
П 10 - приемник|47...63]
П 11 —
Если использовать один и тот же ХММ-регистр в качестве источника и приемника, то можно выполнять любые перестановки слов в пределах младшего учетверенного слова одного ХММ- регистра, в том числе и инициализацию значением одного слова других слов. Работу команды PSHUFLW поясняет следующая схема (см. предыдущую страницу).
PSLLDQ
PSLLDQ приемник,
rxmml,imm8
Логический сдвиг влево приемника на заданное число байтов.
Действие: сдвиг влево приемника на число байтов, указанных непосредственным операндом количество_сдвигов. Освобождаемые слева младшие байты обнуляются. Если значение, указанное операндомбольше, чем 15, операнд приемник обнуляется.
PSLLW/PSLLD/PSLLQ
PSLLW приемник,
PSLLD приемник, количество_сдвигов
PSLLQ приемник, количество_сдвигов
См. описание команд PSLLW/PSLLD/PSLLQ в разделе «Команды блока
PSRAW/PSRAD
PSRAW приемник, количество_сдвигов
PSRAD приемник, количество_сдвигов
См. описание команд PSRAW/PSRAD в разделе «Команды блока ММХ».
PSRLDQ
PSRLDQ приемник, количество_сдвигов
rxmml,i8
Сдвиг вправо приемника начисло байтов.
Действие: сдвиг вправо приемника на число байтов, указанных непосредственным домколичество_сдвигов. Освобождаемыесправамладшиебайтыобнуляются. Еслиука
занное операндом количество_сдвигов, больше, чем 15, операнд приемник обнуляется.
PSRLW/PSRLD/PSRLQ
PSRLW приемник, количество_сдвигов
PSRLD приемник, количество_сдвигов
PSRLQ приемник, количество_сдвигов
См. описание команд PSRLW/PSRLD/PSRLQ в разделе «Команды блока ММХ».
PSUBB/PSUBW/PSUB
PSUBBисточник
PSUBW приемник, источник
PSUBD приемник, источник
См. описание команд PSUBB/PSUBW/PSUBD в разделе «Команды блока ММХ».
PSUBQ
PSUBQ приемник, источник
См. описание команды PSUBQ в разделе «Команды блока ММХ».
PSUBSB/PSUBSW
PSUBSB приемник, источник
PSUBSW приемник, источник
См. описание команд PSUBSB/PSUBSW в разделе «Команды блока ММХ».
PSUBUSB/PSUBUSW
PSUBUSB приемник, источник
PSUBUSW приемник, источник
См. описание команд PSUBUSB/PSUBUSW в разделе «Команды блока ММХ».
PUNPCKHBW/PUNPCKHWD/PUNPCKHDQ/PUNPCKHQDQ
PUNPCKHBW приемник, источник
PUNPCKHWD приемник, источник
PUNPCKHDQ приемник, источник
PUNPCKHQDQ приемник, источник
См. описание командPUNPCKHBW/PUNPCKHWD/PUNPCKHDQ/PUNPCKHQDQ в разделе «Команды блока ММХ».
PUNPCKLBW/PUNPCKLWD/PUNPCKLDQ/PUNPCKLQDQ
PUNPCKLBW приемник, источник
PUNPCKLWD приемник, источник
PUNPCKLDQ приемник, источник
PUNPCKLQDQ приемник, источник
См. описание командPUNPCKLBW/PUNPCKLWD/PUNPCKLDQ/PUNPCKLQDQ в разделе «Команды блока ММХ».
PXOR
PXOR приемник, источник
см. описание команды PXOR в разделе «Команды блока ММХ».
RCPPS
RCPPS приемник, источник
OF 53 /rRCPPS rxmml,rxmm2/ml28
Вычисление обратных упакованных значений в формате ХММ.
Действие: команда вычисляет обратные значения элементов источника по формуле 1/(эле- мент источника). Максимальная ошибка вычисления не должна превышать значения 1,5 • 2'12. Схема работы команды RCPPS показана на следующем рисунке.
repps приемник, источник
Источникrxmm/m128 匚也3 I и2 I I иО I! iI
У у
Приемник rxmm I 1/иЗ I 1/и2 I 1/и1 I 1/иО
RCPSS
RCPSS приемник, источник
F3 OF 53 /г RCPSS rxmml,rxmm2/m32
Скалярное вычисление обратного упакованного значения в формате ХММ.
Действие: команда вычисляет обратное значение младшего элемента операнда источник по формуле 1/(элемент источника). Максимальная ошибка вычисления не должна превышать значения 1,5 • 2*13.
RSQRTPS
RSQRTPS приемник, источник
OF 52 /г RSQRTPS rxmml,rxmm2/ml28
Вычисление обратного значения квадратного корня от упакованного значения в формате
ХММ.
Действие: команда вычисляет сначала квадратный корень каждого элемента операнда источник, а затем — обратную величину полученного значения:
1/д/элемент источника.
Максимальная ошибка вычисления не должна превышать значения 1,5 • 2-'"
RSQRTSS
RSQRTSS приемник, источник
F3 OF 52 /г RSQRTSS rxmml,rxmm2/m32
Скалярная аппроксимация обратных значений квадратного корня упакованных значений в формате ХММ.
Действие: команда для младшего элемента операнда источник сначала вычисляет квадратный корень, а затем — обратную величину полученного значения:
1/ ^элемент источника.
Максимальная ошибка вычисления не должна превышать значения 1,5 • 2 — Старшие элементы операнда приемник не меняются.
SHUFPD
SHUFPD приемник, источник, маска
66 OFC6 /г i8SHUFPDrxmmljxmmZ/mlZejS■
Перестановка упакованных значений с плавающей точкой двойной точности.
Действие: команда перемещает упакованные значения с плавающей точкой двойной точности из приемника и источника в приемник в соответствии со значением непосредственного операнда маска. Биты маски определяют номера упакованных значений с плавающей точкой двойной точности в источнике или приемнике, которые будут перемещены в приемник следующим образом:
斂 Маска[0]:
D 0 — приемник(0.„63] — приемник[0...63];
1 — приемник[0...63]—叩 иемник[64 …127].
Маска[1]:
0 — приемник[64...127] — источник[0...63];
1 — приемник[64...127] е- источник[64...127].
620Приложение. Система команд процессоров IA-32
Для перестановки в пределах одного регистра можно использовать один и тот же ХММ-ре- гистрвкачествеисточникаиприемника.
SHUFPS
SHUFPS приемник, источник, маска
OFC6 /rib SHUFPS rxmml,rxmm2/ml28,imm8
Перераспределение упакованных значений в формате ХММ.
Действие: команда перераспределяет любые два из четырех двойных слов приемника в два младших двойных слова того же приемника и любые два из четырех двойных слов источника в два старших двойных слова приемника. Если использовать один и тот же ХММ-регистр в качс- стве источника и приемника, то можно выполнять любые перестановки в пределах одного регистра. Каждая пара битов маски определяет номер двойного слова источника или приемника, которое будет перемещено в приемник следующим образом:
Маска| 1:OJ:
П 00 приемник[00...31]источник[00...311;
П 01 приемник[00.„31] — источник[32...63|;
П 10叩 мемник[00 …31] — источник164...95];
D 11 приемник[00...31]«—источник[96...127].
然 Маска[3:2]:
D 00 приемник[32..,63] — источник[00...31];
D 01 приемник[32...63] <- источник[32..,63];
П 10叩 иемник[32...63] — источник[64.„95];
D И приемник|32.„63] источник[96...127]..
拒 Маска[5:4]:
D 00 приемник[64...95] —источник[00...31];
П 01 приемник[64...95] j источник[32...63|;
П 10 приемник[64.„95] — источник[64...95];
D 11 приемник[64..,95] — источник[96...127].
Я Маска[7:6]:
D 00 приемник|96..,127] — источник[00.„31];
П 01 приемник[96...127] 一 источник[32..,63];
П 10 приемник[96...127] —источник[64...95];
П И приемник[96...127] <— источник[96...127].
Схематически работа команды SHUFPS показана на слспующем рисунке.
shufps приемник, источник, маскаshufps приемник, источник, маскаИсточник rxmm/m128Источник rxmm/m128Приемник rxmmПриемник rxmm| иЗ| иЗи2П и1 I иО I I пЗ I п2I п1 Iи2П и1 I иО I I пЗ I п2I п1 I史史пО□пО□МаскаМаскаV !V !i8fc¥现?火張文х!|i8fc¥现?火張文х!|
shufps приемник, источник, маска
shufps приемник, источник, маска
Источник rxmm/m128
Источник rxmm/m128
Приемник rxmm
Приемник rxmm
| иЗ
| иЗ
и2П и1 I иО I I пЗ I п2I п1 I
и2П и1 I иО I I пЗ I п2I п1 I
史
史
пО□
пО□
Маска
Маска
V !
V !
i8fc¥现?火張文х!|
i8fc¥现?火張文х!|
【立1而1 n3\i0 1口3二口0 I n3:riO I
Приемник rxmm
SQRTPD
SQRTPD приемник, источник
• 66 OF 51 Д• 66 OF 51 ДSQRTPD rxmmlfrxmm2/ml28SQRTPD rxmmlfrxmm2/ml28
• 66 OF 51 Д
• 66 OF 51 Д
SQRTPD rxmmlfrxmm2/ml28
SQRTPD rxmmlfrxmm2/ml28
Вычисление квадратного корня упакованных значений с плавающей точкой двойной точно
сти.
Действие: команда вычисляет квадратный корень упакованных значений с плавающей точкой двойной точности источника по следующей схеме: вычисляется значение квадратного корня в разрядах 0...63 источника и помещается в разряды 0...63 приемника, вычисляется значение квадратного корня в разрядах 64... 127 источника и помещается в разряды 64... 127 приемника.
SQRTPS
SQRTPS приемник, источник
OF 51/гSQRTPS rxmmljxmm2/ml28
Вычисление квадратного корня упакованных значений в формате ХММ.
Действие: команда извлекает квадратный корень из четырех упакованных вещественных чисел в коротком формате.
SQRTSD
SQRTSD приемник, источник
F2 OF 51 /гSQRTSD rxmml,rxmm2/m64
Вычисление квадратного корня скалярного упакованного значения сплавающей точкой двойной точности.
Действие: команда вычисляет значение квадратного корня младшего упакованного значения с плавающей точкой двойной точности источника по следующей схеме: вычисляется значение квадратного корня в разрядах 0...63 источника и помещается в разряды 0...63 приемника, значение в разрядах 64...127 приемника не меняется.
SQRTSS
SQRTSS приемник, источник
F3 0F51/rSQRTSS rxmml,rxmm2/m32
Скалярное извлечение квадратного корня.
Действие: команда извлекает квадратный корень из младшего двойного слова операнда источник, который должен представлять собой упакованное вещественное число в формате ХММ.
STMXCSR
STMXCSR приемник
OF АЕ /3STMXCSR m32
Сохранение регистра управления/состояния MXCSR в 32-разрядной ячейке памяти.
SUBPD
SUBPD приемник, источник
66 OF 5С/гSUBPDrxmml,rxmm2/ml28
Вычитание упакованных значений с плавающей точкой двойной точности.
Действие: команда вычитает пары упакованных значений с плавающей точкой двойной точности источника и приемника по следующей схеме: из разрядов 0...63 приемника вычитаются разряды 0...63 источника и результат помещается в разряды 0...63 приемника, из разрядов 64...127 приемника вычитаются разряды 64...127 источника и результат помещается в разряды 64... 127 приемника.
SUBPS
SUBPS приемник, источник
OF 5С/гSUBPS rxmml,rxmm2/ml28
Вычитание упакованных значений в форматехмм.
SUBSD
SUBSD приемник, источник
F2 OF 5С/гSUBSD rxmml,rxmm2/m64
Вычитание скалярных упакованных значений с плавающей точкой двойной точности.
Действие: команда вычитает младшие упакованные значения с плавающей точкой двойной точности источника и приемника по следующей схеме: из разрядов 0...63 приемника вычитаются разряды 0...63 источника и результат помещается в разряды 0...63 приемника, разряды 64...127 приемника не меняются.
SUBSS
SUBSS приемник, источник
F3 OF 5С/г SUBSS rxmml,rxmm2/m32
Вычитание скалярных значений в формате ХММ.
Действие: команда вычитает значения младшей пары вещественных чисел в формате ХММ.
UCOMISD
UCOMISD приемник, источник^ условие
66 OF 2E/r UCOMISD rxmml,rxmm2/m64
Сравнение неупорядоченных скалярных значений с плавающей точкой двойной точности и установка регистра Е FLAGS.
Действие: команда сравнивает неупорядоченные скалярные значения с плавающей точкой двойной точности в разрядах 0...63 приемника и источника. По результату сравнения устанавливаются флаги ZF, PF, и CF в регистре EFLAGS (см. описание команды COMISD). Отличие команды COMISD от команды UCOMISD состоит в условиях генерации исключения недействительной операции с плавающей точкой (#1): команда COMISD генерирует его, когда приемник и/ или источник являются нечислом (QNaN или SNaN); команда UCOMISD генерирует его только в том случае, когда один из исходных операндов является нечислом SNaN. В случае генерации немаскированного исключения недействительной операции с плавающей точкой регистр EFLAGS не модифицируется.
UCOMISS
UCOMISS приемник, источник
0F2E/rUCOMISSrxmmVxmm2/m32
Неупорядоченное скалярное сравнение значений в формате ХММ с установкой флагов в EFLAGS.
Таблица П.25. Допустимые значения флагов в регистре EFLAGS
Соотношение операндов
Значения флагов
Приемник > источник
OF - SF - AF - 0; ZF - 0; PF = 0; CF - 0
Приемник < источник
OF - SF- AF-0; ZF-0;PF-0; CF- 1
Приемник = источник
OF - SF - AF - 0; ZF = 1; PF - 0; CF 0
Приемник или источник — нечисло sNaN
OF - SF- AF - 0; ZF = 1; PF = 1; CF = 1
Действие: сравнение пары вещественных элементов в коротком формате, расположенных в младшем двойном слове операндов в формате ХММ. В результате выполнения команды формируются значения флагов ZFPF и CF, а флаги OF, SF и AF устанавливаются в 0 (табл. П.25). В про-
цессе работы команда распознает специальные значения qNaN и sNaN. При возникновении незамаскированных исключений расширения ХММ регистр EFLAGS не изменяется.
UNPCKHPD
UNPCKHPD приемник, источник
66 OF 15/г UNPCKHPD rxmml,rxmm2/ml28.
Разделение и чередование старших упакованных значений с плавающей точкой двойной точности.
Действие: команда разделяет старшие упакованные значения с плавающей точкой двойной точности в источнике и приемнике, помещая их с чередованием в приемник но следующей схеме: разряды 64...127 приемника помещаются в разряды 0...63 приемника, разряды 64...127 источника помещаются в разряды 64...127 приемника.
UNPCKHPS
UNPCKHPS приемник, источник
OF 15/гUNPCKHPSrxmml,rxmm2/inl28
Чередование верхнихупакованных значений в формате ХММ.
Действие: перемещение путем чередования двух старших двойных слова приемника и источника. Младшие двойные слова приемника и источника игнорируются.
UNPCKLPD
UNPCKLPD приемник, источник
660FU /г UNPCKLPDrxmml,rxmm2/ml28
Разделение и чередование младших упакованныхзначений с плавающей точкой двойной точ- пости.
Действие: команда разделяет младшие упакованные значения с плавающей точкой двойной точности в источнике и приемнике, помещая их с чередованием в приемник по следующей схеме: разряды 0...63 приемника не меняются, разряды 0...63 источника помещаются в разряды 64...127 приемника.
UNPCKLPS
UNPCKLPS приемник, источник
OF 14/гUNPCKLPS rxmml,rxmm2/nil28
Чередование нижних упакованных значений в формате ХММ.
Действие: перемещение путем чередования двух младших двойных слова приемника и источника. Старшие двойные слова приемника и источника игнорируются.
unpcklps приемник, источник
Источник rxmm/m128Приемник rxmm—
I иЗ I и2 II иО I I пЗ I п2 I г
11 I
I
V
Приемник rxmm I и1 IиО I пО 1
XORPD
XORPD приемник, источник
бб OF 57 /г XORPD rxmml,rxmm2/ml28
Выполнение поразрядной операции логического исключающегоИЛИ над упакованными значениями с плавающей точкой двойной точности.
Действие: команда выполняет поразрядную опергщию логического исключающего ИЛИ над парами упакованных значений с плавающей точкой двойной точности в разрядах0...127 приемника и источника и результат помещает в разряды 0...127 приемника.
XORPS
XORPS приемник, источник
OF 57 /г XORPS rxmml,rxmm2/ml28
Выполнение поразряднойоперации логического исключающегоИЛИ над упакованными значениями в формате ХММ.
Список литературы
ХаммелР. Л. Последовательная передача данных: Руководство для программиста. - М.: Мир, 1996.
Тук М. Аппаратные средства IBM PC: Энциклопедия. — СПб.: Питер, 2001.
Тук М. Интерфейсы ПК: Энциклопедия. — СПб.: Питер, 2001.
Рихтер Дж. Программирование серверных приложений для Windows 2000. — СПб.: Питер, 2001.
Соломон Д. Внутреннее устройство Windows 2000. — СПб.: Питер, 2001.
ВильямсА. Системное программирование Windows 2000. — СПб.: Питер, 2001.
ТаненбаумЭ. Архитектура компьютера. — СПб.: Питер, 2002.
ЮровВ. Assembler: Практикум. — СПб.: Питер, 2001.
КасперскийКрис. Образ мышления — дизассемблер IDA. — М.: Солон-Р, 2001.
Рихтер Дж. Windows для профессионалов. — СПб.: Питер, 2000.
И. IA-32 Intel® Architecture Software Developer's Manual. Vol. 2. Instruction Set Reference. Intel Corporation, 2002.
Assembly-Language Developer System, Version 6.1, for MS-DOS and Windows Operation System Microsoft Corporation.
Тук М, Юров В. Процессоры Pentium IV, Athlon и Duron. — СПб.: Питер, 2001.
IA-32 Intel® Architecture Software Developer's Manual. Vol. 1. Basic architecture. Intel Corporation, 2002.
IA-32 Intel® Architecture Software Developer's Manual. Vol. 3. System programming guide. Intel Corporation, 2002.
Кулаков В. Программирование на аппаратном уровне: Спец. справ.. — СПб.: Питер, 2003.
Персональный компьютер в играх и задачах. —М.: Наука, 1988.
Шилдт Г. Теория и практика C++. - СПб.: БХВ-Петербург, 2001.
Алфавитный указатель
с
CISC-процессор, 31
CMOS-память, 156
м
make-файл, 403
R
RISC-технология разработки процессоров, 23
и
32
V
v-конвейер, 32
W
Windows-приложение
каркасное, 367, 379
консольное, 367
оконное, 367
А
абсолютное выражение, 99
адресное пространство ввода-вывода, 149
адресный операнд, 92
аргументы, 330
пролога/эпилога, 343
фактические,
300, 330
арифметические команды, 474
арифметический сдвиг, 200
архитектура
компьютера фон Неймана, 28
процессоров IA-32, 22, 29
27
асинхронные сообщения Windows, 393, 399
атрибут комбинирования сегментов, 333
Б
база
кадра стека, 161
сегмента, 52
базовая индексная адресация
со смещением, 272
байт,
масштаба, индекса и базы sib, 70
режима адресации mod64
безусловный переход,
бесконечность, 464
библиотека DLL, 220, 363
бит,
битовое поле,
битовые строки, 205
блок
декодирования команд, 22
микропрограммного
управления,60
предварительной выборки, 22
предсказания переходов, 32
сегментации, 22
страничной адресации, 22
удаления и восстановления, 34, 38
буфер
команд, 34
меток перехода, 35
переупорядочивания запросов
к памяти, 35
В
венгерская запись, 386
вещественная неопределенность, 465
внешнее устройство, 18
возврат результата из процедуры, 340, 355
выражение ассемблера, 91, 97
вычисление степени произвольного числа, 492
г
главная функция Windows-
приложения, 368
глобальная дескрипторная таблица, 56
д
данные простого типа, из
двоично-десятичный код, 168
неупакованный формат, 168, 184
упакованный формат, 168,190
двоичное дополнение, 84
двоичные целые числа, 168
двойное слово, 111
двухмерный массив, 275
декодер, 36
денормализованные вещественные
числа, 463
дескриптор сегмента, .54
дескрипторная таблица векторов
прерываний, 56
десятичные числа, 168
диалоговая процедура, 424
диалоговая функция, 420
динамический анализ потока данных, 32
динамическое исполнение, 32
директива
%CONDS, 321
%CTLS, 321
%INCL(321
%LIST, 321
%MACS,322
%NOCONDS, 321
%NOCTLS,321
%NOINCL,321
%NOLIST, 321
%NOMACS, 322
%NOSYMS,321
%OUT,319
%SYMS,321
.ENDW, 244
_ERRB(ERRIFB),317
.ERRDEF (ERRIFDEF), 317
.ERRDIF (ERRIFDIF), 317
.ERRE(ERRIFE),318
.ERRIDN (ERRIFIDN), 318
•ERRNB(ERRIFNB),3i7
.ERRNDEF (ERRIFNDEF), 317
.ERRNZ (ERRIF),318
.LALL, 322
•LFCOND, 321
директива (продолжение)
.LIST, 321
.MODEL, 384
.SALL, 322
.SFCONDS, 321
.TFCOND, 322
.UNTILCXZ, 244
.XALL, 322
.XLIST,321
_asm, 345
ARG, 223, 351,357, 358,360
ASSUME, 106,214
CATSTR, 296
DB, 114
DD, 114
DF, 115
DISPLAY,313,319
DP, 115
DQ, 115
DT, 115
DW, 114
ENDP,219, 327
ERR, 316
ERRIFDIFI.318
ERRIFIDNI.318
EVEN, 508
EXITM, 305,308,314
EXTRN, 329, 337,385
GOTO, 305, 308
IF, 309
IFB,313
IFDEF.311
IFDIF,314
IFDIFI,314
IFE, 309
IFIDN.314
IFIDN1,314
IFNB,313
IFNDEF,311
INCLUDE, 384
INSTR, 296
INVOKE, 341
IRP, 305, 307
IRPC, 305, 307
LABEL,211,270
LOCAL, 223, 303, 343, 357
LOCALS,234, 384 MODEL,107,353
OPTION EPILOGUE, 343
OPTION PROLOGUE, 343
ORG,213
PROC, 219, 327, 346
PROTO, 341
PUBLIC,212,329, 337
REPT, 270, 305, 306
RETURNS, 223
директива (продолжение)
SEGMENT, 104, 337
SIZESTR, 297
SUBSTR, 296
USES,223,357
WHILE, 305, 306
директивы
ассемблера, 86, 87,133
вывода
блоков условного
ассемблирования, 321
макрорасширений, 322
текста включаемых файлов, 321
выделения подстроки в строке, 296
генерации пользовательской
ошибки, 315
определения
вхождения одной строки
в другую, 296
длины строки в текстовом
макросе, 297
повторения, 305,306
резервирования и инициализации
данных, ИЗ
сегментации, 337
слияния строк, 296
управления
листингом, 134, 321
процессом генерации
макрорасширений, 305, 308
условной компиляции, 308
дополнительный код, 83
драйвер расширенной памяти EMS, 22
Ж
жизненный цикл программы
на ассемблере, 122
3
запись, 286
значение REQ, 302
И
идентификатор, 89
имя макрокоманды, 297
индексная адресация со смещением, 272
инициализация массива, 270
интерфейс
API, 367
GUI, 367
исключение, 179, 500
исключительная ситуация, 453
исполнительное устройство, 18
исполнительный блок, 22
исчисление высказываний, 194
К
кадр стека, 359
каркасное Windows-приложение, 367, 379
кисть, 390
класс окна, 389
ключевые слова ассемблера, 89
код операции, 60, 63, 87
кодировка
ANSI, 386
UNICODE, 386
коды условия C3...C0, 472
команда
ААА, 185,513
AAD, 189, 513
ААМ, 188,513
AAS, 186, 513
ADC,514
adc, 171
ADD, 514
add, 171
ADDPD, 587
ADDPS, 587
ADDSD, 587
ADDSS, 587
AND, 195, 289, 514
ANDNPD, 588
ANDNPS, 588
ANDPD, 588
ANDPS, 588
ARPL.515
BOUND, 515
BSF,197,515
BSR, 197,516
BSWAP, 516
ВТ, 516
bt, 198
ВТС.516
btc, 199
BTR.517
btr, 198
BTS.517
bts, 198
CALL, 517
call, 222
CBW,182,518
CDQ, 182, 518
CLC,518
CLD, 253,518
CLFLUSH,518,588
CLL519
CLTS, 519
CMC, 519
CMOVcc,519
CMP, 225, 520
команда (продолжение)
команда (продолжение)
CMPPD, 589
FCHS, 479, 555
CMPPS, 589
FCLEX, 496,555
CMPS, 250, 255, 520
FCMOVcc, 555
CMPSB, 250,258,520
FCOM, 472,555
CMPSD, 250, 258, 520,589
FCOMI, 556
CMPSS, 590
FCOMIP, 556
CMPSW, 250, 258, 520
FCOMP,472,555
CMPXCHG.521
FCOMPP, 472
CMPXCHG8Bf521
FCOS, 483, 557
COMISDt591
FDECSTP, 497, 557
COMISS, 591
FDIV.477,557
CPUID, 521
FDIVP, 478
CVTDQ2PD, 592
FDIVR,478,558
CVTDQ2PS, 592
FDIVRP, 478
CVTPD2DQ, 592
FFREE, 558
CVTPD2PI, 593
FIADD.474,554
CVTPD2PS, 593
FICOM, 472,558
CVTPI2PD, 594
FICOMP, 472, 558
CVTPI2PS, 594
FIDIV, 475
CVTPS2DQ, 594
FIDIVR, 475
CVTPS2PD, 595
FILD.469,559
CVTPS2PI, 595
FIMUL.475
CVTSD2SI, 596
FINCSTP, 497, 559
CVTSD2SS, 597
FINIT, 559
CVTSI2SD, 597
FIST, 469,559
CVTSI2SS, 597
FISTP,469,559
CVTSS2SD, 598
FISUB.474,566
CVTSS2SI, 598
FISUBR,475,567
CVTTPD2DQ, 599
FLD, 469,560
CVTTPD2PI, 598
FLD1,471,56O
CVTTPS2DQ, 599
FLDCW, 496, 560
CVTTPS2PI, 600
FLDCWR, 481
CVTTSD2SI, 600
FLDENV, 499,561
CVTTSS2sL 601
FLDL2E, 471,560
CWD, 182,518
FLDL2T,471,560
CWDE,182,518
FLDLG2,471,560
DAA,191,523
FLDLN2,471,560
DAS, 192f 524
FLDPI, 471,560
DEC, 524
FLDZ,471,560
dec, 174
FMUL, 477, 561
DIV, 524
FMULP.477
div, 179
FNCLEX,496,555
DiVPD, 601
FNINIT, 559
DIVPS, 601
FNOP, 497, 561
DIVSD, 602
FNSAVE, 497
DIVSS, 602
FNSTCW, 496,565, 566
EMMS, 569
FNSTENV, 498, 565
ENTER, 356, 525
FNSTSW, 496
F2XM1,491,553
FPATAN, 483,484,562
FABS, 479,553
FPREM, 490, 562
FADD, 476, 554
FPREM1,490,562
FADDP, 476,554
FPTAN, 483,563
FBLD, 469,554
FRNDINT, 481, 563
FBSTP, 470,554
FRSTOR, 497,563
команда (продолжение)
команда (продолжение)
FRSTPM, 500
LAR, 531
FSAVE,497,564
LDMXCSR, 603
FSCALE, 492,564
LDS, 156, 252, 532
FSETPM, 500
LEA, 156, 532
FSIN, 483, 564
LEAVE, 356, 533
FSINCOS, 483, 565
LES, 156, 252, 532
FSQRT,479,565
LFENCE, 603
FST, 469,565
LFS, 157, 532
FSTCW, 496,565,566
LGDT, 533
FSTCWR, 481
LGS, 157, 532
FSTENV, 498,565
LIDT, 533
FSTP, 469,565
LLDT, 533
FSTSW, 496
LMSW, 533
FSUB, 476, 566
LOCK, 533
FSUBP, 477,566
LODS, 250,261,533
FSUBR, 477, 567
LODSB, 250, 262, 533
FSUBRP, 477, 567
LODSD, 250, 262, 533
FTST, 472, 567
LODSW, 250, 262, 533
FUCOM,472,567
LOOP, 230, 534
FUCOMI,556
LOOPE, 230
FUCOMIP, 556
LOOPNE, 231
FUCOMP, 472, 567
LOOPNZ, 231
FUCOMPP,472,567
LOOPZ, 230
FWAIT.453,495,568
LOOPcc, 534
FXAM, 464,474, 568
LSL, 534
FXCH, 569
LSS, 157, 532
FXRSTOR, 602
LTR, 534
FXSAVE, 602
MASKMOVDQU, 603
FXTRACT.479,569
MASKMOVQ, 570
FYL2X, 492,569
MAXPD, 604
FYL2XP1,494,569
MAXPS, 604
GETFIELD, 290
MAXSD, 604
HLT, 525
MAXSS, 605
IDIV, 180,525
MFENCE, 605
IMUL, 525
MINPD, 605
imul, 179
MINPS, 605
IN, 526
MINSD, 605
INC, 171,526
MINSS, 606
INS,250,265, 527
MOV,69,147,534,535
INSB, 250, 265,527
MOVAPD, 606
INSD, 250, 265, 527
MOVAPS, 606
INSW, 250, 265,527
MOVD, 570, 606
INT 3, 527
MOVDQ2Q, 606
INTO, 527
MOVDQA, 606
INVD, 528
MOVDQU, 606
INVLPG, 528
MOVHLPS, 607
IRET, 528
MOVHPD, 607
IRETD, 528
MOVHPS, 607
Jcc, 528
MOVLHPS, 607
jcc, 224
MOVLPD, 607
JCXZ, 229
MOVLPS, 608
JECXZ, 229
MOVMSKPD, 608
JMP, 215,220,241, 530
MOVMSKPS,608
LAHF, 531
MOVNTDQ, 608
команда (продолжение)
команда (продолжение)
MOVNTI, 608
PCMPGTB, 575,612
MOVNTPD, 609
PCMPGTD, 575, 612
MOVNTPS, 609
PCMPGTW, 575, 612
MOVNTQ, 570
PEXTRW,575,612
MOVQ, 570, 609
PINSRW, 575, 613
MOVQ2DQ, 609
PMADDWD,575, 613
MOVS, 250, 253, 535
PMAXSW, 576, 613
MOVSB, 250, 254, 535
PMAXUB, 576,613
MOVSD, 250, 254, 535, 609
PMINSW, 576, 613
MOVSS, 609
PMINUB, 576,613
MOVSW, 250, 254, 535
PMOVMSKB, 576, 613
MOVSX, 182, 536
PMULHUW, 577, 613
MOVUPD.610
PMULHW, 578, 613
MOVUPS,610
PMULLW, 578, 613
MOVZX, 182, 536
PMULUDQ, 579, 613
MUL, 536
POP, 148,162,538
mul, 177
POPA, 163, 538
MULPD, 610
POPAD, 163, 538
MULPS, 610
POPAW, 163
MULSD,611
POPF, 164,539
MULSS,611
POPFD, 164, 539
NEG, 175,183,536
POPFW, 164
NOP, 537
POR, 579,613
NOT, 196, 537
PREFETCHNTn, 539
OR, 195, 289, 537
PSADBW, 580, 614
ORPD.611
PSHUFD.614
ORPS,611
PSHUFHW, 615
OUT, 537
PSHUFLW.616
OUTBS, 251
PSHUFW, 580
OUTDS, 251
PSLLD, 581,617
OUTS, 250, 265, 538
PSLLDQ,617
OUTSB, 266, 538
PSLLQ, 581,617
OUTSD, 266, 538
PSLLW, 581,617
OUTSW, 266, 538
PSRAD,581,617
OUTWS, 251
PSRAW, 581,617
PACKSSDW, 571,611
PSRLD.581,617
PACKSSWB, 571,611
PSRLDQ, 617
PACKUSWB, 572,611
PSRLQ, 581,617
PADDB, 572, 612
PSRLW, 581,617
PADDD.572, 612
PSUBBt 582,617
PADDQ,573, 612
PSUBD, 582,617
PADDSB, 573,612
PSUBQ,582,618
PADDSW, 573,612
PSUBSB,583,618
PADDUSB, 573,612
PSUBSW,583, 618
PADDUSW, 573, 612
PSUBUSB, 583, 618
PADDW, 572,612
PSUBUSW, 583,618
PAND, 574,612
PSUBW, 582,617
PANDN,574, 612
PUNPCKHBW, 618
PAUSE, 538
PUNPCKHDQ, 618
PAVGB, 574,612
PUNPCKHQDQ,618
PAVGW, 574,612
PUNPCKHWD, 618
PCMPEQB,574,612
PUNPCKLBW, 585,618
PCMPEQD, 574, 612
PUNPCKLDQ, 585, 618
PCMPEQW, 574,612
PUNPCKLQDQ, 585, 618
команда (продолжение)
команда (продолжение)
PUNPCKLWD, 585, 618
STD, 253, 548
PUSH, 148,162,540
STI, 548
PUSHA, 162,540
STMXCSR,621
PUSHAD, 163, 540
STOS, 250, 263,548
PUSHAW, 162
STOSB, 250, 264, 548
PUSHF, 164, 540
STOSD, 250, 264,548
PUSHFD, 164, 540
STOSW, 250, 264, 548
PUSHFW, 164
STR, 549
PXOR, 587,618
SUB, 549
RCL, 203,540
sub, 174
RCPPS,618
SUBPD,621
RCPSS, 619
SUBPS, 621
RCR, 203,540
SUBSD, 622
RDMSR, 541
SUBSS, 622
RDPMC, 541
SYSENTER, 549
RDTSC, 541
SYSEXIT, 550
REP, 251,542
TEST, 196,551
REPE,251,542
UCOMISD, 622
REPNE,251,542
UCOMISS, 622
REPNZ.251,542
UD2,551
REPZ, 251, 542
UNPCKHPD, 623
RET, 222, 542
UNPCKHPS, 623
ROL,201,543
UNPCKLPD, 623
ROR, 201, 543
UNPCKLPS, 623
RSM, 543
VERR, 551
RSQRTPS, 619
WAIT, 453,495, 552
RSQRTSS, 619
WBINVD, 552
SAHF, 544
WRMSR, 552
SAL, 200, 544
XADD, 183,552
SAR, 200, 544
XCHG, 149, 552
SBB, 545
XLAT, 158,553
sbb, 174
XLATB, 553
SCAS, 250, 259, 545
XOR, 196,553
SCASB, 250, 260, 545
XORPD, 624
SCASD, 250, 260, 545
XORPS,624
SCASW, 250,260,545
командная строка
SETcc, 229, 545
ml, 142
SETFIELD, 290
tasm, 129
SFENCE, 545
tlink, 134
SGDT, 546
команды
SHL, 199, 546
арифметические, 474
SHLD, 204, 547
вещественные, 476
SHR, 200, 288, 546
дополнительные, 479
SHRD, 547
целочисленные, 474
shrd, 204
ассемблера, 86,133
SHUFPD,619
загрузки констант, 471
SHUFPS, 620
обработки строк символов, 249
SLDT, 548
передачи данных, 468
SMSW, 548
сдвига, 199, 288
SQRTPD, 620
сдвига двойной точности, 203
SQRTPS, 621
сравнения данных, 472
SQRTSD, 621
трансцендентных функций, 482
SQRTSS, 621
управления сопроцессором, 494
STC, 548
цепочечные, 249
комментарийв программе на ассемблере, 86в теле макроопределения, 305компилятор ресурсов, 403компоновка программы, 134конвейер, 31конвейеризация вычислений, 22конечный автомат, 245консольноеWindows-приложение,367, 436константа, 330конструкция.BREAK, 244.CONTINUE, 244•IF, 242.REPEAT, 243.WHILE, 244контекст процедуры, 222контроллер DMA, 265конфигурационный файл tlink, 135косинус угла, 483кэшпамять, 34трасс, 39Ллексемы ассемблера, 89линейный адрес памяти, 51логические данные, 194логические операции, 194логический сдвиг, 199логическое исключающее сложение, 195логическое сложение, 195логическое умножение, 195локальная дескрипторная таблица, 56локальные блоковые метки, 234ммакроассемблер, 294макробиблиотека, 298макрогенерация, 300макродирективы, 305макрокоманды, 86, 133297макроопределения, 297макрорасширение, 300макрос MAKEINTRESOURCE, 409мантисса, 458,479массив, 269масштабированиеиндекса массива, 272регистра, 96масштабный множитель, 70математический сопроцессор, 21,448машинные командыCISC, 23RISC, 23комментарийв программе на ассемблере, 86в теле макроопределения, 305компилятор ресурсов, 403компоновка программы, 134конвейер, 31конвейеризация вычислений, 22конечный автомат, 245консольноеWindows-приложение,367, 436константа, 330конструкция.BREAK, 244.CONTINUE, 244•IF, 242.REPEAT, 243.WHILE, 244контекст процедуры, 222контроллер DMA, 265конфигурационный файл tlink, 135косинус угла, 483кэшпамять, 34трасс, 39Ллексемы ассемблера, 89линейный адрес памяти, 51логические данные, 194логические операции, 194логический сдвиг, 199логическое исключающее сложение, 195логическое сложение, 195логическое умножение, 195локальная дескрипторная таблица, 56локальные блоковые метки, 234ммакроассемблер, 294макробиблиотека, 298макрогенерация, 300макродирективы, 305макрокоманды, 86, 133297макроопределения, 297макрорасширение, 300макрос MAKEINTRESOURCE, 409мантисса, 458,479массив, 269масштабированиеиндекса массива, 272регистра, 96масштабный множитель, 70математический сопроцессор, 21,448машинные командыCISC, 23RISC, 23машинный формат команд сопроцессора, 467
комментарий
в программе на ассемблере, 86
в теле макроопределения, 305
компилятор ресурсов, 403
компоновка программы, 134
конвейер, 31
конвейеризация вычислений, 22
конечный автомат, 245
консольноеWindows-приложение,367, 436
константа, 330
конструкция
.BREAK, 244
.CONTINUE, 244
•IF, 242
.REPEAT, 243
.WHILE, 244
контекст процедуры, 222
контроллер DMA, 265
конфигурационный файл tlink, 135
косинус угла, 483
кэш
память, 34
трасс, 39
Л
лексемы ассемблера, 89
линейный адрес памяти, 51
логические данные, 194
логические операции, 194
логический сдвиг, 199
логическое исключающее сложение, 195
логическое сложение, 195
логическое умножение, 195
локальная дескрипторная таблица, 56
локальные блоковые метки, 234
м
макроассемблер, 294
макробиблиотека, 298
макрогенерация, 300
макродирективы, 305
макрокоманды, 86, 133297
макроопределения, 297
макрорасширение, 300
макрос MAKEINTRESOURCE, 409
мантисса, 458,479
массив, 269
масштабирование
индекса массива, 272
регистра, 96
масштабный множитель, 70
математический сопроцессор, 21,448
машинные команды
CISC, 23
RISC, 23
комментарий
в программе на ассемблере, 86
в теле макроопределения, 305
компилятор ресурсов, 403
компоновка программы, 134
конвейер, 31
конвейеризация вычислений, 22
конечный автомат, 245
консольноеWindows-приложение,367, 436
константа, 330
конструкция
.BREAK, 244
.CONTINUE, 244
•IF, 242
.REPEAT, 243
.WHILE, 244
контекст процедуры, 222
контроллер DMA, 265
конфигурационный файл tlink, 135
косинус угла, 483
кэш
память, 34
трасс, 39
Л
лексемы ассемблера, 89
линейный адрес памяти, 51
логические данные, 194
логические операции, 194
логический сдвиг, 199
логическое исключающее сложение, 195
логическое сложение, 195
логическое умножение, 195
локальная дескрипторная таблица, 56
локальные блоковые метки, 234
м
макроассемблер, 294
макробиблиотека, 298
макрогенерация, 300
макродирективы, 305
макрокоманды, 86, 133297
макроопределения, 297
макрорасширение, 300
макрос MAKEINTRESOURCE, 409
мантисса, 458,479
массив, 269
масштабирование
индекса массива, 272
регистра, 96
масштабный множитель, 70
математический сопроцессор, 21,448
машинные команды
CISC, 23
RISC, 23
машинный язык, 19
метка, 87, 210 механизм
вызова процедур, 222 макроподстановок, 154 переименования регистров, 36 препроцессорной обработки, 294 микроархитектура, 29
NetBurst, 30, 38
Р6, 30,34
микрооперация, 36
микропрограмма, 19, 60 модификатор
в команде перехода, 215
вызова процедуры, 222
языка, 110
модуль, 325,328
модульное программирование, 325
н
непосредственный операнд,71,91 нетерминальный символ, 88 неупакованный двоично-десятичный тип, 113
нечисла, 465
нормализованный вид, 459
нормальная форма Бэкуса-Наура, 86 нуль, 464
О
обработчик
исключений сопроцессора, 504 событий, 443
обратная польская запись
(ИОЛИЗ), 449,467 объединение, 284 объектный модуль, 129 окна диалога, 420 оконная функция, 369,398 оконное Windows-приложение, 367 округление результатов работы
команды, 481 операнд, 60,87,90 оперативная память, 18,49 оператор
!,301
$,212
%, 301 &,302
. (точка), 282
: (двоеточие),211
break, 240
continue, 240
do-while, 241
оператор (продолжение)
DUP,270
for, 241
goto, 240
if-else, 237
inline, 345
MASK, 288
PTR, 148
switch, 237
TYPE, 283
WIDTH, 288
операторы, 97
арифметические, 98
арифметические сравнения, 99
выбора, 237
выделения, 295
замены, 302
именования типа структуры, 102
индексные, 100
итерационного цикла, 241
логические, 99
переопределения
сегмента, 100
типа, 100
получения
сегментной информации, 102
смещения выражения, 102
сдвига, 98
условные, 237
цикла с постусловием, 241
операция распространения знака, 181
описание шаблона структуры, 281
определение структуры, 281
основание системы счисления, 458
особый случай, 454
отладчик TD, 137
отрицание, 194
п
передача аргументов в процедуру, 330
в языках высокого уровня, 342
по адресу, 337
по значению, 336, 355
по ссылке, 355
переменная, 330
перемещаемый операнд, 92
переполнение
мантиссы, 175
разрядной сетки, 172
перерисовка изображений, 413
пересылка битов, 207
переход
косвенный
внутрисегментный, 216
межсегментный, 218
регистровый межсегментный, 218
переход (продолжение)
прямой
внутрисегментный, 216
короткий внутрисегментный, 216
межсегментный, 217
поиск элемента в массиве, 276
порт ввода-вывода, 93, 149
порядок числа, 458,459,479
постбайт, 64
постфиксная запись, 468
поток, 388
п редо п рсделенные имена
@@, 235
@В, 235
@F, 235
предсказание перехода, 23, 32
префикс, 87
блокировки шины, 61
замены сегмента, 61, 102
машинной команды, 61
повторения, 61
программного сегмента, 214
размера
адреса, 61
операнда, 62
смены алфавита, 63
принцип
микропрограммирования, 28
микропрограммного управления, 19
программная модель
процессора, 30, 40
сопроцессора, 448
программные отладчики, 136
пролог, 336, 350
протокол MESI, 35
процедура, 219, 327
процесс, 388
процессор, 18
процессоры Intel, 21
i4004,21
i8008,21
i80286, 22
i80386, 22
i80486, 23
i8080,21
i8086,21
i8088,21
Pentium II, 24
Pentium IV, 24
Pentium Pro, 24
Pentium-60, 23
псевдооператор
(равно), 295
EQU, 295
расширенный программируемый
контроллер прерываний APIC
IC, 23
расширенный формат вещественного
числа, 456
регистр
CWR, 449, 454
DPR, 450
EFLAGS/FLAGS, 46, 292
IPR, 450
SWR, 449, 453, 500
TWR, 450, 455
регистровый операнд, 93
регистры
аккумуляторы, 94
общего назначения, 44
процессора, 18
сегментные, 45, 69
системные, 43
слова тегов, 450
состояния и управления, 46
состояния сопроцессора, 449
стека сопроцессора, 455
тегов, 455
указателей
данных, 450
команд, 450
флагов, 46
редактор ресурсов, 406
режим работы процессора, 42
виртуального процессора 8086, 22,42
защищенный, 22, 42
реальный, 42
системного управления, 42
режимы округления, 481
ресурс Windows-приложений, 406
ресурсный файл, 135
c
сегмент, 50, 86
сегментированная модель памяти, 50
селектор, 56
сигнальное нечисло, 465
синтаксис формального аргумента
макроопределения, 302
синтаксическая диаграмма, 86
синус угла, 483
синхронные сообщения Windows, 393, 399 система
машинных команд, 18, 58
представления информации
в компьютере, 194
счисления, 74
система (продолжение)
непозиционная, 74
позиционная, 75
системная шина, 18
скалярная архитектура, 31
сканер, 245
слово, 111
служебное слово VARARG, 342, 343
сообщение, 369, 394
сортировка массива, 278
спекулятивное исполнение, 33
специальные численные значения, 463
сплошная модель памяти, 51
спокойное нечисло, 465
среда сопроцессора, 497
стандарт
IEEE-754,21,448
IEEE-854,448
стандартные директивы сегментации, 106
стартовый код, 378, 387
стек, 160, 334
стек сопроцессора, 449
страничная модель памяти, 50
структура, 280
WNDCLASS, 385
WNDCLASSEX, 385
структурное программирование, 325
суперскалярная архитектура, 23, 31 счетчик
адреса, 92,133, 212
размещения, 93
T
таблица
ASCII, 123
истинности, 194
перекрестных ссылок, 133
псевдонимов регистров, 36
таймер, 152.
терминальный символ, 88
тестирование программы, 135
технология HyperThreading, 39
тип
данных, 110
операнда, 60
точка входа в программу, 138
трансляция программы, 129
трансцендентные функции, 483
трехходовая суперскалярная конвейерная архитектура, 31, 32
У
указатель на память, 112
упакованные десятичные числа, 458
упакованный двоично-десятичный
тип, 113
упрощенные директивы сегментации, 106
условный оператор, 237
устройство
ввода-вывода, 18
выборки/декодирования, 34,35
диспетчеризации/исполнения, 34,37
микропрограммного управления, 29
связи с памятью, 35, 38
удаления и восстановления, 37
шинного интерфейса, 22, 34
утилита
cxe2bin, 214
make.exe, 144
nmake.exe, 144
учетверенное слово, 111
Ф
файл
СОМ, 213
Def, 383
библиотеки, 134
карты, 134
листинга, 131
определений, 135,402
фактические аргументы, 300, 330
физический адрес, 49
флаг направления df, 252
фон Нейман, 28
форма Бэкуса-Наура, 246
формальная логика, 193
формальныеаргументы,300, 330
функциональнаядекомпозиция
задачи, 325
функция RegisterClass, 385
X
характеристика, 459
Ц
целое число, 90
целый тип
без знака, 112
сознаком, 111
центральный процессор, 18
цепочечные команды, 249
цепочка, 89, 112
цикл, 230, 240
вложенный, 233
обработки сообщений, 369, 396
с предусловием while, 240
циклический сдвиг, 201
ч
частичный арктангенс угла, 483
частичный остаток от деления, 490
частичный тангенс угла, 483
Ш
шаблон записи, 286
шина
ISA, 18
PCI, 18
расширения, 18
системная, 18
э
экранный буфер, 436
эпилог, 336, 352
эффективный адрес, 50, 61
я
язык ассемблера, 20
Учебное издание
Юров Виктор ИвановичAssembler. Учебник для вузов2 издание
Главный редактор Е. СтрогановаЗаведующий редакцией И. КорнеевРуководитель проекта Ю. СуркисЛитературный редактор А. ЖдановХудожник Я. БиржаковИллюстрации Л. Харитонов, В. ШендероваКорректоры Я. Рощина, Н. ЛукинаВерстка Л. Харитонов
Лицензия ИД № 05784 от07.09.01
Подписано к печати 22.07.03. Формат 70x100 1/16
Усл. п. л. 51,6. Доп. тираж 4 500. Заказ 256
ООО «Питер Принт»
196105, Санкт-Петербург, ул. Благодатная, 67
Налоговая льготаобщероссийский классификатор продукции
ОК005-93, том 2; 95 3005 — литература учебная.
Отпечатано с готовых диапозитивов
в ФГУП ордена Трудового Красного Знамени «Техническая книга»Министерства Российской Федера инк по делам печати,телерадиовещания и средств массовых коммуникаций
198005, Санкт-Петербург, Измайловский пр., 29.
КЛУБКЛУБП РОФЕССИОНАЛ
КЛУБ
КЛУБ
^^^^,-嚥巒鍔Z
В 1997году по инициативе генерального директора Издательского дома «Питер» Валерия Степанова и при поддержке деловых кругов города в Санкт-Петербурге был основан «Книжный клуб Профессионал». Он собрал под флагом клуба профессионалов своего дела, которых объединяет постоянная тяга к знаниям и любовь к книгам. Членами клуба являются лучшие студенты и известные практики из разных сфер деятельности, которые хотят стать или уже стали профессионалами в той или иной области.
Как и все развивающиеся проекты, с течением времени книжный клуб вырос в «Клуб Профессионал». Идею клуба сегодня формируют три основные «клубные» функции:
неформальное общение и совместный досуг интересных людей;
участие в подготовке специалистов высокого класса (семинары, пакеты книг по специальной литературе);
формирование и высказывание мнений современного профессионала (при встречах и на страницах журнала).
КАК ВСТУПИТЬ В КЛУБ?
Для вступления в «Клуб Профессионал» вам необходимо:
ознакомиться с правилами вступления в «Клуб Профессионал» на страницах журнала или на сайтеwww.piter.com;
выразить свое желание вступить в «Клуб Профессионал» по электронной почте postbook@piter.com или по тел.(812)103-73-74;
заказать книги на сумму не менее 500 рублей в течение любого времени или приобрести комплект «Библиотека профессионала».
«БИБЛИОТЕКА ПРОФЕССИОНАЛА»
Мы предлагаем вам получить все необходимые знания, подписавшись на «Библиотеку профессионала». Она для тех, кто экономит не только время, но и деньги. Покупая комплект - книжную полку «Библиотека профессионала», вы получаете:
скидку15%от розничной цены издания, без учета почтовых расходов;
при покупке двух или более комплектов-- дополнительную скидку 3%;
членство в «Клубе Профессионал»;
• подарок- журнал «Клуб Профессионал».
Закажите бесплатный журнал «Клуб Профессионал».
Г>^пт^^
, WWW.PITER.COM
i ;UJnna управления
Устройства ввода/вывода
Рис. 1.2. Структурная схема персонального компьютера
ности машинных команд. Каждая машинная команда является своего рода раздражителем для возбуждения того или иного безусловного рефлекса. Реакция на этот раздражитель всегда однозначна и «зашита» в блоке микропрограммного управления в виде микропрограммы. Эта микропрограмма и реализует действия по выполнению машинной команды, но уже на уровне сигналов, подаваемых на те или иные логические схемы компьютера, тем самым управляя различными подсистемами компьютера. В этом состоит так называемый принцип микропрограммного управления. Продолжая аналогию с человеком, отметим: для того чтобы компьютер правильно «питался», придумано множество операционных систем, компиляторов сотен языков программирования и т. д. Но все они являются, по сути, лишь блюдом, на котором по определенным правилам доставляется пища (программы) желудку (компьютеру). Только (вот досада!) желудок компьютера любит диетическую, однообразную пищу — подавай ему информацию структурированную, в виде строго организованных последовательностей нулей и единиц, комбинации которых и составляют машинный язык.
*J 3.37* 10**-4932~
Денормализованные числа
Рис. 17.11. Положение денормализованных вещественных чисел на числовой шкале
Как нам уже известно, сопроцессор хранит числа в нормализованном виде. По мере приближения чисел к нулю ему все труднее «вытягивать» их значения к нормализованному виду, то есть к такому виду, чтобы первой значащей цифрой мантиссы была единица. Размерность разрядной сетки, отведенной в форматах вещественных чисел сопроцессора для представления характеристики, не безгранична. Поэтому при определенных значениях числа в расширенном формате значение характеристики становится равным нулю (рис. 17.11). Но на самом деле число отлично от нуля, то есть это «не настоящий» численный нуль. Таким образом, между
Минимальное положительное денормализованное число:
I о| 00.. оо| ооооо• • • 001|
79 7864 63О
Минимальное отрицательное денормализованное число:
11|оо..ооI оооо.....0011
79 7864 63О
Максимальное положительное денормализованное число:
| о| 00.. 00| 11111111|
79 7864 63О
Максимальное отрицательное денормализованное число:
| 00.. оо Inil
79 7864 63О
Рис. 17.12.Диапазон представимых в сопроцессоре денормализованных чисел
УЧЕБНИК / ДЛ Я ВУЗОВ
В. И. Юров
ASSEMBLER
2-еиздание
Допущено Министерством образования Российской Федерациив качестве учебного пособия для студентов высших учебных заведений,обучающихся по направлению подготовки дипломированныхспециалистов «Информатика и вычислительная техника»
300.piter.com
Издательская программа
300 лучших учебников для высшей школыв честь 300-летия Санкт-Петербурга
осуществляется при поддержке Министерства образования РФ
Москва • Санкт-Петербург • Нижний Новгород • ВоронежРостов-на-Дону • Екатеринбург • СамараКиев • Харьков • Минск
2003
ББК32.973-018.1я7
УДК 681.3.06(075)
Ю70
Рецензенты:
Гурко А. В., кандидат технических наук, доцент кафедры военной кибернетики филиала ВУ ПВО
Тузов В. А., доктор физико-математических наук, профессор кафедры «Технологии программирования»Санкт-Петербургского государственного университета
Ю70 Assembler. Учебник для вузов. 2-е изд. / В. И. Юров — СПб.: Питер, 2003. — 637 с.: ил.
ISBN 5-94723-581-1
В учебнике рассматриваются вопросы программирования на языке ассемблера для компьютеров на базе микропроцессоров фирмы Intel. Основу книги составляет материал, являющийся частью курса, читаемого автором в высшем учебном заведении и посвященного вопросам системного программирования. По сравнению с первым изданием учебник существенно переработан. Исправлены ошибки и неточности. Добавлено описание команд для Intel-совместимых процессоров (до Pentium IV включительно).
Книга будет полезна студентам вузов, программистам и всем желающим изучить язык Assembler.
Допущено Министерством образования Российской Федерации в качестве учебного пособия для студентов высших учебных заведений, обучающихся по направлению подготовки дипломированных специалистов «Информатика и вычислительная техника».
ББК 32.973-018.1я7
УДК681.3.06(075)
Информация, содержащаяся в данной книге, получена из источников, рассматриваемых издательством как надежные. Тем не менее, имея в виду возможные человеческие или технические ошибки, издательство не может гарантировать абсолютную точность и полноту приводимых сведений и не несет ответственности за возможные ошибки, связанные с использованием книги.
ISBN 5-94723-581 -1ISBN 5-94723-581 -1© ЗАО Издательский дом «Питер», 2003
ISBN 5-94723-581 -1
ISBN 5-94723-581 -1
Краткое содержание
Предисловие13
Благодарности16
От издательства16