Text
                    В. Юров
С. Хорошенко
учебный
курс
[^ППТЕР
Санкт-Петербург
Москва • Харьков • Минск
1999

В. Юров, С. Хорошенко Assembler: учебный курс Главный редактор Заведующий редакцией Литературный редактор Художественный редактор Корректоры Верстка В. Усманов А. Пасечник Ф. Андреев И. Половодов Н. Рощина, Е. Тигонен Л Чернышова ББК 32.973.2-018Я7 УДК 681.3.06 Юров В., Хорошенко С. Ю78 Assembler: учебный курс — СПб: Питер Ком, 1999. — 672 с.: ил. ISBN 5-314-00047-4 До сих пор программирование на языке низкого уровня Assembler было прерогативой узкого круга профессионалов Впервые читателю предлагается учебное пособие, способствующее формированию фундаментальных знаний по архитектуре процессора Intel Pentium и основам низкоуровневого програм - мирования на языке Assembler, не требующее никакой начальной подготовки, кроме определенного опыта работы с персональным компьютером. Издание предназначено для специалистов в области информатики, программистов и пользователей, интересующихся предметом Написанная профессиональным преподавателем, книга может быть рекомендована в качестве учебного пособия для студентов вузов. © В. Юров, 1999 © Серия, оформление, издательство «Питер Ком», 1999 ISBN 5-314-00047-4 Все упомянутые в данном издании товарные знаки и зарегистрированные товарные знаки принадлежат своим законным владельцам. Информация, содержащаяся в данной книге, получена из источников, рассматриваемых издательством как надежные Тем не менее, имея в виду возможные человеческие или технические ошибки, издательство не может гарантировать абсолютную точность и полноту приводимых сведений и не несет ответственности за возможные ошибки, связанные с использованием книги Издательство «Питер Ком». 196105, С -Петербург, Благодатная ул., 67. Лицензия ЛР № 065360 от 20.08 97. Подписано в печать 22 12.98. Формат 70Х100'/|6. Усл п. л 54,6 Печать офсетная Доп. тираж 5000 экз Заказ N» 237. Отпечатано с фотоформ в ГПП «Печатный Двор» Государственного комитета РФ по печати. 197110. С-Петербург, Чкаловский пр, 15
Краткое содержание Предисловие.............................................16 Урок 1. Общие сведения об ЭВМ.........................20 Урок 2. Архитектура персонального компьютера..........31 Урок 3. Разработка простой программы на ассемблере....54 Урок 4. Создание программы на ассемблере..............64 Урок 5. Структура программы на ассемблере.............83 Урок 6. Система команд микропроцессора...............111 Урок 7. Команды обмена данными.......................131 Урок 8. Арифметические команды.......................153 Урок 9. Логические команды...........................186 Урок 10. Команды передачи управления..................205 Урок 11. Цепочечные команды...........................236 Урок 12. Сложные структуры данных.....................259 Урок 13. Макросредства языка ассемблера...............292 Урок 14. Модульное программирование...................332 Урок 15. Прерывания...................................377
6 Краткое содержание Урок 16. Защищенный режим работы микропроцессора................407 Урок 17. Обработка прерываний в защищенном режиме.....438 Приложение 1. Опции транслятора TASM и редактора связей TLINK..........................................463 Приложение 2. Описание системы команд микропроцессоров Intel................................468 Приложение 3. Таблицы кодов символов.........................592 Приложение 4. Функции прерываний 1 Oh (BIOS) и 21 h (DOS) ...600 Приложение 5. Директивы управления листингом.................604 Приложение 6. Значения полей инициализации...................612 Приложение 7. Библиотека арифметических подпрограмм..........617 Приложение 8. Пример работы со структурой....................636 Приложение 9. Текст макроопределения SHOW....................645 Приложение 10. Предупреждающие сообщения и сообщения об ошибках......................................652
Содержание Предисловие......................................................16 Урок 1. Общие сведения об ЭВМ................................20 Урок 2. Архитектура персонального компьютера.....................31 Архитектура ЭВМ...........................................32 Набор регистров...........................................36 Пользовательские регистры Организация памяти........................................44 Сегментированная модель памяти ♦ Формирование физического адреса в реальном режиме Типы данных...............................................48 Формат команд.............................................51 Обработка прерываний......................................52 Урок 3. Разработка простой программы на ассемблере...............54 Урок 4. Создание программы на ассемблере.........................64 Создание объектного модуля (трансляция программы).........68 Создание загрузочного модуля (компоновка программы).......75 Отладчик Turbo Debugger...................................77 Урок 5. Структура программы на ассемблере........................83 Синтаксис ассемблера......................................85 Директивы сегментации.....................................96 Описание простых типов данных ассемблера.................104
8 Содержание Урок 6. Система команд микропроцессора...................................111 Системы счисления...............................................112 Двоичная система счисления ♦ Шестнадцатеричная система счисления ♦ Десятичная система счисления Перевод чисел из одной системы счисления в другую...............116 Перевод в десятичную систему счисления ♦ Перевод в двоичную систему счисления * Перевод в шестнадцатеричную систему счисления ♦ Числа со знаком Структура машинной команды.......................................120 Способы задания операндов команды Функциональная классификация машинных команд.....................129 Урок 7. Команды обмена данными..........................................131 Команды пересылки данных.........................................133 Команды ввода-вывода в порт......................................135 Команды работы с адресами и указателями.........................143 Команды преобразования данных....................................145 Команды работы со стеком.........................................147 Урок 8. Арифметические команды.........................................153 Обзор группы арифметических команд и данных.....................154 Целые двоичные числа ♦ Десятичные числа Арифметические операции над целыми двоичными числами............159 Сложение двоичных чисел без знака ♦ Сложение двоичных чисел со знаком ♦ Вычитание двоичных чисел без знака ♦ Вычитание двоичных чисел со знаком ♦ Вычитание и сложение операндов большой размерности ♦ Умножение двоичных чисел без знака * Умножение двоичных чисел со знаком ♦ Деление двоичных чисел без знака ♦ Деление двоичных чисел со знаком Вспомогательные команды для целочисленных операций..............172 Команды преобразования типов ♦ Другие полезные команды Арифметические операции над двоично-десятичными числами.........174 Арифметические действия над неупакованными BCD-числами ♦ Арифметические действия над упакованными BCD-числами Урок 9. Логические команды...............................................186 Логические данные................................................188 Логические команды...............................................189 Команды сдвига...................................................193 Команды линейного сдвига ♦ Команды циклического сдвига ♦ Дополнительные команды сдвига
Содержание 9 Примеры работы с битовыми строками..............................200 Рассогласование битовых строк ♦ Вставка битовых строк ♦ Извлечение битовых строк ♦ Пересылка битов Урок 10. Команды передачи управления................................205 Безусловные переходы............................................211 Команда безусловного перехода jmp ф Процедуры Условные переходы...............................................222 Команда сравнения стр ♦ Команды условного перехода и флаги ♦ Команды условного перехода и регистр есх/сх Организация циклов..............................................228 Урок 11. Цепочечные команды..............................................236 Пересылка цепочек...............................................241 Команда movs ♦ Команды пересылки байтов, слов и двойных слов Сравнение цепочек...............................................244 Команда cmps ♦ Команды сравнения байтов, слов и двойных слов Сканирование цепочек............................................248 Команда seas ♦ Сканирование строки байтов, слов, двойных слов Загрузка элемента цепочки в аккумулятор.........................250 Команда lods ♦ Загрузка в регистр al/ax/eax байтов, слов, двойных слов Перенос элемента из аккумулятора в цепочку......................253 Команда stos ♦ Сохранение в цепочке байта, слова, двойного слова из регистра al/ax/eax Ввод элемента цепочки из порта ввода-вывода.....................256 Вывод элемента цепочки в порт ввода-вывода......................257 Урок 12. Сложные структуры данных........................................259 Массивы.........................................................261 Описание и инициализация массива в программе ♦ Доступ к элементам массива ♦ Типовые операции с массивами Структуры.......................................................276 Описание шаблона структуры ♦ Определение данных с типом структуры ♦ Методы работы со структурой Объединения.....................................................281 Записи..........................................................284 Описание записи ♦ Определение экземпляра записи ♦ Работа с записями ♦ Дополнительные возможности обработки Урок 13. Макросредства языка ассемблера...........................292 Псевдооператоры equ и в.........................................295 Макрокоманды....................................................297
10 Содержание Макродирективы.................................................308 Директивы WHILE и REPT ♦ Директива IRP ♦ Директива IRPC Директивы условной компиляции..................................311 Директивы компиляции по условию ♦ Директивы генерации ошибок Константные выражения в условных директивах....................324 Дополнительное управление трансляцией..........................326 Урок 14. Модульное программирование.....................................332 Технологии программирования....................................334 Структурное программирование ♦ Концепция модульного программирования Процедуры в языке ассемблера...................................337 Организация интерфейса с процедурой Связь ассемблера с языками высокого уровня.....................356 Связь Pascal—ассемблер ♦ Связь С—.ассемблер Урок 15. Прерывания.....................................................377 Контроллер прерываний..........................................381 Программирование контроллера прерываний i8259A.................386 ICW1 — определить особенности последовательности приказов ♦ ICW2 — определение базового адреса ♦ ICW3 — связь контроллеров ♦ ICW4 — дополнительные особенности обработки прерываний ♦ OCW1 — управление регистром масок IMR ♦ OCW2 — управление приоритетом ♦ OCW3 — общее управление контроллером ♦ Каскадирование микросхем i8259A Реальный режим работы микропроцессора..........................395 Обработка прерываний в реальном режиме Урок 16. Защищенный режим работы микропроцессора........................407 Системные регистры микропроцессора.............................410 Регистры управления ♦ Регистры системных адресов ♦ Регистры отладки Структуры данных защищенного режима............................413 Пример программы защищенного режима............................418 Подготовка таблиц глобальных дескрипторов GDT ♦ Запрет обработки аппаратных прерываний ♦ Переключение микропроцессора в защищенный режим ♦ Работа в защищенном режиме ♦ Переключение микропроцессора в реальный режим ♦ Разрешение прерываний ♦ Стандартное для MS-DOS завершение работы программы Урок 17. Обработка прерываний в защищенном режиме................438 Шлюз ловушки...................................................445 Шлюз прерывания................................................447
Содержание 11 Шлюз задачи........................................................447 Инициализация таблицы IDT 4 Обработчики прерываний Программирование контроллера прерываний i8259A......................451 Загрузка регистра IDTR.............................................451 Приложение 1. Опции транслятора TASM и редактора связей TLINK................................................................463 Приложение 2. Описание системы команд микропроцессоров Intel....468 AAA (Ascii Adjust after Addition)..................................470 AAD (Ascii Adjust before Division).................................472 AAM (Ascii Adjust after Multiply)..................................473 AAS (Ascii Adjust after Substraction)..............................474 ADC (Addition with Carry)..........................................476 ADD (ADDition).....................................................477 AND (logical AND)..................................................479 BOUND (check array BOUNDs).........................................480 BSF (Bit Scan Forward).............................................482 BSR (Bit Scan Reverse).............................................483 BSWAP (Byte SWAP)..................................................484 ВТ (Bit Test)......................................................485 BTC (Bit Test and Complement)......................................486 BTR (Bit Test and Reset)...........................................487 BTS (Bit Test and Set).............................................488 CALL (CALL)........................................................489 CBW/CWDE (Convert Byte to Word/Convert Word to Double Word Extended)..........................................................490 CLC (CLear Carry flag).............................................491 CLD (CLear Direction flag).........................................492 CLI (CLear Interrupt flag).........................................493 CMC (CoMplement Carry flag)........................................493 CMP (CoMPare operands).............................................494 CMPS/CMPSB/CMPSW/CMPSD (CoMPare String Byte/Word/Double word operands).........................................*...........495 CMPXCHG (CoMPare and eXCHanGe).....................................498 CWD (Convert Word to Double word)..................................499
12 Содержание CDQ (Convert Double word to Quod word)................................500 DAA (Decimal Adjust for Addition).....................................500 DAS (Decimal Adjust for Subtraction)..................................502 DEC (DECrement operand by 1)..........................................503 DIV (DIVide unsigned).................................................504 ENTER (setup parameter block for ENTERing procedure)..................505 HLT(HaLT).............................................................509 IDIV (Integer DIVide).................................................511 IMUL (Integer MULtiply)...............................................512 IN (INput operand from port)..........................................514 INC (INCrement operand by 1)..........................................515 INS/INSB/INSW/INSD (Input String Byte/Word/Double word operands).............................................................516 INT (INTerrupt).......................................................517 INTO (INTerrupt if Overflow)..........................................518 IRET/IRETD (Interrupt RETurn).........................................520 Jcc (Jump it condition) JCXZ/JECXZ (Jump if CX=Zero/ Jump if ECX=Zero).....................521 JMP (JuMP)............................................................524 LAHF (Load AH register from register Flags)...........................525 LDS/LES/LFS/LGS/LSS (Load pointer into ds/es/fs/gs/ss segment register).............................................................526 LEA (Load Effective Address)..........................................527 LEAVE (LEAVE from procedure)..........................................528 LGDT (Load Global Descriptor Table)...................................529 LIDT (Load Interrupt Descriptor Table)................................530 LODS/LODSB/LODSW/LODSD (LOad String Byte/Word/Double word operands).............................................................532 LOOP (LOOP control by register ex)....................................533 LOOPE/LOOPZ (LOOP control by register ex not equal 0 and ZF=1)........534 LOOPNE/LOOPNZ (LOOP control by register ex not equal 0 and ZF=0)...........................................................534 MOV (MOVe operand)....................................................535 MOV (MOVe operand to/from system registers).....................536
Содержание 13 MOVS/MOVSB/MOVSW/MOVSD (MOVe String Byte/Word/Double word)................................................................537 MOVSX (MOVe and Sign extension)......................................539 MOVZX (MOVe and Zero extension)......................................540 MUL (MULtiply).......................................................541 NEG (NEGate operand).................................................543 NOP (No OPeration)...................................................544 NOT (NOT operand)....................................................544 OR (logical OR)......................................................545 OUT (OUT operand to port)............................................546 OUTS/OUTSB/OUTSW/OUTSD (OUTput Byte/Word/Double word String to port)......................................................547 POP (POP operand from the stack).....................................548 POPA (POP All general registers from the stack)......................549 POPAD (POP All general Double word registers from the stack).........550 POPE (POP Flags register from the stack).............................551 POPFD (POP eFlags Double word register from the stack)...............552 PUSH (PUSH operand onto stack).......................................553 PUSHA (PUSH All general registers onto stack)........................554 PUSHAD (PUSH All general Double word registers onto stack)...........555 PUSHF (PUSH Flags register onto stack)...............................556 PUSHFD (PUSH eFlags Double word register onto stack).................557 RCL (Rotate operand through Carry flag Left).........................558 RCR (Rotate operand through Carry flag Right)........................559 REP/REPE/REPZ/REPNE/REPNZ (REPeat string operation)..................561 RET/RETF (RETurn/RETurn Far from procedure)..........................562 ROL (Rotate operand Left)............................................564 ROR (Rotate operand Right)...........................................565 SAHF (Store AH register into register Flags).........................566 SAL (Shift Arithmetic operand Left)..................................567 SAR (Shift Arithmetic operand Right).................................568 SBB (SuBtract with Borrow)...........................................569 SCAS/SCASB/SCASW/SCASD (SCAn String Byte/Word/Double word)...........570
14 Содержание SETcc (byte SET on condition)......................................572 SGDT (Store Global Descriptor Table)...............................574 SIDT (Store Interrupt Descriptor Table)............................575 SHL (SHift logical Left)...........................................576 SHLD (SHift Left Double word)......................................577 SHR (SHift logical Right)..........................................578 SHRD (SHift Right Double word).....................................579 STC (Set Carry Flag)...............................................580 STD (SeT Direction Flag)...........................................581 STI (SeT Interrupt flag)...........................................582 STOS/STOSB/STOSW/STOSD.............................................583 SUB (SUBtract).....................................................585 TEST (TEST operand)................................................586 XADD (eXchange and ADD)............................................587 XCHG (eXCHanGe)....................................................588 XLAT/XLATB (transLATe Byte from table).............................589 XOR (logical eXclusive OR).........................................590 Приложение 3. Таблицы кодов символов.......................................592 Приложение 4. Функции прерываний 1 Oh (BIOS) и 21 h (DOS)..................600 Приложение 5. Директивы управления листингом...............................604 Общие директивы управления листингом...............................605 %LIST и %NOLIST (.LIST и .XLIST ♦ %CTLS и %NOCTLS ♦ %SYMS и %NOSYMS Директивы вывода текста включаемых файлов..........................606 %INCL и %NOINCL Директивы вывода блоков условного ассемблирования..................606 %CONDS и %NOCONDS (.LFCOND и .SFCONDS) Директивы вывода макрорасширений...................................607 %MACS (.LALL) и %NOMACS (.SALL) Директивы вывода листинга перекрестных ссылок....................607 Директивы изменения формата листинга...............................607 .PAGE ♦ %PAGESIZE (.PAGESIZE) ♦ %NEWPAGE ♦ %BIN ♦ %DEPTH ♦ %LINENUM ♦ %TRUNC и %NOTRUNC ♦ %PCNT ♦ %TITLE ♦ %SUBTTL ♦ %TABSIZE ♦ %TEXT
Содержание 15 Приложение 6. Значения полей инициализации.........................612 DB (Define Byte) — определить байт.........................613 DW (Define Word) — определить слово........................613 DD (Define Double word) — определить двойное слово.........614 DQ (Define Quarter word) — определить учетверенное слово...614 DF (Define Far word) — определить указатель дальнего слова.615 DP (Define Pointer) — определить указатель 48 бит..........615 DT (Define Ten Bytes) — определить 10 байт.................615 Приложение 7. Библиотека арифметических подпрограмм................617 Подпрограммы для двоичных чисел............................618 Подпрограммы для двоично-десятичных (BCD) чисел............627 Приложение 8. Пример работы со структурой..........................636 Приложение 9. Текст макроопределения SHOW..........................645 Приложение 10. Предупреждающие сообщения и сообщения об ошибках.........................................................652 Сообщения об ошибках.......................................653 Сообщения о фатальных ошибках..............................664
Предисловие Эта книга посвящена одному из самых старых из существующих сегодня язы- ков программирования - ассемблеру. Интересно проследить, начиная со време- ни появления первых компьютеров и заканчивая сегодняшним днем, за транс- формациями представлений об этом языке у программистов. Когда-то это был основной язык, без знания которого нельзя было заставить компьютер сделать что-либо полезное. Постепенно ситуация менялась. Появлялись более удобные средства общения с компьютером. Но в отличие от других языков ассемблер не умирал, более того, он не мог сделать этого в принципе. Почему? Чтобы отве- тить на этот вопрос, нужно понять, что такое язык ассемблера. Если коротко, то ассемблер — это символическое представление машинного языка. Все про- цессы в машине на самом низком, аппаратном уровне приводятся в действие только командами (инструкциями) машинного языка. Отсюда понятно, что, несмотря на общее название, язык ассемблера для каждого типа компьютера свой. Это касается и внешнего вида программ, написанных на ассемблере, и идей, отражением которых этот язык является. По-настоящему решить пробле- мы, связанные с аппаратурой, невозможно без знания ассемблера. Программист или любой другой пользователь может применять любые высокоуровневые средства, вплоть до программ построения виртуальных миров, и, возможно, даже не подозревать, что на самом деле компьютер выполняет не команды язы- ка, на котором написана его программа, а их трансформированное представле- ние в форме скучной и унылой последовательности команд совсем другого языка — машинного. А теперь представим, что у такого пользователя возникла нестандартная проблема или просто что-то не получается. К примеру, его про- грамма должна работать с некоторым необычным устройством или выполнять другие действия, связанные с непосредственным обращением к аппаратуре. И вот здесь-то и начинается «совсем другая история». Каким бы умным ни был программист, каким бы хорошим ни был язык, на котором он написал свою чудную программу, без знания ассемблера ему не обойтись. И не случайно практически все компиляторы языков высокого уровня содержат средства свя- зи своих модулей с модулями на ассемблере либо поддерживают выход на ас- семблерный уровень программирования. Конечно, время компьютерных уни- версалов уже прошло. Как говорится, «нельзя объять необъятное». Но есть нечто общее, своего рода фундамент, на котором строится любое сколько-ни-
Предисловие 17 будь серьезное компьютерное образование. Это — знание принципов работы ком- пьютера, его архитектуры и языка ассемблера, отражающего устройство компью- тера. Мы исходим именно из того положения, что знание данных вопросов явля- ется частью компьютерного образования. В книге рассматриваются вопросы программирования на языке ассемблера для компьютеров на базе микропроцессоров фирмы Intel. Основу книги составляет материал, являющийся частью курса, читаемого авторами в высшем учебном заведении, посвященного вопросам системного программирования. Это нало- жило отпечаток не только на методику изложения материала, но и позволило расставить необходимые акценты на тех вопросах, которые обычно вызывают трудности у студентов. Но научить — это только одна цель книги. Вторая цель книги — послужить хорошим справочником по языку ассемблера. Ведь мало знать набор и назначение команд. Смысл многих команд далеко не очевиден, а некоторые из них имеют свойства, которыми можно воспользоваться в ситуа- циях, когда команда применяется не по своему прямому назначению. Для та- ких команд приводятся алгоритмы, в контексте которых эти команды исполь- зуются. Знание таких особенностей и их воплощение в программе могут вызвать зависть и восхищение у ваших оппонентов. И ради этого стоит потратить время на оптимизацию и найти оптимальную реализацию вашей идеи в форме компь- ютерной программы. И это еще не все. Авторы, будучи достаточно давно связа- ными с вычислительной техникой, помнят то время, когда компьютеры серии ЕС ЭВМ (единая серия электронно-вычислительных машин) были у нас в стране самыми популярными. За последний десяток лет все стремительно переменилось. Полностью обновился парк машин. И хотя не все изменения можно приветство- вать, тем не менее сам процесс перехода позволил авторам накопить некоторый опыт, который также нашел отражение на страницах этой книги. Исходя из вышесказанного, авторы адресуют книгу следующим категориям чита- телей: О молодым людям, школьникам, углубленно интересующимся вопросами про- граммирования для компьютеров на базе микропроцессоров Intel; О студентам вузов, готовящимся стать профессиональными программистами и изучающим архитектуру микропроцессоров Intel и язык ассемблера в рамках соответствующих дисциплин; О специалистам, профессионально занимающимся программированием и же- лающим освоить ассемблер для решения стоящих перед ними задач; О всем, кто интересуется вопросами программирования для микропроцессоров Intel на низком уровне или просто желает познакомиться с тем, как устроен и работает компьютер. Так как ассемблер является символическим представлением машинного языка, то он неразрывно связан с архитектурой самого микропроцессора. По ходу вне- сения изменений в его архитектуру совершенствуется и язык ассемблера. По этой причине книга стремится решить комплексную задачу — не просто рас- смотреть ассемблер как еще один из сотен языков программирования, а показать
18 Предисловие неразрывную связь его конструкций с архитектурой микропроцессора. Изложе- ние материала ведется в форме уроков. На первых двух уроках читатель узнает, что представляет собой компьютер, что такое архитектура микропроцессора и компьютера в целом. При рассмотрении этого материала становится очевидной роль языка ассемблера как выразителя архитектуры компьютера. На третьем и четвертом уроках читатель познакомится с типичной программой на языке ассемблера и поймет, что представляет собой «ассемблерный» уро- вень программирования. Читатель также познакомится со средствами по- строения исполняемых модулей и компоновщиком. Кроме того, на четвертом уроке читатель узнает о средствах, которые помогут ему выйти из затрудни- тельных положений, когда программа, написанная на ассемблере (и не только), отказывается работать. На пятом и шестом уроках читатель узнает, как правильно оформить программу на ассемблере, и познакомится с ее синтаксическими конструкциями. В конце шестого урока приведена классификация машинных команд, в соот- ветствии с которой будет вестись их обсуждение на последующих уроках (уроки 7-11). Вторая часть книги, начиная с урока 12, посвящена углубленному изучению воп- росов программирования с использованием языка ассемблера. Так, на уроке 12 читатель подробно познакомится со средствами ассемблера для работы со струк- турами данных, которые характерны для языков высокого уровня (таких, как Pascal и С). Это несколько приближает уровень программирования на ассембле- ре к указанным языкам. На уроке 13 читатель очень подробно познакомится с весьма полезным инстру- ментом языка ассемблера — макросредствами. Макросы, при надлежащем овла- дении ими, могут сделать процесс программирования на ассемблере не только легким, но и приятным. Урок 14 посвящен очень важному вопросу — организации модульного про- граммирования с использованием ассемблера. Подробно описываются все тон- кости связи отдельных программ, написанных на ассемблере. Затем показыва- ется, что эти принципы действительны и при связывании программ на ассемблере с программами на других языках. Понятно, что описать все возмож- ные случаи просто невозможно, тем более что многое здесь зависит от особен- ностей (и даже версии) конкретного компилятора языка высокого уровня. Но тем не менее в основе такой связи лежат несколько основных принципов, по- нимание которых позволит читателю быстрее сориентироваться в конкретной ситуации. Заключительные уроки 15-17 логически завершают рассмотрение особенностей архитектуры современных моделей микропроцессоров, отражением которых яв- ляется язык ассемблера. Здесь читатель познакомится с режимами работы микро- процессора, поймет, как тот взаимодействует с остальными устройствами компь- ютера и получает информацию извне. Приведенные сведения, возможно, не
Предисловие 19 будут востребованы немедленно, но они позволят читателю лучше понять смысл программирования (не только на ассемблере). Достаточно большое место в книге отведено различным приложениям, и это не случайно. Опыт показывает, что на следующих после непосредственно изуче- ния стадиях работы с ассемблером именно этот материал всегда должен быть под рукой. Подбор приложений производился по этому принципу. Авторами не ставилась задача рассмотреть все опции или директивы, так как они вряд ли понадобятся в обычной работе. К книге прилагается дискета, которая содержит не только все основные про- граммы книги и соответствующий пояснительный материал, но и разработан- ную авторами справочную систему по языку ассемблера. Хотелось бы надеяться, что содержимое дискеты значительно повысит удобство и гибкость работы с материалом книги. Что нужно для работы с книгой? Во многом это зависит от целей, которые чита- тель перед собой ставит. Если это простое знакомство, то компьютер может и не понадобиться — достаточно прочитать материал. Но научиться программировать на ассемблере таким образом, конечно, нельзя. Для этого нужен компьютер на базе одного из микропроцессоров Intel, начиная с 18086 и заканчивая Pentium. Требования к конфигурации компьютера нет смысла обсуждать, так как любой существующий сегодня компьютер им наверняка удовлетворяет. Из программно- го обеспечения необходим редактор текстов, хотя бы самый простой, и пакет транслятора ассемблера фирм Microsoft или Borland. Для того чтобы разговор с читателем шел на одном языке, желательно отдать предпочтение транслятору Turbo Assembler (TASM) фирмы Borland, потому, что именно он использовался при разработке программ из книги. Ну и конечно, необходимы большое желание, терпение и тяга к познанию, так как первые шаги всегда бывают трудными. Сложность еще и в том, что материал книги неразрывно связан со множеством других вопросов, касающихся не только компьютерного «железа». Понятно, что учесть и рассмотреть их все в рамках одной книги просто невозможно, да и вряд ли нужно. С этой точки зрения читателю следует воспринимать книгу скорее как описание инструмента и приемов работы с ним для решения некоторых конкрет- ных задач. В заключение авторы хотели бы выразить благодарность всем тем, кто активно или пассивно помогал появлению этой книги на свет.
чТ) УРОК Общие сведения об ЭВМ □ Путешествие в историю, далекую и не очень □ Внешний вид типичного современного компьютера □ Структурная схема компьютера □ Что такое ассемблер?
Современному человеку сегодня трудно представить свою жизнь без электрон- но-вычислительных машин (ЭВМ). В настоящее время любой желающий в соответствии со своими запросами может собрать у себя на рабочем столе пол- ноценный вычислительный центр. Так было, конечно, не всегда. Путь человече- ства к этому достижению был труден и тернист. Много веков назад люди хоте- ли иметь приспособления, которые помогали бы им решать разнообразные задачи. Многие из этих задач решались последовательным выполнением неко- торых рутинных действий, или, как принято говорить сейчас, выполнением ал- горитма. С попытки изобрести устройство, способное реализовать простейшие из этих алгоритмов (сложение и вычитание чисел), все и началось... Точкой отсчета можно считать начало XVII века (1623 год), когда ученый В. Шикард создал машину, умеющую складывать и вычитать числа. Но пер- вым арифмометром, способным выполнять четыре основных арифметических действия, стал арифмометр знаменитого французского ученого и философа Блеза Паскаля. Основным элементом в нем было зубчатое колесо, изобретение которого уже само по себе стало ключевым событием в истории вычислитель- ной техники. Правнуки этого колеса еще совсем недавно, каких-нибудь полто- ра десятка лет назад, использовались в арифмометрах (соответствующая мо- дель была создана в 1842 году) на столах советских бухгалтеров. Хотелось бы отметить, что эволюция в области вычислительной техники носит неравномер- ный, скачкообразный характер: периоды накопления сил сменяются прорыва- ми в разработках, после чего наступает период стабилизации, во время кото- рого достигнутые результаты используются практически и одновременно накапливаются знания и силы для очередного рывка вперед. После каждого витка процесс эволюции выходит на новую, более высокую ступень. В 1671 году немецкий философ и математик Густав Лейбниц также создает арифмометр на основе зубчатого колеса особенной конструкции — зубчатого колеса Лейбница. Арифмометр Лейбница, как и арифмометры его предшествен- ников, выполнял четыре основных арифметических действия. На этом данный период закончился, и человечество в течение почти полутора веков копило силы и знания для следующего витка эволюции вычислительной техники. XVIII и XIX века были временем, когда бурно развивались различные науки, в том числе математика и астрономия. В них часто возникали задачи, требую- щие длительных и трудоемких вычислений.
22 Урок 1. Общие сведения об ЭВМ Еще одним известным человеком в истории вычислительной техники стал анг- лийский математик Чарльз Бэббидж. В 1823 году Бэббидж начал работать над машиной для вычисления полиномов, но, что более интересно, эта машина должна была, кроме непосредственного производства вычислений, выдавать результаты — печатать их на негативной пластине для фотопечати. Планиро- валось, что машина будет приводиться в действие паровым двигателем. Из-за технических трудностей Бэббиджу до конца не удалось реализовать свой проект. Здесь впервые возникла идея использовать некоторое внешнее (перифе- рийное) устройство для выдачи результатов вычислений. Отметим, что другой ученый, С. Шойц, в 1853 году все же реализовал машину, задуманную Бэббид- жем (она получилась даже меньше, чем планировалась). Наверное Ч. Бэббид- жу больше нравился творческий процесс поиска новых идей, чем воплощение их в нечто материальное. В 1834 году он изложил принципы работы очеред- ной машины, которая была названа им «аналитической». Технические труднос- ти вновь не позволили ему до конца реализовать свои идеи. Бэббидж смог до- вести машину лишь до стадии эксперимента. Но именно идея является двигателем научно-технического прогресса. Очередная машина Чарльза Бэб- биджа был воплощением следующих идей: О управление производственным процессом. Машина управляла работой ткац- кого станка, изменяя узор создаваемой ткани в зависимости от сочетания отверстий на специальной бумажной ленте. Эта лента стала предшественни- цей таких знакомых нам всем носителей информации, как перфокарты и перфоленты; О программируемость. Работой машины также управляла специальная бумаж- ная лента с отверстиями. Порядок следования отверстий на ней определял команды и обрабатываемые этими командами данные. Машина имела ариф- метическое устройство и память. В состав команд машины входила даже команда условного перехода, изменяющая ход вычислений в зависимости от некоторых промежуточных результатов. В разработке этой машины принимала участие графиня Ада Августа Лавлейс1, которую считают первой в мире женщиной-программистом. Не слишком ли много для одного проекта и одного человека?! Идеи Чарльза Бэббиджа развивались и использовались другими учеными. Так, в 1890 году, на рубеже XX века, американец Г. Холлерит разработал машину, ра- ботающую с таблицами данных (первый Excel?). Машина управлялась програм- мой на перфокартах. Она использовалась при проведении переписи населения в США в 1890 году. В 1896 году Г. Холлерит основал фирму, явившуюся пред- шественницей корпорации IBM. Со смертью Бэббиджа в эволюции вычисли- тельной техники наступил очередной перерыв вплоть до 30-х годов XX века. В дальнейшем все развитие человечества стало немыслимым без компьютеров. 1 В честь графини Ады Августы Лавлейс, родственницы Байрона, был назван язык программирова- ния Ada.
23 В 1938 году центр разработок ненадолго смещается из Америки в Германию, где К. Цузе создает машину, которая оперирует, в отличие от своих предшест- венниц, не десятичными числами, а двоичными. Эта машина также была все еще механической, но ее несомненным достоинством было то, что в ней была реализована идея обработки данных в двоичном коде. Продолжая свои работы, Цузе в 1941 году создал электромеханическую машину, арифметическое ус- тройство которой было выполнено на базе реле. Машина умела выполнять опе- рации с плавающей точкой. За океаном, в Америке, в этот период также шли работы по созданию подобных электромеханических машин. В 1944 году Г. Айкен спроектировал машину, ко- торую назвали MARK-1. Она, как и машина К. Цузе, работала на реле. Но из-за того, что данная машина явно была создана под влиянием работ Бэббиджа, она оперировала с данными в десятичной форме. Естественно, из-за большого удельного веса механических частей эти машины были обречены. Нужно было искать новую, более технологичную элементную базу. И тогда вспомнили об изобретении Л. Фореста, который в 1906 году создал трехэлектродную вакуумную лампу, названную триодом. В силу своих функциональных свойств она стала наиболее естественной заменой реле. В 1946 году в США, в университете города Пенсильвания, была создана первая универсальная ЭВМ — ENIAC. ЭВМ ENIAC содержала 18 тыс. ламп, весила 30 тонн, занимала площадь 200 м2 и потребляла огромную мощность. В ней все еще использовались десятичные операции и программирование осуществлялось путем коммутации разъемов и установки переключателей. Естественно, что та- кое «программирование» влекло за собой появление множества проблем, вызванных, прежде всего, неверной установкой переключателей. С про- ектом ENIAC связано имя еще одной ключевой фигуры в истории вычисли- тельной техники — математика Джона фон Неймана. Именно он впервые предложил записывать программу и ее данные в память машины так, чтобы их можно было при необходимости модифицировать в процессе работы. Этот ключевой принцип, получивший название принципа хранимой программы, был использован в дальнейшем при создании принципиально новой ЭВМ EDVAC (1951 год). В этой машине уже применяется двоичная арифметика и исполь- зуется оперативная память, построенная на ультразвуковых ртутных линиях задержки. Память могла хранить 1024 слова. Каждое слово состояло из 44 дво- ичных разрядов. После создания EDVAC человечество осознало, какие высоты науки и техники могут быть достигнуты тандемом человек—компьютер. Данная отрасль стала раз- виваться очень быстро и динамично, хотя здесь тоже наблюдалась некоторая пе- риодичность, связанная с необходимостью накопления определенного багажа зна- ний для очередного прорыва. До середины 80-х годов процесс эволюции вычислительной техники принято делить на поколения. Для полноты изложения дадим этим поколениям краткие качественные характеристики: 1-е поколение (1945-1954 гг.) — время становления машин с фон-неймановской архитектурой. В этот период формируется типовой набор структурных элемен-
24 Урок 1. Общие сведения об ЭВМ тов, входящих в состав ЭВМ. К этому времени у разработчиков уже сложилось примерно одинаковое представление о том, из каких элементов должна состо- ять типичная ЭВМ. Это — центральный процессор (ЦП), оперативная память (или оперативное запоминающее устройство — ОЗУ) и устройства ввода-вы- вода (УВВ). ЦП, в свою очередь, должен состоять из арифметико-логического устройства (АЛУ) и управляющего устройства (УУ). Машины этого поколе- ния работали на ламповой элементной базе, из-за чего поглощали огромное количество энергии и были очень ненадежны. С их помощью в основном реша- лись научные задачи. Программы для этих машин уже можно было составлять не на машинном языке, а на языке ассемблера. 2-е поколение (1955-1964 гг.). Смену поколений определило появление новой элементной базы: вместо громоздкой лампы в ЭВМ стали применяться миниа- тюрные транзисторы, линии задержки как элементы оперативной памяти сме- нила память на магнитных сердечниках. Это в конечном итоге привело к уменьшению габаритов, повышению надежности и производительности ЭВМ. В архитектуре ЭВМ появились индексные регистры и аппаратные средства для выполнения операций с плавающей точкой. Были разработаны команды для вызова подпрограмм. Появились языки высокого уровня — Algol, FORTRAN, COBOL — создавшие предпосылки для появления переносимого программно- го обеспечения, не зависящего от типа ЭВМ. С появлением языков высокого уровня возникли компиляторы для них, библиотеки стандартных подпрограмм и другие хорошо знакомые нам сейчас вещи. Важное новшество, которое хоте- лось бы отметить, — это появление так называемых процессоров ввода-вывода. Эти специализированные процессоры позволили освободить ЦП от управле- ния вводом-выводом и осуществлять ввод-вывод с помощью специализирован- ного устройства одновременно с процессом вычислений. На этом этапе резко расширился круг пользователей ЭВМ и возросла номенклатура решаемых за- дач. Для эффективного управления ресурсами машины стали использоваться операционные системы (ОС). 3-е поколение (1965-1970 гг.). Смена поколений вновь была обусловлена об- новлением элементной базы: вместо транзисторов в различных узлах ЭВМ ста- ли использоваться интегральные микросхемы различной степени интеграции. Микросхемы позволили разместить десятки элементов на пластине размером в несколько сантиметров. Это, в свою очередь, не только повысило производи- тельность ЭВМ, но и снизило их габариты и стоимость. Появились сравни- тельно недорогие и малогабаритные машины — лштш-ЭВМ. Они активно использовались для управления различными технологическими производ- ственными процессами в системах сбора и обработки информации. Увеличе- ние мощности ЭВМ сделало возможным одновременное выполнение нескольких программ на одной ЭВМ. Для этого нужно было научиться координировать между собой одновременно выполняемые действия, для чего были расширены функции операционной системы. Одновременно с активными разработками в области аппаратных и архитектурных решений растет удельный вес разрабо- ток в области технологий программирования. В это время активно разрабаты- ваются теоретические основы методов программирования, компиляции, баз
25 данных, операционных систем и т. д. Создаются пакеты прикладных программ для самых различных областей жизнедеятельности человека. Теперь уже стано- вится непозволительной роскошью переписывать все программы с появлением каждого нового типа ЭВМ. Наблюдается тенденция к созданию семейств ЭВМ, то есть машины становятся совместимы снизу вверх на программно-аппарат- ном уровне. Примерами таких семейств была серия IBM System 360 и наш оте- чественный аналог — ЕС ЭВМ. 4-е поколение (1970-1984 гг.). Очередная смена элементной базы привела к сме- не поколений. В 70-е годы активно ведутся работы по созданию больших и сверхбольших интегральных схем (БИС и СБИС), которые позволили размес- тить на одном кристалле десятки тысяч элементов. Это повлекло дальнейшее существенное снижение размеров и стоимости ЭВМ. Работа с программным обеспечением стала более дружественной, что повлекло за собой рост количест- ва пользователей. В принципе, при такой степени интеграции элементов стало возможным попытаться создать функционально полную ЭВМ на одном крис- талле. Соответствующие попытки были предприняты, хотя они и встречались в основном недоверчивой улыбкой. Наверное, этих улыбок стало бы меньше, если бы можно было предвидеть, что именно эта идея станет причиной «выми- рания» больших ЭВМ через каких-нибудь полтора десятка лет. Тем не менее в начале 70-х годов фирмой Intel был выпущен микропроцессор (МП) i4004. И если до этого в мире вычислительной техники были только три направления (суперЭВМ, большие ЭВМ (мэйнфреймы) и мини-ЭВМ), то теперь к ним при- бавилось еще одно — микропроцессорное. В общем случае под процессором понимают функциональный блок ЭВМ, предназначенный для логической и арифметической обработки информации на основе принципа микропрограммно- го управления. По аппаратной реализации процессоры можно разделить на мик- ропроцессоры (полностью интегрирующие все функции процессора) и процес- соры с малой и средней интеграцией. Конструктивно это выражается в том, что микропроцессоры реализуют все функции процессора на одном кристалле, а процессоры других типов реализуют их путем соединения большого количества микросхем. Итак, первый МП 14004 был создан фирмой Intel на рубеже 70-х годов. Он представлял собой 4-разрядное параллельное вычислительное устройство, и его возможности были сильно ограничены. 14004 мог производить четыре ос- новные арифметические операции и применялся поначалу только в карманных калькуляторах. Позднее сфера его применения была расширена за счет исполь- зования в различных системах управления (например, для управления свето- форами). Фирма Intel, правильно предугадав перспективность микропроцессо- ров, продолжила интенсивные разработки, и один из ее проектов в конечном итоге привел к крупному успеху, предопределившему будущий путь развития вычислительной техники. Им стал проект по разработке 8-разрядного микро- процессора 18008 (1972 г.). Этот микропроцессор имел довольно развитую сис- тему команд и умел делить числа. Именно он был использован при создании персонального компьютера Альтаир, для которого молодой Билл Гейтс напи-
26 Урок 1. Общие сведения об ЭВМ сал один из своих первых интерпретаторов языка Basic. Наверное, именно с это- го момента следует вести отсчет 5-го поколения. 5-е поколение можно назвать микропроцессорным. Заметьте, что 4-е поколение закончилось только в начале 80-х, то есть «родители» в лице больших машин и их быстро взрослеющее и набирающее силы «чадо» в течение почти 10 лет относительно мирно существовали вместе. Для них обоих это время по- шло только на пользу. Проектировщики больших компьютеров накопили огромный теоретический и практический опыт, а программисты микропроцессо- ров сумели найти свою, пусть поначалу очень узкую, нишу на рынке. В 1976 году фирма Intel закончила разработку 16-разрядного микропроцессора 18086. Он имел достаточно большую разрядность регистров (16 бит) и системной шины адреса (20 бит), за счет чего мог адресовать до 1 Мбайт оперативной памяти. В 1982 году был создан i80286. Этот микропроцессор представлял собой улучшен- ный вариант 18086. Он поддерживал уже несколько режимов работы: реальный, когда формирование адреса производилось по правилам 18086, и защищенный, который аппаратно реализовывал многозадачность и управление виртуальной памятью, i80286 имел также большую разрядность шины адреса — 24 разряда против 20 у 18086, и поэтому он мог адресовать до 16 Мбайт оперативной памя- ти. Первые компьютеры на базе этого микропроцессора появились в 1984 году. По своим вычислительным возможностям этот компьютер стал сопоставим с IBM 370. Поэтому можно считать, что на этом 4-е поколение развития ЭВМ за- вершилось. В 1985 году фирма Intel представила первый 32-разрядный микропроцессор i80386, аппаратно совместимый снизу вверх со всеми предыдущими микропро- цессорами этой фирмы. Он был гораздо мощнее своих предшественников, имел 32-разрядную архитектуру и мог прямо адресовать до 4 Гбайт оперативной памяти. Вскоре после i80386 появился i486, в котором математический сопро- цессор был интегрирован на одном кристалле с основным процессором. Также впервые микропроцессор стал дополняться внутренней кэш-памятью; появи- лось понятие конвейеризации вычислений. С 1993 года стали выпускаться микропроцессоры Intel Pentium. Их появление вначале омрачилось ошибкой в блоке операций с плавающей точкой. Эта ошибка была быстро устранена, но недоверие к этим микропроцессорам еще некоторое время оставалось. В конеч- ном итоге несомненные преимущества, заложенные в архитектуре Pentium, по- могли ему стать на ноги, и сегодня мы видим, что i486 и Pentium являются са- мыми популярными и массовыми микропроцессорами на российском рынке. На этом можно, наверное, закончить историческое введение. В этой книге мы подробно будем обсуждать архитектуру и особенности программирования именно микропроцессоров Intel. Я сознательно опустил обсуждение микропро- цессоров других фирм. Они, несомненно, имеют свои достоинства (и недостат- ки), которые в силу специфики книги здесь обсуждать неуместно. Типичный современный компьютер (на базе i486 или Pentium) состоит из сле- дующих компонентов (рис. 1.1).
27 Системный блок Рис. 1.1. Компьютер и периферийные устройства Из рисунка видно, что компьютер составлен из нескольких физических уст- ройств, каждое из которых подключено к одному блоку, называемому сис- темным. Если рассуждать логически, то ясно, что он играет роль некоторого координирующего устройства. Давайте заглянем внутрь системного блока (не нужно пытаться проникнуть внутрь монитора — там нет ничего интересного, к тому же это опасно): открываем корпус и видим какие-то платы, блоки, соеди- нительные провода. Чтобы понять их функциональное назначение, посмотрим на структурную схему типичного компьютера (рис. 1.2). Она не претендует на безусловную точность и имеет целью лишь показать назначение, взаимосвязь и типовой состав элементов современного персонального компьютера. Обсудим схему на рис. 1.2 в несколько нетрадиционном стиле. У меня часто возникают ассоциации компьютера с человеком. У компьютера есть органы восприятия информации из внешнего мира — это клавиатура, мышь, накопители на магнитных дисках. На рис. 1.2 эти органы расположены справа от системных шин. У компьютера есть органы, «переваривающие» полу- ченную информацию, — это центральный процессор и оперативная память. И наконец, у компьютера есть органы речи, выдающие результаты переработки. Это также некоторые из устройств справа. Современным компьютерам, конеч- но, далеко до человека. Их можно сравнить с существами, взаимодействующи- ми с внешним миром на уровне большого, но ограниченного набора безуслов- ных рефлексов. Этот набор рефлексов образует систему машинных команд. На каком бы высоком уровне вы ни общались с компьютером, в конечном итоге все сводится к скучной и однообразной последовательности машинных команд. Каждая машинная команда является своего рода раздражителем для возбужде- ния того или иного безусловного рефлекса. Реакция на этот раздражитель всег- да однозначная и «зашита» в блоке микрокоманд в виде микропрограммы. Эта микропрограмма и реализует действия по реализации машинной команды, но уже на уровне сигналов, подаваемых на те или иные логические схемы ком- пьютера, тем самым управляя различными подсистемами компьютера. В этом состоит так называемый принцип микропрограммного управления. Продолжая аналогию с человеком, отметим: для того, чтобы компьютер правильно питался,
28 Урок 1. Общие сведения об ЭВМ придумано множество операционных систем, компиляторов сотен языков про- граммирования и т. д. Но все они являются по сути лишь блюдом, на котором по определенным правилам доставляется пища (программы) желудку (компью- теру). Только (вот досада!) желудок компьютера любит диетическую, однооб- разную пищу — подавай ему информацию структурированную, в виде строго организованных последовательностей нулей и единиц, комбинации которых и составляют машинный язык. Таким образом, внешне являясь полиглотом, ком- пьютер понимает только один язык — язык машинных команд. Конечно, для общения и работы с компьютером необязательно знать этот язык, но практи- чески любой профессиональный программист рано или поздно сталкивается с необходимостью его изучения. К счастью, программисту не нужно пытаться пос- тичь значение различных комбинаций двоичных чисел, так как еще в 50-е гг. программисты стали использовать для программирования символический ана- лог машинного языка, который назвали языком ассемблера. Этот язык точно отражает все особенности машинного языка. Именно поэтому, в отличие от языков высокого уровня, язык ассемблера для каждого типа компьютера свой. адреса данных управления Рис. 1.2. Структурная схема персонального компьютера
29 Из всего вышесказанного можно сделать вывод, что, так как язык ассемблера для компьютера «родной», то и самая эффективная программа может быть написана только на нем (при условии, что ее пишет квалифицированный программист). Здесь есть одно маленькое «но»: это очень трудоемкий и требующий большого внимания и практического опыта процесс. Поэтому реально на ассемблере пишут в основном только программы, которые должны обеспечить эффективную работу с аппаратной частью. Иногда на ассемблере пишутся критичные по времени вы- полнения или расходованию памяти участки программы. Впоследствии они оформляются в виде подпрограмм и совмещаются с кодом на языке высокого уровня. На наших уроках в дальнейшем мы подробно разберемся с большин- ством перечисленных выше областей применения ассемблера. Если вы держите в руках эту книгу, значит, для вас настало время сделать оче- редной шаг в профессиональном росте. Поэтому переворачивайте страницу и приступайте к уроку 2. Подведем некоторые итоги: 0 Очень нелегок и длителен был путь развития вычислительной техники. Вна- чале были простые машины, выполняющие несложные арифметические дей- ствия. Люди постарше помнят широко распространенные полтора десятка лет назад механические арифмометры, изобретенные еще в XVII веке, и 30-тонные махины с очень ограниченными возможностями в конце 50-х годов. И вот в конце XX века мы имеем компактный 30-килограммовый набор устройств с колоссальными потенциальными возможностями. 0 Несмотря на большие различия во внешнем виде, структурно все компьюте- ры устроены примерно одинаково. В их состав обязательно входят цент- ральный процессор, внешняя и оперативная память, устройства ввода-выво- да и отображения информации. 0 Работать компьютер заставляет некий «серый кардинал» — машинный язык. Пользователь может даже и не подозревать о его существовании. Общаться с компьютером пользователю помогают операционные системы, офисные пакеты, системы программирования и т. д. Использование современных тех- нологий программирования позволяет создавать программы, не написав ни строчки кода. Но в мозг компьютера команды все же поступают на машин- ном языке. 0 Машинный язык полностью отражает все архитектурные тонкости конкрет- ного типа компьютеров. Следствием этого является то, что он индивидуален для каждого семейства ЭВМ. Для того чтобы использовать эффективно все возможности компьютера, применяют символический аналог машинного языка — язык ассемблера.
30 Урок 1. Общие сведения об ЭВМ 0 Работать на компьютере можно и без знания языка ассемблера. Но элементом подготовки программиста-профессионала обязательно является изучение ас- семблера. Почему? Изучая ассемблер, вы обязательно попутно познакомитесь с архитектурой компьютера. А это, в свою очередь, позволит вам в дальней- шем создавать более эффективные программы на других языках и объединять их, при необходимости, с программами на ассемблере.
) УРОК Архитектура персонального компьютера □ Понятие об архитектуре ЭВМ □ Архитектурные особенности компьютеров на базе i486 и Pentium □ Описание набора регистров микропроцессора □ Организация оперативной памяти компьютера □ Форматы и типы данных, поддерживаемые микропроцессором □ Формат машинных команд □ Система прерываний компьютера
На уроке 1 мы описали компьютер на «житейском» уровне. При обсуждении понятия машинного языка отмечалось, что его характеристики полностью определяются особенностями того типа компьютера, для которого этот язык предназначен. Возникает вопрос — как оценить возможности конкретного типа (или модели) компьютера и его отличительные особенности от компьютеров других типов (моделей). Рассмотрения одной лишь только его структурной схемы явно недостаточно, так как она принципиально мало чем отличается для разных машин. У всех компьютеров есть оперативная память, процессор, внеш- ние устройства. Различными являются способы, средства и используемые ре- сурсы, с помощью которых компьютер функционирует как единый механизм. Чтобы собрать воедино все понятия, характеризующие компьютер с точки зре- ния его функциональных программно-управляемых свойств, существует специ- альный термин — архитектура ЭВМ. Впервые это понятие стало упоминаться с появлением машин 3-го поколения для их сравнительной оценки. Мы отме- чали на уроке 1, что в это время наблюдался всплеск разработок как про- граммного, так и аппаратного обеспечения. Ниже мы дадим более формальное определение понятия архитектуры ЭВМ. Но прежде хотелось бы предупредить читателя, что урок будет нелегким. Приступать к изучению языка ассемблера любого компьютера имеет смысл только после выяснения того, какая часть компьютера оставлена видимой и доступной для программирования на этом языке. Это так называемая программная модель компьютера. Для программиро- вания на языках высокого уровня совсем необязательно вникать слишком глу- боко в эти вопросы. Мы не стали «размазывать» вопросы архитектуры по всей книге, а систематизированно изложили их на одном уроке. На последующих уроках эти вопросы будут уточняться и расширяться, но у вас уже будет перед глазами общая картина. Если же вы чувствуете, что перестаете понимать мате- риал, то просто просмотрите его и переходите к уроку 3. В любом случае, в процессе работы над книгой периодически возвращайтесь к этому уроку, и со временем вы найдете ответы на все вопросы. Архитектура ЭВМ Это понятие довольно трудно определить однозначно, потому что при желании в него можно включить все, что связано с ЭВМ вообще и какой-то конкретной моделью компьютера в частности. Попытаемся все же формализовать этот ши- роко распространенный термин.
Архитектура ЭВМ 33 Архитектура ЭВМ — это абстрактное представление ЭВМ, которое отражает ее структурную, схемотехническую и логическую организацию. Понятие архи- тектуры ЭВМ является комплексным и включает в себя: О структурную схему ЭВМ; О средства и способы доступа к элементам структурной схемы ЭВМ; О организацию и разрядность интерфейсов ЭВМ; О набор и доступность регистров; О организацию и способы адресации памяти; О способы представления и форматы данных ЭВМ; О набор машинных команд ЭВМ; О форматы машинных команд; О обработку нештатных ситуаций (прерываний). Как видите, понятие архитектуры включает в себя практически всю необходи- мую для программиста информацию о компьютере. Поэтому прежде чем при- ступить к изучению вопросов, связанных с программированием на ассемблере для компьютеров на базе микропроцессоров фирмы Intel, познакомимся с их архитектурой. Все современные ЭВМ обладают некоторыми общими и индивидуальными свойствами архитектуры. Индивидуальные свойства присущи только конкрет- ной модели компьютера и отличают его от больших и малых собратьев. Нали- чие общих архитектурных свойств обусловлено тем, что большинство типов существующих машин принадлежат 4 и 5-му поколениям ЭВМ так называе- мой фон-неймановской архитектуры. К числу общих архитектурных свойств и принципов можно отнести: О Принцип хранимой программы. Согласно ему, код программы и ее данные находятся в одном адресном пространстве в оперативной памяти. О Принцип микропрограммирования. Суть этого принципа заключается в том, что машинный язык все-таки еще не является той конечной субстанцией, которая физически приводит в действие процессы в машине. В состав про- цессора входит блок микропрограммного управления (см. рис. 1.2). Этот блок для каждой машинной команды имеет набор действий-сигналов, кото- рые нужно сгенерировать для физического выполнения требуемой машин- ной команды. Здесь уместно вспомнить характеристику ЭВМ 1-го поколе- ния. В них для генерации нужных сигналов необходимо было осуществить ручное программирование всех логических схем — поистине адская и не- благодарная работа! О Линейное пространство памяти — совокупность ячеек памяти, которым последовательно присваиваются номера (адреса) О, 1, 2, ....
34 Урок 2. Архитектура персонального компьютера О Последовательное выполнение программ. Процессор выбирает из памяти ко- манды строго последовательно. Для изменения прямолинейного хода вы- полнения программы или осуществления ветвления необходимо использо- вать специальные команды. Они называются командами условного и безусловного перехода. О С точки зрения процессора нет принципиальной разницы между данными и командами. Данные и машинные команды находятся в одном пространстве памяти в виде последовательности нулей и единиц. Это свойство связано с предыдущим. Процессор, исполняя содержимое некоторых последователь- ных ячеек памяти, всегда пытается трактовать его как коды машинной ко- манды, а если это не так, то происходит аварийное завершение программы, содержащей некорректный фрагмент. Поэтому важно в программе всегда четко разделять пространство данных и команд. О Безразличие к целевому назначению данных. Машине все равно, какую логи- ческую нагрузку несут обрабатываемые ею данные. Наша книга посвящена вопросам программирования микропроцессоров фир- мы Intel — i486 и Pentium1. У них, как и у процессоров других фирм, есть ин- дивидуальные архитектурные принципы. К слову сказать, перечень архитек- турных нововведений для этих микропроцессоров впечатляет. Так, некоторые источники приводят до 14 новых или усовершенствованных старых решений. Их полное рассмотрение не является нашей целью, поэтому уделим внимание наиболее характерным и необходимым для дальнейшего изложения новациям. Суперскалярная архитектура. Для того чтобы пояснить этот термин, разбе- ремся вначале со значением другого термина — конвейеризация вычислений. Важным элементом архитектуры, появившимся в i486, стал конвейер — специ- альное устройство, реализующее такой метод обработки команд внутри микро- процессора, при котором исполнение команды разбивается на несколько этапов. i486 имеет пятиступенчатый конвейер. Соответствующие пять этапов включают: О выборку команды из кэш-памяти или оперативной памяти; О декодирование команды; О генерацию адреса, при которой определяются адреса операндов в памяти; О выполнение операции с помощью АЛУ; О запись результата (куда будет записан результат, зависит от алгоритма рабо- ты конкретной машинной команды). Таким образом, на стадии выполнения каждая машинная команда как бы разби- 1 Этот выбор обусловлен тем, что указанные процессоры являются наиболее популярными на сегод- няшний день в России. Все программы в данной книге приведены для этих микропроцессоров. Так как i486 и Pentium являются результатом эволюции более ранних моделей микропроцессоров фир- мы Intel, то они полностью совместимы с предыдущими моделями микропроцессоров. Исходя из этого, большую часть сведений, приведенных в книге, можно использовать для программирования младших моделей микропроцессоров Intel.
Архитектура ЭВМ 35 вается на более элементарные операции. В чем преимущество такого подхода? Очередная команда после ее выборки попадает в блок декодирования. Таким образом, блок выборки свободен и может выбрать следующую команду. В результате на конвейере могут находиться в различной стадии выполнения пять команд. Скорость вычисления в результате существенно возрастает. Мик- ропроцессоры, имеющие один конвейер, называются скалярными. Pentium име- ет два конвейера, a Pentium Pro — три, поэтому эти микропроцессоры называ- ются суперскалярными. Раздельное кэширование кода и данных. Кэширование — это способ увеличения быстродействия системы за счет хранения часто используемых данных и кодов в так называемой «кэш-памяти первого уровня» (быстрой памяти), находящей- ся внутри микропроцессора, i486, к примеру, содержит один блок встроенной кэш-памяти размером 8 Кбайт, который используется для кэширования и кодов, и данных. Pentium содержит два блока кэш-памяти: один для кода и один для данных, каждый по 8 Кбайт. При этом становится возможным одновременный доступ к коду и данным, что увеличивает скорость работы компьютера. Предсказание правильного адреса перехода. Под переходом понимается заплани- рованное алгоритмом изменение последовательного характера выполнения программы. Как показывает статистика, типичная программа на каждые 6-8 ко- манд содержит 1 команду перехода. Последствия этого предсказать несложно: при наличии конвейера через каждые 6-8 команд его нужно очищать и заполнять заново в соответствии с адресом перехода. Все преимущества кон- вейеризации теряются. Поэтому в архитектуру Pentium был введен блок пред- сказания переходов. Суть этого метода заключается в следующем. Pentium име- ет буфер адресов перехода, который хранит информацию о последних 256 переходах. Если некоторая команда управляет ветвлением, то в буфере запо- минаются эта команда, адрес перехода и предположение о том, какая ветвь про- граммы будет выполнена следующей. Почти в любой программе имеются цик- лы, в ходе выполнения которых периодически необходимо принимать решение либо о выходе из цикла, либо о переходе на его начало. Специальный блок предсказания адреса перехода прогнозирует, какое решение будет принято про- граммой. При этом он основывается на предположении, что ветвь, которая была пройдена, будет использоваться снова, и загружает соответствующую ко- манду перехода на конвейер. В случае, если это предсказание верно, переход осуществляется без задержки. Для того чтобы судить об эффективности этого нововведения, достаточно отметить, что вероятность правильного предсказания составляет около 80%. Усовершенствованный блок вычислений с плавающей точкой. Он позволяет вы- полнять одну команду с плавающей точкой за один такт микропроцессора. На этом, наверное, следует завершить обсуждение общих вопросов, связанных с архитектурой микропроцессоров Intel. Им можно посвятить не одну увлека- тельную книгу, но это не является нашей целью. Наша ближайшая задача состоит
36 Урок 2. Архитектура персонального компьютера в том, чтобы разобраться, как управлять этими сложнейшими микропроцессора- ми. В начале урока мы уже упоминали, что для этого нужно разобраться как с общей программной моделью компьютера вообще, так и программной моделью микропроцессора в частности. В этих моделях описываются основные особенно- сти архитектуры компьютера, знание которых позволяет программисту эффек- тивно и в полном объеме использовать все его возможности. Первым шагом программиста, который начинает разбираться с вопросами про- граммирования на ассемблере, является выяснение того, какие регистры микро- процессора ему доступны, их функционального назначения и порядка исполь- зования. С рассмотрения регистров мы и начнем знакомство с программной моделью микропроцессора. Набор регистров Программная модель микропроцессора содержит 32 регистра, в той или иной мере доступных для использования программистом. Их можно разделить на две большие группы: О 16 пользовательских регистров; О 16 системных регистров. В программах на языке ассемблера регистры используются очень интенсивно. Большинство регистров имеют определенное функциональное назначение. На этом уроке будет дана характеристика только первой группы — пользователь- ских регистров. Системные регистры предназначены в основном для поддержа- ния защищенного режима работы, поэтому обсуждать их следует вместе с рас- смотрением этого режима, чем мы вплотную и займемся на уроке 16. Пользовательские регистры Как следует из названия, пользовательскими регистры называются потому, что программист может использовать их при написании своих программ. К этим регистрам относятся (рис. 2.1): О восемь 32-битных регистров, которые могут использоваться программиста- ми для хранения данных и адресов (их еще называют регистрами общего назначения (РОН)): eax/ax/ah/al, ebx/bx/bh/bl, edx/dx/dh/dl, ecx/cx/ch/cl, ebp/bp, esi/si, edi/di, esp/sp; О шесть регистров сегментов: cs, ds, ss, es, fs, gs; О регистры состояния и управления: регистр флагов eflags/flags и регистр указателя команды eip/ip.
Набор регистров 37 еах edx есх ebx ebp esi edi Регистры общего назначения: ax ah I al 31 15 7 0 dx dh I dl 31 15 7 0 ex ch I cl 31 15 7 0 bx bh I ы 31 15 7 0 bp 31 15 0 si 31 15 0 di 31 15 0 sp esp 31 15 О Сегментные регистры: CS 15 0 SS 15 0 ds 15 0 es 15 0 fs 15 0 gs 15 0 Регистры флагов и указателя команд: eflags flags 31 15 0 eip ip 31 15 0 Рис. 2.1. Пользовательские регистры микропроцессоров i486 и Pentium
38 Урок 2. Архитектура персонального компьютера Почему многие из этих регистров приведены с наклонной разделительной чер- той? Нет, это не разные регистры — это части одного большого 32-разрядного регистра. Их можно использовать в программе как отдельные объекты. Зачем так сделано? Для обеспечения работоспособности программ, написанных для младших 16-разрядных моделей микропроцессоров фирмы Intel, начиная с 18086. Микропроцессоры i486 и Pentium имеют, в основном, 32-разрядные ре- гистры. Их количество, за исключением сегментных регистров, такое же, как и у i8086, но размерность больше, что и отражено в их обозначениях — они име- ют приставку е (Extended). Разберемся подробнее с составом и назначением пользовательских регистров. Регистры общего назначения Все регистры этой группы позволяют обращаться к своим «младшим» частям (см. рис. 2.1). Рассматривая этот рисунок, заметьте, что использовать для само- стоятельной адресации можно только младшие 16- и 8-битные части этих регист- ров. Старшие 16 бит этих регистров как самостоятельные объекты недоступны. Это сделано, как мы отметили выше, для совместимости с младшими 16-раз- рядными моделями микропроцессоров фирмы Intel. Перечислим регистры, относящиеся к группе регистров общего назначения. Так как эти регистры фи- зически находятся в микропроцессоре внутри арифметико-логического уст- ройства (АЛУ), то их еще называют регистрами АЛУ: О eax/ax/ah/al (Accumulator register) — аккумулятор. Применяется для хра- нения промежуточных данных. В некоторых командах использование этого регистра обязательно; О ebx/bx/bh/bl (Base register) — базовый регистр. Применяется для хранения базового адреса некоторого объекта в памяти; О ecx/cx/ch/cl (Count register) — регистр-счетчик. Применяется в командах, производящих некоторые повторяющиеся действия. Его использование за- частую неявно и скрыто в алгоритме работы соответствующей команды. К примеру, команда организации цикла loop кроме передачи управления ко- манде, находящейся по некоторому адресу, анализирует и уменьшает на единицу значение регистра есх/сх; О edx/dx/dh/dl (Data register) — регистр данных. Так же как и регистр еах/ах/ ah/al, он хранит промежуточные данные. В некоторых командах его исполь- зование обязательно; для некоторых команд это происходит неявно. Следующие два регистра используются для поддержки так называемых цепо- чечных операций, то есть операций, производящих последовательную обработ- ку цепочек элементов, каждый из которых может иметь длину 32, 16 или 8 бит: О esi/si (Source Index register) — индекс источника. Этот регистр в цепочеч- ных операциях содержит текущий адрес элемента в цепочке-источнике;
Набор регистров 39 О edi/di (Destination Index register) — индекс приемника (получателя). Этот регистр в цепочечных операциях содержит текущий адрес в цепочке-прием- нике. В архитектуре микропроцессора на программно-аппаратном уровне поддержи- вается такая структура данных, как стек. В свое время мы подробно познако- мимся с тем, как его использовать. Для работы со стеком в системе команд микропроцессора есть специальные команды, а в программной модели микро- процессора для этого существуют специальные регистры: О esp/sp (Stack Pointer register) — регистр указателя стека. Содержит указа- тель вершины стека в текущем сегменте стека; О ebp/bp (Base Pointer register) — регистр указателя базы кадра стека. Пред- назначен для организации произвольного доступа к данным внутри стека. Не спешите пугаться столь жесткого функционального назначения регистров АЛУ. На самом деле, большинство из них могут использоваться при програм- мировании для хранения операндов практически в любых сочетаниях. В при- ложении 2 (справочнике команд) при описании команд в синтаксических диа- граммах приведены все возможные варианты использования этих регистров. Но, как мы отметили выше, некоторые команды используют фиксированные регистры для выполнения своих действий. Это нужно обязательно учитывать. В том же приложении 2 при описании каждой команды это обстоятельство особо отмечается. Использование жесткого закрепления регистров для некото- рых команд позволяет более компактно кодировать их машинное представле- ние. Знание этих особенностей позволит вам при необходимости хотя бы на несколько байт сэкономить память, занимаемую кодом программы. Сегментные регистры В программной модели микропроцессора имеется шесть сегментных регистров'. cs, ss, ds, es, gs, fs. Их существование обусловлено спецификой организации и использования оперативной памяти микропроцессорами Intel. Она заключает- ся в том, что микропроцессор аппаратно поддерживает структурную организа- цию программы в виде трех частей, называемых сегментами. Соответственно, такая организация памяти называется сегментной. Для того чтобы указать на сегменты, к которым программа имеет доступ в конкретный момент времени, и предназначены сегментные регистры. Фактически, с небольшой поправкой, как мы увидим далее, в этих регистрах содержатся адреса памяти, с которых начи- наются соответствующие сегменты. Логика обработки машинной команды по- строена так, что при выборке команды, доступе к данным программы или к стеку неявно используются адреса во вполне определенных сегментных регист- рах. Микропроцессор поддерживает следующие типы сегментов: 1. Сегмент кода. Содержит команды программы. Для доступа к этому сегменту служит регистр cs (code segment register) — сегментный регистр кода. Он
40 Урок 2. Архитектура персонального компьютера содержит адрес сегмента с машинными командами, к которому имеет доступ микропроцессор (то есть эти команды загружаются в конвейер микропроцес- сора); 2. Сегмент данных. Содержит обрабатываемые программой данные. Для доступа к этому сегменту служит регистр ds (data segment register) — сегментный ре- гистр данных, который хранит адрес сегмента данных текущей программы; 3. Сегмент стека. Этот сегмент представляет собой область памяти, называемую стеком. Работу со стеком микропроцессор организует по следующему прин- ципу: последний записанный в эту область элемент выбирается первым. Для доступа к этому сегменту служит регистр ss (stack segment register) — сегмен- тный регистр стека, содержащий адрес сегмента стека; 4. Дополнительный сегмент данных. Неявно алгоритмы выполнения большин- ства машинных команд предполагают, что обрабатываемые ими данные рас- положены в сегменте данных, адрес которого находится в сегментном регис- тре ds. Если программе недостаточно одного сегмента данных, то она имеет возможность использовать еще три дополнительных сегмента данных. Но в отличие от основного сегмента данных, адрес которого содержится в сегмент- ном регистре ds, при использовании дополнительных сегментов данных их адреса требуется указывать явно с помощью специальных префиксов пере- определения сегментов в команде. Адреса дополнительных сегментов дан- ных должны содержаться в регистрах es, gs, fs (extension data segment registers). Регистры состояния и управления В микропроцессор включены несколько регистров (см. рис. 2.1), которые по- стоянно содержат информацию о состоянии как самого микропроцессора, так и программы, команды которой в данный момент загружены на конвейер. К этим регистрам относятся: О регистр флагов eflags/flags; О регистр указателя команды eip/ip. Используя эти регистры, можно получать информацию о результатах выполне- ния команд и влиять на состояние самого микропроцессора. Рассмотрим по- дробнее назначение и содержимое этих регистров: eflags/flags (flag register) — регистр флагов. Разрядность eflags/flags — 32/16 бит. Отдельные биты данного регистра имеют определенное функцио- нальное назначение и называются флагами. Младшая часть этого регистра пол- ностью аналогична регистру flags для i8086. На рис. 2.2 показано содержимое регистра eflags.
Набор регистров 41 ФЛАГИ СОСТОЯНИЯ: флаг вложенности задачи Рис. 2.2. Содержимое регистра eflags Исходя из особенностей использования, флаги регистра ef lags/f lags можно раз- делить на три группы: О 8 флагов состояния. Эти флаги могут изменяться после выполнения машин- ных команд. Флаги состояния регистра eflags отражают особенности ре- зультата исполнения арифметических или логических операций. Это дает возможность анализировать состояние вычислительного процесса и реаги- ровать на него с помощью команд условных переходов и вызовов подпро- грамм. В табл. 2.1 приведены флаги состояния и указано их назначение; О 1 флаг управления. Обозначается как df (Directory Flag). Он находится в 10-м бите регистра eflags и используется цепочечными командами. Значение флага df определяет направление поэлементной обработки в этих операци- ях: от начала строки к концу (df = 0), либо наоборот, от конца строки к ее началу (df = 1). Для работы с флагом df существуют специальные команды cld (снять флаг df) и std (установить флаг df). Применение этих команд позволяет привести флаг df в соответствие с алгоритмом и обеспечить авто- матическое увеличение или уменьшение счетчиков при выполнении опера- ций со строками; О 5 системных флагов, управляющих вводом/выводом, маскируемыми преры- ваниями, отладкой, переключением между задачами и виртуальным режи- мом 8086. Прикладным программам не рекомендуется модифицировать без
42 Урок 2. Архитектура персонального компьютера необходимости эти флаги, так как в большинстве случаев это приведет к пре- рыванию работы программы. В табл. 2.2 перечислены системные флаги, их назначение. Таблица 2.1. Флаги состояния Мнемоника флага Флаг Номер бита в eflags Содержание и назначение cf Флаг переноса (Carry Flag) 0 1 — арифметическая операция произвела перенос из старшего бита результата. Стар- шим является 7-й, 15-й или 31-й бит в зави- симости от размерности операнда; 0 — переноса не было pf Флаг паритета (Parity Flag) 2 1—8 младших разрядов (этот флаг — только для 8 младших разрядов операнда любого размера) результата содержат чет- ное число единиц; 0 — 8 младших разрядов результата содер- жат нечетное число единиц af Вспомогатель- ный флаг переноса (Auxiliary carry Flag) 4 Только для команд, работающих с BCD- числами. Фиксирует факт заема из млад- шей тетрады результата: 1 — в результате операции сложения был произведен перенос из разряда 3 в старший разряд или при вычитании был заем в раз- ряд 3 младшей тетрады из значения в стар- шей тетраде; 0 — переносов и заемов в (из) 3 разряд(а) младшей тетрады результата не было zf Флаг нуля (Zero Flag) 6 1 — результат нулевой; 0 — результат ненулевой sf Флаг знака (Sign Flag) 7 Отражает состояние старшего бита резуль- тата (биты 7, 15 или 31 для 8, 16 или 32-разрядных операндов соответственно): 1 — старший бит результата равен 1; 0 — старший бит результата равен 0 of Флаг переполнения (Overflow Flag) И Флаг of используется для фиксирования факта потери значащего бита при арифме- тических операциях: 1 — в результате операции происходит пе- ренос (заем) в (из) старшего, знакового бита результата (биты 7, 15 или 31 для 8, 16 или 32-разрядных операндов соответ- ственно); 0 — в результате операции не происходит переноса (заема) в (из) старшего, знакового бита результата
Набор регистров 43 Мнемоника Флаг флага Номер бита Содержание и назначение в eflags iopl nt Уровень привилегий ввода-вывода (Input/Output Privilege Level) Флаг вложен- ности задачи (Nested Task) 12, 13 14 Используется в защищенном режиме рабо- ты микропроцессора для контроля доступа к командам ввода-вывода в зависимости от привилегированности задачи Используется в защищенном режиме рабо- ты микропроцессора для фиксации того факта, что одна задача вложена в другую Таблица 2.2. Системные флаги Мнемоника флага Флаг Номер бита в eflags Содержание и назначение tf Флаг трассировки (Trace Flag) 8 Предназначен для организации пошаговой работы микропроцессора: 1 — микропроцессор генерирует прерывание с номером 1 после выполнения каждой ма- шинной команды. Может использоваться при отладке программ, в частности отладчиками; 0 — обычная работа if Флаг прерывания (Interrupt enable Flag) 9 Предназначен для разрешения или запре- щения (маскирования) аппаратных преры- ваний (прерываний по входу INTR): 1 — аппаратные прерывания разрешены; 0 — аппаратные прерывания запрещены rf Флаг возобновления (Resume Flag) 16 Используется при обработке прерываний от регистров отладки vm Флаг виртуального 8086 (Virtual 8086 Mode) 17 Признак работы микропроцессора в режиме виртуального 8086: 1 — процессор работает в режиме виртуаль- ного 8086; 0 — процессор работает в реальном или за- щищенном режиме ас Флаг контроля выравнивания (Alignment Check) 18 Предназначен для разрешения контроля выравнивания при обращениях к памяти. Используется совместно с битом ат в сис- темном регистре сгО. К примеру, Pentium разрешает размещать команды и данные с любого адреса. Если требуется контролиро- вать выравнивание данных и команд по ад- ресам кратным 2 или 4, то установка дан- ных битов приведет к тому, что все обраще- ния по некратным адресам будут возбуж- дать исключительную ситуацию
44 Урок 2. Архитектура персонального компьютера eip/ip (Instruction Pointer register) — указатель команд. Регистр eip/ip имеет разрядность 32/16 бит и содержит смещение следующей подлежащей выпол- нению команды относительно содержимого сегментного регистра cs в текущем сегменте команд. Этот регистр непосредственно недоступен программисту, но загрузка и изменение его значения производятся различными командами управления, к которым относятся команды условных и безусловных переходов, вызова процедур и возврата из процедур. Возникновение прерываний также приводит к модификации регистра eip/ip. Организация памяти Физическая память, к которой микропроцессор имеет доступ по шине адреса (см. рис. 1.2), называется оперативной памятью (или оперативным запомина- ющим устройством — ОЗУ). На самом нижнем уровне память компьютера можно рассматривать как массив битов. Один бит может хранить значение О или 1. Для физической реализации битов и работы с ними идеально подходят логические схемы. Но микропроцессору неудобно работать с памятью на уров- не битов, поэтому реально ОЗУ организовано как последовательность ячеек — байтов. Один байт состоит из 8 бит. Каждому байту соответствует свой уникальный адрес (его номер), называемый физическим. Диапазон значе- ний физических адресов зависит от разрядности шины адреса микропроцессо- ра. Для i486 и Pentium он находится в пределах от 0 до 232 - 1 (4 Гбайт). Механизм управления памятью полностью аппаратный. Это означает, что про- грамма не может сама сформировать физический адрес памяти на адресной шине. Ей приходится «играть» по правилам микропроцессора. Что это за пра- вила, мы узнаем чуть ниже. Пока же отметим, что в конечном итоге этот меха- низм позволяет обеспечить: О компактность хранения адреса в машинной команде; О гибкость механизма адресации; О защиту адресных пространств задач в многозадачной системе; О поддержку виртуальной памяти. Микропроцессор аппаратно поддерживает несколько моделей использования оперативной памяти: О сегментированную модель. В этой модели память для программы делится на непрерывные области памяти (сегменты), а сама программа может обра- щаться только к данным, которые находятся в этих сегментах; О страничную модель. Ее можно рассматривать как надстройку над сегменти- рованной моделью. В случае использования этой модели оперативная па- мять рассматривается как совокупность блоков фиксированного размера 4 Кбайт. Основное применение этой модели связано с организацией вирту-
Организация памяти 45 альной памяти, что позволяет операционной системе использовать для работы программ пространство памяти большее, чем объем физической памяти. Для микропроцессоров i486 и Pentium размер возможной виртуальной памяти может достигать 4 Тбайт (терабайт). Особенности использования и реализации этих моделей зависят от режима ра- боты микропроцессора: О Режим реальных адресов, или просто реальный режим. Это режим, в котором работал 18086. Наличие его в i486 и Pentium обусловлено тем, что фирма Intel старается обеспечить в новых моделях микропроцессоров функциони- рование программ, разработанных для ранних моделей микропроцессоров. О Защищенный режим. Этот режим позволяет максимально реализовать все архитектурные идеи, заложенные в модели микропроцессоров Intel, начиная с i80286. Программы, разработанные для 18086 (реального режима), не могут функционировать в защищенном режиме. Одна из причин этого связана именно с особенностями формирования физического адреса в защищенном режиме. О Режим виртуального 8086. Переход в этот режим возможен, если микропро- цессор уже находится в защищенном режиме. Отличительной особенностью этого режима является возможность одновременной работы нескольких программ, разработанных для i8086. Несмотря на то, что микропроцессор находился в защищенном режиме, в режиме виртуального 18086 возможна работа программ реального режима. Это объясняется тем, что процесс фор- мирования физического адреса для этих программ производится по прави- лам реального режима. В этой книге мы еще вернемся к подробному обсуждению различных аспектов реального и защищенного режимов. На данном уроке рассмотрим только осо- бенности работы с оперативной памятью для реального режима, в котором поддерживается только сегментированная модель организации памяти. Все вопросы, связанные с защищенным режимом, и, в частности, особенности орга- низации памяти в этом режиме рассмотрим на уроке 16. Сегментированная модель памяти Вначале постараемся формально определить фундаментальные понятия сег- мента и сегментации. Сегментация — механизм адресации, обеспечивающий существование несколь- ких независимых адресных пространств как в пределах одной задачи, так и в системе в целом для защиты задач от взаимного влияния. В основе механизма сегментации лежит понятие сегмента, который представляет собой независи- мый, поддерживаемый на аппаратном уровне блок памяти. Когда мы рассматривали сегментные регистры, то отмечали, что для микропро- цессоров Intel, начиная с 18086, принят особый подход к управлению памятью.
46 Урок 2. Архитектура персонального компьютера Каждая программа в общем случае может состоять из любого количества сегментов, но непосредственный доступ она имеет только к трем основным: кода, данных и сте- ка, — и от одного до трех дополнительных сегментов данных. Программа никогда не знает, по каким физическим адресам будут размещены ее сегменты. Этим занимает- ся операционная система. Операционная система размещает сегменты программы в оперативной памяти по определенным физическим адресам, после чего помещает значения этих адресов в определенные места. Куда именно, зависит от режима рабо- ты микропроцессора. Так, в реальном режиме эти адреса помещаются непосред- ственно в соответствующие сегментные регистры, а в защищенном режиме они размещаются в элементы специальной системной дескрипторной таблицы. Внутри сегмента программа обращается к адресам относительно начала сегмента линейно, то есть начиная с 0 и заканчивая адресом, равным размеру сегмента. Этот относитель- ный адрес, или смещение, который микропроцессор использует для доступа к дан- ным внутри сегмента, называется эффективным. Рассмотрим порядок формирования физического адреса в реальном режиме. Порядок формирования физического адреса в защищенном режиме мы рас- смотрим на уроке 16. Под физическим адресом понимается адрес памяти, вы- даваемый на шину адреса микропроцессора (см. рис. 1.2). Другое название этого адреса — линейный адрес. Эта двойственность в названии обусловлена наличием страничной модели организации оперативной памяти. Эти названия являются синонимами только при отключении страничного преобразования адреса (в реальном режиме страничная адресация всегда отключена). Странич- ная модель, как мы отметили выше, является надстройкой над сегментирован- ной моделью. В страничной модели линейный адрес и физический адрес име- ют разные значения. Чуть ниже мы будем обсуждать рис. 2.3, на котором показан порядок формирования адреса в реальном режиме работы микропро- цессора. Обратите внимание на наличие в этой схеме устройства страничного преобразования адреса. Это устройство предназначено для того, чтобы совмес- тить две принципиально разные модели организации оперативной памяти и выдать на шину адреса истинное значение физического адреса памяти. Формирование физического адреса в реальном режиме В реальном режиме механизм адресации физической памяти имеет следующие характеристики: О диапазон изменения физического адреса от 0 до 1 Мбайт. Эта величина определяется тем, что шина адреса 18086 имела 20 линий; О максимальный размер сегмента 64 Кбайт. Это объясняется 16-разрядной архитектурой i8086. Нетрудно подсчитать, что максимальное значение, ко- торое могут содержать 16-разрядные регистры, составляет 216 - 1, что при- менительно к памяти и определяет величину 64 Кбайт; О для обращения к конкретному физическому адресу оперативной памяти необходимо определить адрес начала сегмента (сегментную составляющую)
Организация памяти и смещение внутри сегмента. Но мы помним, что сегментная составляющая адреса (или база сегмента) представляет собой всего лишь 16-битное значе- ние, помещенное в один из сегментных регистров. Максимальное значение, которое при этом получается, соответствует 216 - 1. Если так рассуждать, то получается, что адрес начала сегмента может быть только в диапазоне 0-64 Кбайт от начала оперативной памяти. Возникает вопрос о том, как ад- ресовать остальную часть оперативной памяти вплоть до 1 Мбайт с учетом того, что размер самого сегмента не превышает 64 Кбайт. Дело в том, что в сегментном регистре содержатся только старшие 16 бит физического адреса начала сегмента. Недостающие младшие четыре бита 20-битного адреса получаются сдвигом значения в сегментном регистре влево на 4 разряда. Эта операция сдвига выполняется аппаратно и для программного обеспечения абсолютно прозрачна. Получившееся 20-битное значение и является настоя- щим физическим адресом, соответствующим началу сегмента. Что касается второго компонента, участвующего в образовании физического адреса неко- торого объекта в памяти, — смещения, — то оно представляет собой 16-бит- ное значение. Это значение может содержаться явно в команде либо косвен- но в одном из регистров общего назначения. В микропроцессоре эти две составляющие складываются на аппаратном уровне, в результате чего полу- чается физический адрес памяти размерностью 20 бит. Данный механизм образования физического адреса позволяет сделать программное обеспече- ние перемещаемым, то есть не зависящим от конкретных адресов загрузки его в оперативной памяти. Он показан на рис. 2.3. Рис. 23. Механизм формирования физического адреса в реальном режиме
48 Урок 2. Архитектура персонального компьютера На рис. 2.3 хорошо видно, как формируется некоторый целевой физический адрес: сегментная часть извлекается из одного из сегментных регистров, сдви- гается на четыре разряда влево и суммируется со смещением. В свою очередь, видно, что значение смещения можно получить минимум из одного и макси- мум из трех источников: из значения смещения в самой машинной команде и (или) из содержимого одного базового и (или) одного индексного регистра. Количество источников, участвующих в формировании смещения, определяет- ся кодированием конкретной машинной команды, и если таких источников не- сколько, то значения в них складываются. В заключение заметим, что не стоит волноваться насчет того, что существует несоответствие размеров шины адреса микропроцессора i486 или Pentium (32 бита) и 20-битного значения физичес- кого адреса реального режима. Пока микропроцессор находится в реальном режиме, старшие 12 линий шины адреса попросту недоступны, хотя при опре- деленных условиях и существует возможность работы с первыми 64 Кбайт оперативной памяти, лежащими сразу после первого мегабайта. Недостатки такой организации памяти: О сегменты бесконтрольно размещаются с любого адреса, кратного 16 (так как содержимое сегментного регистра аппаратно смещается на 4 разряда). Как следствие, программа может обращаться по любым адресам, в том числе и реально не существующим; О сегменты имеют максимальный размер 64 Кбайт; О сегменты могут перекрываться с другими сегментами. Желанием ввести в архитектуру средства, позволяющие избавиться от указан- ных недостатков, в частности, и обусловлено появление защищенного режима (см. урок 16). Типы данных Понятие типа данных носит двойственный характер. С точки зрения размер- ности микропроцессор аппаратно поддерживает следующие основные типы данных (рис. 2.4): О Байт — восемь последовательно расположенных битов, пронумерованных от О до 7, при этом бит 0 является самым младшим значащим битом. О Слово — последовательность из двух байт, имеющих последовательные адре- са. Размер слова — 16 бит; биты в слове нумеруются от 0 до 15. Байт, содер- жащий нулевой бит, называется младшим байтом, а байт, содержащий 15-й бит — старшим байтом. Микропроцессоры Intel имеют важную особен- ность — младший байт всегда хранится по меньшему адресу. Адресом слова считается адрес его младшего байта. Адрес старшего байта может быть ис- пользован для доступа к старшей половине слова.
Типы данных 49 О Двойное слово — последовательность из четырех байт (32 бита), расположен- ных по последовательным адресам. Нумерация этих бит производится от 0 до 31. Слово, содержащее нулевой бит, называется младшим словом, а слово, со- держащее 31-й бит, — старшим словом. Младшее слово хранится по меньше- му адресу. Адресом двойного слова считается адрес его младшего слова. Адрес старшего слова может быть использован для доступа к старшей половине двойного слова. О Учетверенное слово — последовательность из восьми байт (64 бита), распо- ложенных по последовательным адресам. Нумерация бит производится от О до 63. Двойное слово, содержащее нулевой бит, называется младшим двой- ным словом, а двойное слово, содержащее 63-й бит, — старшим двойным сло- вом. Младшее двойное слово хранится по меньшему адресу. Адресом учетве- ренного слова считается адрес его младшего двойного слова. Адрес старшего двойного слова может быть использован для доступа к старшей половине учетверенного слова. Рис. 2.4. Основные типы данных микропроцессора Кроме трактовки типов данных с точки зрения их разрядности, микро- процессор на уровне команд поддерживает логическую интерпретацию этих типов (рис. 2.5). О Целый тип со знаком — двоичное значение со знаком, размером 8, 16 или 32 бита. Знак в этом двоичном числе содержится в 7, 15 или 31-м бите соответственно. Ноль в этих битах в операндах соответствует положитель- ному числу, а единица — отрицательному. Отрицательные числа представ- ляются в дополнительном коде. Числовые диапазоны для этого типа данных следующие: 8-разрядное целое — от —128 до +127; 16-разрядное целое — от -32 768 до +32 767; 32-разрядное целое — от -231 до +231 - 1. О Целый тип без знака — двоичное значение без знака, размером 8, 16 или 32 бита. Числовой диапазон для этого типа следующий: байт — от 0 до 255; слово — от 0 до 65 535; двойное слово — от 0 до 232 - 1.
50 Урок 2. Архитектура персонального компьютера О Указатель на память бывает двух типов. • Ближний тип — 32-разрядный логический адрес, представляющий собой относительное смещение в байтах от начала сегмента. Эти указатели могут также использоваться в сплошной (плоской) модели памяти, где сегмент- ные составляющие одинаковы. • Дальний тип — 48-разрядный логический адрес, состоящий из двух частей: 16-разрядной сегментной части — селектора и 32-разрядного смещения. О Цепочка представляет собой некоторый непрерывный набор байтов, слов или двойных слов максимальной длиной до 4 Гбайт. О Битовое поле представляет собой непрерывную последовательность бит, в ко- торой каждый бит является независимым и может рассматриваться как от- дельная переменная. Битовое поле может начинаться с любого бита любого байта и содержать до 32 бит. О Неупакованный двоично-десятичный тип — байтовое представление десятич- ной цифры от 0 до 9. Неупакованные десятичные числа хранятся как байто- вые значения без знака по одной цифре в каждом байте. Значение цифры определяется младшим полубайтом. О Упакованный двоично-десятичный тип представляет собой упакованное пред- ставление двух десятичных цифр от 0 до 9 в одном байте. Каждая цифра хра- нится в своем полубайте. Цифра в старшем полубайте (биты 4-7) является старшей. Битовое поле: Целые со знаком: . ,^айт слово |3н| двойное слово |зД |3н| 31 15 7 О Неупакованное десятичное (BCD) число: Упакованное десятичное (BCD) число: OOOOIBCD ... । ... OOOOIBCD OOOOIBCD Указатель ближнего типа: BCDIBCD ... । ... BCDIBCD BCDIBCD 15 7 0 15 7 О Указатель дальнего типа: I I I СЕЛЕКТОР I I I Illi III СМЕЩЕНИЕ I I III I I 47 31 15 7 О Рис. 2.5. Основные логические типы данных микропроцессора Отметим, что «Зн» на рис. 2.5 означает знаковый бит.
Формат команд 51 Формат команд Программирование на уровне машинных команд — это тот минимальный уро- вень, на котором еще возможно программирование компьютера. Система ма- шинных команд должна быть достаточной для того, чтобы реализовать требуе- мые действия, выдавая указания аппаратуре машины. Каждая машинная команда состоит из двух частей: операционной части, определяющей, «что делать?», и операндной части, определяющей объекты обработки, то есть то, «над чем де- лать?». Вся эта информация, разумеется, должна быть определенным образом закодиро- вана и формализована. Мы вернемся к этому вопросу позже на уроке 6, где под- робно разберемся со структурой и правилами формирования машинной коман- ды. Отметим только интересующий нас архитектурный аспект. В машинную команду микропроцессора явно или неявно входят следующие эле- менты: О поле префиксов — элемент команды, который уточняет либо модифицирует действие этой команды в следующих аспектах: в замена сегмента, если нас по какой-либо причине не удовлетворяет сег- мент по умолчанию; • изменение размерности адреса; в изменение размерности операнда; • указание на необходимость повторения данной команды; О поле кода операции, определяющее действие данной команды. Одной и той же команде могут соответствовать несколько кодов операций в зависимости от ее операндов; О поле операндов', содержит от 0 до 2 элементов. Важной особенностью машинных команд является то, что они не могут мани- пулировать одновременно двумя операндами, находящимися в оперативной памяти. Это означает, что в команде могут быть использованы один регистр и/или регистр и некоторый операнд, который может либо непосредственно на- ходиться в команде, либо располагаться в памяти. По этой причине возможны только следующие сочетания операндов в команде: О регистр — регистр; О регистр — память; О память — регистр; О непосредственный операнд — регистр; О непосредственный операнд — память.
52 Урок 2. Архитектура персонального компьютера Обработка прерываний По определению прерывание означает временное прекращение основного про- цесса вычислений для выполнения некоторых запланированных или незапла- нированных действий, вызываемых работой аппаратуры или программы. Эти действия могут носить сервисный характер, быть запросами со стороны про- граммы пользователя на выполнение обслуживания со стороны операционной системы либо быть реакцией на нештатные ситуации. Механизм прерываний поддерживается на аппаратном уровне и позволяет реа- лизовать как эффективное взаимодействие программ с операционной системой, так и эффективное управление программой аппаратной частью компьютера. В зависимости от источника, прерывания классифицируются так: О аппаратные, возникающие как реакция микропроцессора на физический сигнал от некоторого устройства компьютера (клавиатура, системный тай- мер, жесткий диск и т. д.). По времени возникновения эти прерывания асинхронны, то есть происходят в случайные моменты времени; О программные, которые вызываются искусственно с помощью соответствую- щей команды из программы (команда int). Они предназначены для выпол- нения некоторых действий операционной системы. Эти прерывания явля- ются синхронными', О исключения — разновидность программных прерываний, являющихся реак- цией микропроцессора на нестандартную ситуацию, возникшую внутри микропроцессора во время выполнения некоторой команды программы. Более подробно механизм прерываний будет обсуждаться на уроке 15. Подведем некоторые итоги: 0 Понимание архитектуры ЭВМ является ключевым для изучения ассембле- ра. Это касается любого типа компьютера. Структура ассемблера, формат его команд, адресация операндов и т. д. полностью отражают особенности архитектуры компьютера. Есть общие архитектурные свойства, присущие всем современным машинам фон-неймановской архитектуры, и есть частные свойства, присущие конкретному типу компьютеров. 0 Целью изучения архитектуры является: • выявление набора доступных для программирования регистров, их функционального назначения и структуры; • понимание организации оперативной памяти и порядка ее использования; • знакомство с типами данных; • изучение формата машинных команд; • выяснение организации обработки прерываний.
Обработка прерываний 53 0 Микропроцессор содержит 32 доступных тем или иным образом регистра. Они делятся на пользовательские и системные регистры. 0 Пользовательские регистры имеют определенное функциональное назначе- ние. Среди них особо нужно выделить регистр флагов eflags и регистр указа- теля команды eip. Назначение регистра eflags — отражать состояние микро- процессора после выполнения последней машинной команды. Регистр eip содержит адрес следующей выполняемой машинной команды. Доступ к этим регистрам, в силу их специфики, со стороны программ пользователя огра- ничен. 0 Микропроцессор имеет три режима работы: • реальный режим, который использовался для 18086 и поддерживается до сих пор для обеспечения совместимости программного обеспечения; • защищенный режим, который впервые появился в i80286; • режим виртуального 18086. Обеспечивает полную эмуляцию микропро- цессора 18086, позволяя при этом организовать многозадачную работу нескольких таких программ. 0 Микропроцессор имеет сложную систему управления памятью, работа кото- рой зависит от режима микропроцессора. 0 Микропроцессор благодаря гибкой системе команд поддерживает довольно большую номенклатуру типов данных.
____________ Разработка простой Ж) УРОК ~ программы на ассемблере □ «Ассемблерный» уровень разработки программы □ Пример простой программы на ассемблере □ Разбор программы
Возможно, вас несколько утомили пространные экскурсы в историческую и архитектурную области. Многое вам, возможно, осталось непонятным, особен- но если это первая книга об архитектуре компьютера и его внутреннем языке, которую вы читаете. Смеем вас уверить, что это никак не является поводом к тому, чтобы отложить чтение. Всем известно, что здание прочно стоит на хоро- шем фундаменте. В нашем случае фундаментом являются знания о компью- тере. Естественно, что чем фундаментальнее и богаче они будут, тем более на- дежную и эффективную программу мы сможем построить. Именно это и побуждает нас столь подробно рассматривать вопросы архитектуры, хотя все можно было свести к обсуждению набора команд и основных приемов про- граммирования, в итоге получив карточный домик со всеми вытекающими из этого последствиями. Ободрившись этими замечаниями, посмотрим, что нас ждет дальше. Чтобы оживить повествование, мы приведем пример простой, но полноценной про- граммы на ассемблере. В качестве преамбулы к постановке задачи обсудим одну проблему. На уроке 2 при обсуждении архитектуры мы перечислили большое количество регистров. Как правило, большинство из них задействова- но при работе практически любой программы. Было бы интересно во время работы программы посмотреть их содержимое. Это нетрудно, если использо- вать специальную программу — отладчик. Но как сделать это динамически, не используя других программ? Или, к примеру, как решить обратную задачу — ввести с клавиатуры значения в регистр? Можно, конечно, написать соответ- ствующую программу. Тот, кто работал на одном из современных языков высо- кого уровня, скажет: «Подумаешь, я вызову функцию, предназначенную для вывода содержимого регистра, и нет проблем». Действительно, проблем нет, если нас не интересует эффективность кода. Нельзя забывать, что между желе- зом компьютера и любым языком высокого уровня стоит компилятор, который может генерировать, мягко говоря, не очень эффективный код. Для критичных по размеру системных программ, при написании которых порой учитывается каждый байт, практика использования исключительно языков высокого уровня выглядит сомнительной. На помощь приходит ассемблер. Если представление в машинном виде программы на языке высокого уровня — это черный ящик (в том смысле, что мы мало в чем можем повлиять на компиляцию), то, приме- няя язык ассемблера, мы можем учитывать тончайшие системные и архитек- турные нюансы. Именно поэтому не следует принижать значение низкоуровне- вого программирования по сравнению с мощными языками высокого уровня.
56 Урок 3. Разработка простой программы на ассемблере Разработчики компиляторов по сей день обязательно оставляют возможность выхода на уровень ассемблера. Эта возможность может быть реализована в форме ассемблерных вставок в программу или подключения внешних проце- дур на ассемблере. В любом случае, это позволяет повысить качество получаю- щегося кода. Если вы убедились в полезности (естественно, в разумных пределах) использо- вания ассемблера при разработке ваших программ, то продолжим разбираться с нашей проблемой, которая заключается в том, что нам требуется визуализи- ровать содержимое некоторого регистра или ввести в него значение. При этом мы сразу столкнемся с другой частной проблемой — преобразования данных. Причина здесь в том, что компьютер понимает только те типы данных, кото- рые поддерживаются его системой команд. Поэтому на практике часто возни- кает необходимость преобразования данных из одного представления в другое. На данном уроке мы только наметим контуры решения этой задачи и разберем частный случай. Полностью мы решим ее, когда познакомимся с понятием мак- рокоманды. Для начала нужно продумать алгоритм. Ввод информации с клавиатуры и вы- вод ее на экран осуществляются в символьном виде. Кодирование этой инфор- мации производится согласно таблице ASCII (см. приложение 3). В таблице ASCII каждый символ кодируется одним байтом. Если работа происходит с числами, то при попытке их обработать сразу возникает проблема: команд для арифметической обработки чисел в символьном виде нет. Что делать? Выход очевиден: нужно преобразовать символьную информацию к формату, поддер- живаемому машинными командами. После такого преобразования нужно вы- полнить необходимые вычисления и преобразовать результат обратно к сим- вольному виду. Затем следует отобразить информацию на мониторе. На уроке 2 мы обсуждали логические типы данных, поддерживаемые на уров- не машинных команд. Из приведенного перечня видно, что поддерживаются арифметические операции над двоичными числами, со знаком и без знака, и операции над десятичными числами. Десятичные числа, другое их название BCD-числа (Binary Code Decimal — двоично-десятичный код), наиболее удоб- ны для программирования прикладных задач. Недостаток этих чисел в том, что для них требуется разработка специфических алгоритмов. Сейчас мы рассмот- рим преобразование шестнадцатеричного числа из двух цифр, вводимого с кла- виатуры (то есть в символьном виде), к двоичному виду. После выполнения этой операции полученное число можно использовать как операнд в двоичных арифметических операциях. Программа, представленная в листинге 3.1, является одним из вариантов реше- ния этой задачи. В основу ее алгоритма положена особенность, связанная с ACSII-кодами символов соответствующих шестнадцатеричных цифр. Шест- надцатеричные цифры формируются из символов 0, 1,..., 9, А, В, С, D, Е, F, а, Ь, с, d, е, f, например: 12Af, 34ad.
57 Откроем приложение 3 и посмотрим значения кодов ASCII, соответствующих этим символам. На первый взгляд непонятна популярность способа представ- ления информации в виде шестнадцатеричных чисел. Мы уже знаем, что аппа- ратура компьютера построена на логических микросхемах и поэтому работает только с двоичной информацией. Если нам требуется проанализировать, на- пример, состояние некоторой области памяти, то разобраться в нагромождении нулей и единиц будет очень непросто. Для примера рассмотрим двоичную пос- ледовательность: 010100010101011110101101110101010101000101001010 Это представление очень не наглядно. Мы говорили, что оперативная память состоит из ячеек — байтов — по 8 бит. Приведенная выше цепочка бит при разбиении ее на байты будет выглядеть так: 01010001 01010111 10101101 11010101 01010001 01001010 С наглядностью стало получше, но если область памяти будет больше, то ра- зобраться будет все равно сложно. Давайте проведем еще одну операцию: каж- дый байт разобьем на две части по 4 бита — тетрады*. 0101 0001 0101 0111 1010 1101 1101 0101 0101 0001 0100 1010 И вот тут проявляется замечательное свойство шестнадцатеричных чисел — каждой тетраде можно поставить в соответствие одну шестнадцатеричную цифру (табл. 3.1). Таблица 3.1. Шестнадцатеричные цифры Двоичная тетрада Шестнадцатеричная цифра 0000 0 0001 1 0010 2 ООН 3 0100 4 0101 5 оно 6 0111 7 1000 8 1001 9 1010 А, а 1011 В, b 1100 С, с 1101 D, d 1110 Е, е 1111 F, f
58 Урок 3. Разработка простой программы на ассемблере Если заменить в последней полученной нами строке тетрады на соответствующие шестнадцатеричные цифры, то получим последовательность 51 57 ad d5 51 8а: 0101 0001 0101 0111 1010 1101 1101 0101 0101 0001 0100 1010 5 1 5 7 a d d 5518а Каждый байт наглядно представляется двумя шестнадцатеричными цифрами. Если привыкнуть к такой записи, то она оказывается очень удобной при рабо- те с памятью, дисками и т. д. Вернемся к нашей задаче. В табл. 3.2 приведены шестнадцатеричные цифры и их кодировка в коде ACSII. Таблица 3.2. Кодировка шестнадцатеричных цифр Символ шестнадцатеричной цифры ASCII-код (двоичное представление) Двоичная тетрада 0 ЗОН (ООН 0000) 0000 1 31h (ООН 0001) 0001 2 32h (ООН 0010) 0010 3 33h (ООН ООН) ООН 4 34h (ООН 0100) 0100 5 35h (ООН 0101) 0101 6 36h (ООН ОНО) оно 7 37h (ООН 0111) 0111 8 38h (ООН 1000) 1000 9 39h (ООН 1001) 1001 А а 41h(0100 0001), 61h (ОНО 0001) 1010 В b 42h (0100 0010), 62h (ОНО 0010) 1011 Сс 43h (0100 ООН) 63h (ОНО ООН) 1100 D d 44h (0100 0100) 64h (ОНО 0100) 1101 Е е 45h (0100 0101) 65h (0100 0101) 1110 F f 46h (0100 0110) 66h (ОНО ОНО) 1111 Посмотрите внимательно и сравните эти представления. К примеру, рассмот- рим шестнадцатеричный 0. Соответствующий ему код ASCII — 30h, который в двоичном записывается как ООН 0000, а соответствующее двоичное число сов- падает с двоичным представлением нуля 0000 0000. Первый вывод: для шестнадцатеричных цифр 0...9 код ACSII отличается от со- ответствующего двоичного представления на ООН 0000, или 30h. Поэтому для преобразования кода ACSII в шестнадцатеричное число есть два пути: 1. Выполнить двоичное вычитание: (код ACSII)h — 30h. 2. Обнулить старшую тетраду байта с символом шестнадцатеричной цифры в коде ACSII.
59 Видно, что с шестнадцатеричными цифрами в диапазоне от 0 до 9 все просто. Что касается шестнадцатеричных цифр a, b, с, d, е, f, то здесь ситуация несколь- ко сложнее. Алгоритм должен распознавать эти символы и производить до- полнительные действия при их преобразовании в соответствующее двоичное число. Посмотрите внимательно на символы шестнадцатеричных цифр и соот- ветствующие им двоичные представления (см. табл. 3.2). Видно, что для пре- образования уже недостаточно простого вычитания или обнуления старшей тетрады. При анализе таблицы ASCII (см. приложение 3) видно, что символы прописных букв шестнадцатеричных цифр отличаются от своего двоичного эквивалента на величину 37h. Соответствующие строчные буквы шестнадцате- ричных цифр отличаются от своего двоичного эквивалента на 67h. Отсюда следует второй вывод: алгоритм преобразования должен различать прописные и строчные буквенные символы шестнадцатеричных цифр и кор- ректировать значение кода ASCII на величину 37h или 67h. Вы, наверное, заметили, что после записи значения шестнадцатеричной цифры следует символ «Ь». Это сделано для того, чтобы транслятор мог отличить в программе одинаковые по форме записи десятичные и шестнадцатеричные числа. К примеру, числа 1578 и 1578h выглядят одинаково, но имеют разные значения. Более того, как вы думаете, какое значение в тексте исходной про- граммы имеет лексема fe023? Это может быть и некоторый идентификатор, и, судя по набору символов, шестнадцатеричное число. Для того чтобы однознач- но описать в тексте программы на ассемблере подобное шестнадцатеричное число, его дополняют ведущим нулем и в конце ставят «Ь». В нашем примере это будет выглядеть так: 0fe023h. Разберем программу, представленную в листинге 3.1. Если у вас все еще оста- лись неясные моменты с представлениями чисел в различных форматах, то не отчаивайтесь, так как на уроке 5 эти вопросы будут рассмотрены более систе- матизированно. Листинг 3.1. Пример программы на ассемблере <1> Prg_3_1.asm <2> Программа преобразования двузначного шестнадцатеричного числа <3> в символьном виде в двоичное представление. <4> Вход: исходное шестнадцатеричное число из двух цифр, <5> вводится с клавиатуры. <6> Выход: результат преобразования должен <7> быть в регистре dl. <8> <9> data segment para public "data” ;сегмент данных <io> message db "Введите две шестнадцатеричные цифры,$" <и> data ends <12> stk segment stack <13> db 256 dup (“?”) ;сегмент стека продолжение &
60 Урок 3. Разработка простой программы на ассемблере <14> stk ends <15> code segment para public “code" ; начало сегмента кода <1б> main proc ; начало процедуры main <17> assume cs:code,ds:data,ss:stk <18> mov ax,data ; адрес сегмента данных в регистр ах <19> mov ds,ах ;ах в ds <20> mov ah, 9 <21> mov dx, offset message <22> int 21h <23> хог ax, ax ; очистить регистр ax <24> mov ah, 1h ;1h в регистр ah <25> int 21h ; генерация прерывания с номером 21 h <2б> mov dl,al ; содержимое регистра al в регистр dl <27> sub dl,30h ; вычитание: (dl)=(dl)-30h <28> cmp dl,9h сравнить (dl) c 9h <29> jle M1 ; перейти на метку М1 если dl<9h или dl=9h <30> sub dl,7h ;вычитание: (dl)=(dl)-7h <31> М1: ; определение метки М1 <32> mov cl,4h ; пересылка 4h в регистр cl <33> shl dl,cl ; сдвиг содержимого dl на 4 разряда влево <34> int 21h ; вызов прерывания с номером 21h <35> sub al, 30h ; вычитание: (dl)=(dl)-30h <3б> cmp al, 9h сравнить (al) c 9h 28 <37> jle М2 ; перейти на метку М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 в регистр ax <42> int 21h ; вызов прерывания с номером 21h <43> main endp ; конец процедуры main <44> code ends ;конец сегмента кода <45> end main ; конец программы с точкой входа main Подробно структура и правила оформления программ будут изложены на уро- ке 6. Сейчас мы рассмотрим эти правила лишь в части, касающейся нашего примера. На уроке 2 мы выяснили, что язык ассемблера является символичес- ким представлением машинного языка. Поэтому естественным представляется то обстоятельство, что сам язык и программы на нем отражают архитектуру компьютера. Посмотрите внимательно на программу. В ней есть три участка, заключенные между директивами segment и ends (строки 9 и И, 12 и 14, 15 и 44). Микропро-
61 цессор аппаратно поддерживает шесть адресно-независимых областей памяти: сегмент кода, сегмент данных, сегмент стека и три дополнительных сегмента данных. Наша программа использует только первые три из них. Таким образом, уже сама структура программы отражает особенности организации памяти. Строки 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 — правильный двоичный эквивалент введен-
62 Урок 3. Разработка простой программы на ассемблере ного символа шестнадцатеричной цифры. Если значение в dl больше 9, то вве- денная цифра является одним из символов А, В, С, D, Е, F (строчные буквы для экономии места обрабатывать не будем). В первом случае строка 29 пере- даст управление на метку М1. При обработке цифры 5 это условие как раз вы- полняется, поэтому происходит переход на метку М1 (строка 31). Каждая шестнадцатеричная цифра занимает одну тетраду. У нас — две таких цифры, поэтому нужно их разместить так, чтобы старшинство разрядов со- хранялось. Строки 32-33 сдвигают значение в dl на 4 разряда влево, тем са- мым освобождая место в младшей тетраде под младшую шестнадцатеричную цифру. Строка 34 вводит вторую шестнадцатеричную цифру С (ее ASCII-код 63h) в регистр al: ах = 0000 0001 0100 0011 Строки 35-37 выясняют, попадает ли двоичный эквивалент второго символа шестнадцатеричной цифры в диапазон 0...9. Наша вторая цифра не попадает в диапазон, поэтому для получения правильного двоичного эквивалента нужно произвести дополнительную корректировку. Это делает строка 38. Состояние al после выполнения строки 35 следующее: ах = 0000 0001 0001 0011 В al записано 13Ь, а нужно, чтобы было ОСЬ (помните о правилах записи шестнадцатеричных чисел!). Так как ОСЬ не попадает в диапазон 0...9, то про- исходит переход на строку 38. В результате работы команды вычитания в реги- стре al получается правильное значение (al) = ОСЬ: ах = 0000 0001 0000 1100 И наконец, строка 40 производит сложение сдвинутого значения в dl с числом в al: dx = 0000 0000 0101 0000 + ах = 0000 0001 0000 1100 dx = 0000 0000 0101 1100 Таким образом, в регистре dl мы получили двоичный эквивалент двух введен- ных символов, изображающих двузначное шестнадцатеричное число: (dl) = 05Ch Строки 41-42 предназначены для завершения программы и возврата управле- ния операционной системе. Мы не случайно столь детально рассмотрели этот пример. Он отражает прак- тически все специфические особенности программирования на ассемблере. Ос- тался неясным вопрос о том, как организовать выполнение данной программы. Этим мы и займемся на следующем уроке.
63 Подведем некоторые итоги: 0 Структура программы на ассемблере отражает особенности архитектуры про- цессора. Для микропроцессоров Intel типичная программа состоит из трех сегментов: кода, стека и данных. Хотя, как вы понимаете, это не обязательно. Если ваша программа не использует стек и для ее работы не требуется опре- деления данных, то она может состоять из одного сегмента кода. 0 Программа на ассемблере работает на уровне аппаратных средств, входящих в программную модель микропроцессора, с которой мы познакомились на прошлом уроке. 0 При разработке алгоритма программы и его реализации на ассемблере про- граммист сам должен беспокоиться о размещении данных в памяти, об эф- фективном использовании ограниченного количества регистров, об органи- зации связи с операционной системой и другими программами. 0 Из-за того, что компьютер на самом нижнем уровне работает только с двоич- ной информацией, программисту необходимо ее понимать. Для этого ему нужно научиться работать с шестнадцатеричным представлением чисел. В приклад- ных программах часто используют десятичные числа. Для обмена с устрой- ствами ввода-вывода используют кодировку символов по таблице ASCII (см. приложение 3). Подробно о системах счисления мы поговорим на уроке 6.
УРОК Создание программы на ассемблере □ Разработка программ на ассемблере с использованием пакета TASM □ Назначение и структура выходных файлов, формируемых транслятором □ Ввод и отладка программы из урока 3
На этом уроке мы познакомимся со специальными программными средствами, предназначенными для преобразования исходных текстов на ассемблере к виду, приемлемому для выполнения на компьютере, и научимся использовать их. Но прежде чем обсуждать сами инструментальные средства разработки про- грамм, мне представляется необходимым уделить внимание общим методо- логическим принципам разработки программного обеспечения. Если вы — начинающий программист, то у вас наверняка очень большой интерес к прак- тической работе и, возможно, разработку программы вы производите на чисто интуитивном уровне. До определенного момента здесь нет ничего страшного; это даже естественно. Но совсем не задумываться над тем, как правильно орга- низовать разработку программы (не обязательно на ассемблере), нельзя, так как хаотичность и ставка только на интуицию в конечном итоге станут стилем программирования. А это может привести к тому, что рано или поздно за вами закрепится слава программиста, у которого программы работают «почти всегда» со всеми вытекающими отсюда последствиями для вашей карьеры. Поэтому, как мне кажется, нужно помнить одно золотое правило: надежность программы достигается, в первую очередь, благодаря ее правильному проектированию, а не бесконечному тестированию. Это правило означает, что если программа правильно разработана в отноше- нии как структур данных, так и структур управления, то это в определенной степени гарантирует правильность ее функционирования. При применении такого стиля программирования ошибки являются легко локализуемыми и устранимыми. О том, как правильно организовать разработку программ (независимо от язы- ка), написана не одна сотня книг. Большинство авторов предлагают следую- щий процесс разработки программы (мы адаптируем его, где это необходимо, к особенностям ассемблера): 1. Этап постановки и формулировки задачи: О изучение предметной области и сбор материала в проблемно-ориентиро- ванном контексте; О определение назначения программы, выработка требований к ней и пред- ставление требований, если возможно, в формализованном виде; О формулирование требований к представлению исходных данных и вы- ходных результатов;
66 Урок 4. Создание программы на ассемблере О определение структур входных и выходных данных; О формирование ограничений и допущений на исходные и выходные дан- ные. 2. Этап проектирования'. О формирование «ассемблерной» модели задачи; О выбор метода реализации задачи; О разработка алгоритма реализации задачи; О разработка структуры программы в соответствии с выбранной моделью памяти. 3. Этап кодирования'. О уточнение структуры входных и выходных данных и определение ассемб- лерного формата их представления; О программирование задачи; О комментирование текста программы и составление предварительного описания программы. 4. Этап отладки и тестирования'. О составление тестов для проверки правильности работы программы; О обнаружение, локализация и устранение ошибок в программе, выявлен- ных в тестах; О корректировка кода программы и ее описания. 5. Этап эксплуатации и сопровождения'. О настройка программы на конкретные условия использования; О обучение пользователей работе с программой; О организация сбора сведений о сбоях в работе программы, ошибках в вы- ходных данных, пожеланиях по улучшению интерфейса и удобства рабо- ты с программой; О модификация программы с целью устранения выявленных ошибок и, при необходимости, изменения ее функциональных возможностей. К порядку применения и полноте выполнения перечисленных этапов нужно подходить разумно. Многое определяется особенностями конкретной задачи, ее назначением, объемом кода и обрабатываемых данных, другими характеристи- ками задачи. Некоторые из этих этапов могут либо выполняться одновременно с другими этапами, либо вовсе отсутствовать. Главное, чтобы вы, приступая к созданию нового программного продукта, помнили о необходимости его кон- цептуальной целостности и недопустимости анархии в процессе разработки.
67 На уроке 3 мы обсуждали пример программы на ассемблере. Если посмотреть на описанный выше процесс разработки программы, то можно увидеть, что обсуж- дение на уроке 3 велось нами в полном согласии с этим процессом. Мы подроб- но обсудили проблему, структуры данных, структуру программного модуля и т. д. Наше обсуждение закончилось на этапе кодирования программы. Далее, по логике, нужно было ввести программу в компьютер, перевести в машинное пред- ставление и выполнить. Как это сделать? Дальнейшее обсуждение будет посвя- щено именно этому вопросу. Традиционно у существующих реализаций ассемблера нет интегрированной сре- ды, подобной интегрированным средам Turbo Pascal, Turbo С или Visual C++. Поэтому для выполнения всех функций по вводу кода программы, ее трансля- ции, редактированию и отладке необходимо использовать отдельные служебные программы. Большая часть их входит в состав специализированных пакетов ас- семблера. На рис. 4.1 приведена общая схема процесса разработки программы на ассембле- ре на примере программы урока 3 (см. листинг 3.1). На схеме выделено четыре шага этого процесса. На первом шаге, когда вводится код программы, можно ис- пользовать любой текстовый редактор. Основным требованием к нему является то, чтобы он не вставлял посторонних символов (спецсимволов редактирова- ния). Файл должен иметь расширение . asm. 1.ВВОД ИСХОДНОГО ТЕКСТА ПРОГРАММЫ Рис. 4.1. Процесс разработки программы на ассемблере
68 Урок 4. Создание программы на ассемблере Программы, реализующие остальные шаги схемы, входят в состав программного пакета ассемблера. Традиционно на рынке ассемблеров для микропроцессоров фирмы Intel имеется два пакета: О «Макроассемблер» MASM фирмы Microsoft. О Turbo Assembler TASM фирмы Borland. У этих пакетов много общего. Пакет макроассемблера фирмы Microsoft (MASM) получил свое название потому, что он позволял программисту зада- вать макроопределения (или макросы), представляющие собой именованные группы команд. Они обладали тем свойством, что их можно было вставлять в программу в любом месте, указав только имя группы в месте вставки. Пакет Turbo Assembler (TASM) интересен тем, что имеет два режима работы. Один из этих режимов, называемый MASM, поддерживает все основные возможнос- ти макроассемблера MASM. Другой режим, называемый IDEAL, предоставляет более удобный синтаксис написания программ, более эффективное использова- ние памяти при трансляции программы и другие новшества, приближающие компилятор ассемблера к компиляторам языков высокого уровня. В эти пакеты входят трансляторы, компоновщики, отладчики и другие утили- ты для повышения эффективности процесса разработки программ на ассембле- ре. Мы воспользуемся тем, что транслятор TASM, работая в режиме MASM, поддерживает почти все возможности транслятора MASM. Для работы с дан- ной книгой вполне достаточно иметь пакет ассемблера фирмы Borland — TASM 3.0 или выше. Обратившись к этому пакету, мы «убьем сразу двух зай- цев» — изучим основы и TASM, и MASM. В будущем это позволит вам при необходимости использовать любой из этих пакетов. Создание объектного модуля (трансляция программы) Итак, исходный текст программы на ассемблере подготовлен и записан на диск. Следующий шаг — трансляция программы. На этом шаге формируется объектный модуль, который включает в себя представление исходной програм- мы в машинных кодах и некоторую другую информацию, необходимую для отладки и компоновки его с другими модулями. Для получения объектного модуля исходный файл необходимо подвергнуть трансляции при помощи про- граммы tasm.exe из пакета TASM. Формат командной строки для запуска tasm.exe следующий: TASM [опции] имя_исходного_файла [,имя_объектного_файла] [,имя_файла_листинга] [,имя_файла_перекрестных_ссылок] На первый взгляд, все очень сложно. Не пугайтесь — если вы вдруг забыли формат командной строки и возможные значения параметров, то получить быст-
Создание объектного модуля (трансляция программы) 69 рую справку на экране монитора можно, просто запустив tasm.exe без задания каких-либо аргументов. Обратите внимание, что большинство параметров зак- лючено в квадратные скобки. Это общепринятое соглашение по обозначению параметров, которые могут отсутствовать. Таким образом, обязательным аргу- ментом командной строки является лишь имя_исходного_файла. Этот файл дол- жен находиться на диске и обязательно иметь расширение . asm. За именем ис- ходного файла через запятую могут следовать необязательные аргументы, обозначающие имена объектного файла, файла листинга и файла перекрестных ссылок. Если не задать их, то соответствующие файлы попросту не будут соз- даны. Если же их нужно создать, то необходимо учитывать следующее: О Если имена объектного файла, файла листинга и файла перекрестных ссы- лок должны совпадать с именем исходного файла (наиболее типичный слу- чай), то нужно просто поставить запятые вместо имен этих файлов: tasm.exe prg_3_1 , , , , В результате будут созданы файлы, как показано на рис. 4.1 для шага 2. О Если имена объектного файла, файла листинга и файла перекрестных ссы- лок не должны совпадать с именем исходного файла, то нужно в соответ- ствующем порядке в командной строке указать имена соответствующих файлов, к примеру: tasm.exe prg_3_1 , ,prg_list , , В результате на диске будут созданы файлы prg_3_1.obj prg_list.1st ргд_3_1.erf О Если требуется выборочное создание файлов, то вместо ненужных файлов необходимо подставить параметр nul. Например: tasm.exe prg_3_1 , ,nul, , В результате на диске будут созданы файлы prg_3_l.obj prg_3_l.crf Необязательный аргумент опции позволяет задавать режим работы транслято- ра TASM. Этих опций достаточно много. Для того чтобы получить представле- ние о них, просмотрите приложение 1. Некоторые из опций понадобятся нам в ближайшее время, а большинство из них, скорее всего, никогда не будут вами востребованы. У вас наверняка уже возникло множество вопросов. Большая часть их снимется по мере прочтения книги, работы с материалом и накопления опыта.
70 Урок 4. Создание программы на ассемблере Давайте немного поэкспериментируем с программой tasm.exe. Попутно мы выяс- ним еще несколько важных моментов. Прежде всего проведем некоторые органи- зационные мероприятия. После инсталляции пакета TASM в каталоге \TASM\BIN, где находится файл tasm.exe, вы увидите большое количество файлов. Можно запустить программу tasm.exe прямо отсюда, но тогда создан- ные ею файлы объектного кода, листинга и перекрестных ссылок тоже окажут- ся в этом каталоге. Если вы пишете одну программу, то неудобство не столь за- метно, но при работе с несколькими программами очень скоро этот каталог станет похож на свалку. Чтобы избежать подобной ситуации, рекомендуется выполнить следующие действия: О Создать в каталоге \TASM подкаталоги \TASM\WORK и \TASM\ PROGRAM. Каталог PROGRAM будем использовать для хранения отла- женных кодов программ и их исполняемых модулей (файлы с расширением .ехе). Каталог WORK будем использовать как рабочий; в нем будут нахо- диться необходимые для получения исполняемого модуля файлы из пакета транслятора TASM и файл исходного модуля, с которым мы в данный момент работаем. После того как ошибки в исходном модуле устранены, он вместе со своим исполняемым модулем переписывается в каталог PROGRAM. Из каталога WORK удаляются все ненужные файлы — и он готов для работы со следующим исходным модулем на ассемблере. Таким образом, в каталоге WORK всегда находится рабочая версия программы, а в каталоге PROGRAM — отлаженная версия. О Поместить в каталог WORK файлы tasm.exe, tlink.exe и rtm.exe. Если вы что-то забудете туда поместить, программы tasm.exe и tlink.exe выдадут вам сообщение об этом. О Поместить файл prg_3_1.asm в каталог WORK. После всех этих действий можно начинать работу. Перейдем в каталог WORK и запустим на трансляцию программу prg_3_1.asm командной строкой вида tasm.exe /zi prg_3_1 , , , , В результате на экране вы получите последовательность строк. Самая первая из них будет информировать вас о номере версии пакета TASM, который ис- пользовался для трансляции данной программы. Далее идет строка, содержа- щая имя транслируемого файла. Если ваша программа содержит ошибки, то транслятор выдаст на экран строки сообщений, начинающиеся словами «Error» и «Warning». Программа урока 3 (листинг 3.1) синтаксически правильная, но в учебных целях вы можете внести туда какую-нибудь бессмыслицу и посмот- реть, что получится. Наличие строки с «Еггог» говорит о том, что у вас в про- грамме есть недопустимые с точки зрения синтаксиса комбинации символов. Логика работы программы для транслятора не имеет никакого значения. Вы можете написать абсолютную чушь, но если она будет синтаксически правиль- на, транслятор поспешит вас обрадовать, сообщив, что все хорошо. Наличие строки «Warning» означает, что конструкция синтаксически правильна, но не
Создание объектного модуля (трансляция программы) 71 соответствует некоторым соглашениям языка и это может послужить источни- ком последующих ошибок. Для устранения ошибок нужно определить место их возникновения и проанализировать ситуацию. Место ошибки легко опреде- ляется по значению в скобках в сообщении об ошибке. Это значение является номером ошибочной строки. Запомнив его, вы переходите в файл с исходной программой и по номеру строки находите место ошибки. Этот способ локализации ошибок имеет недостатки. Во-первых, он не нагляден. Во-вторых, не всегда номера строк в сообщении соответствуют действитель- ным номерам ошибочных строк в исходном файле. Такая ситуация будет на- блюдаться, например, в том случае, если вы используете макрокоманды. При их использовании транслятор вставляет в файл дополнительные строки в соответ- ствии с описанием применяемой макрокоманды, в результате чего получается отличие в нумерации. Исходя из этих соображений, для локализации ошибок лучше использовать информацию из специального, создаваемого транслятором файла листинга. Этот файл имеет расширение .1st; его имя определяется в соответствии с теми соглашениями, которые мы разобрали выше. Ниже приве- ден полный формат листинга для программы, содержащей некоторые ошибки. Листинг — очень важный документ, и ему нужно уделить должное внимание. Листинг 4.1. Пример листинга ассемблера Turbo Assembler Version 4.1 02/03/98 21:23:43 Page 1 Prg_3_1.asm 1 ;---Prg_3_1.asm------------------ 2 Программа преобразования двузначного шестнадцатеричного числа 3 ;в символьном виде в двоичное представление. Вход: исходное шестнадцатеричное число из двух цифр, 5 ;вводится с клавиатуры. 6 ; Выход: результат преобразования должен 7 ;быть в регистре al. 8 9 0000 data segment para public ’’data" ; сегмент данных 10 0000 82 A2 A5 A4 A8 E2 A5+ message db "Введите две шестнадцатеричные цифры,$" 11 20 А4 А2 А5 20 Е8 А5+ 12 Е1 Е2 AD АО А4 Е6 А0+ 13 Е2 А5 Е0 А8 Е7 AD ЕВ+ 14 А5 20 Е6 А8 Е4 Е0 ЕВ+ 15 20 24 16 0025 data ends 17 0000 stk segment stack 18 0000 0100*(3F) db 256 dup ; сегмент стека продолжение &
72 Урок 4. Создание программы на ассемблере 19 0100 stk ends 20 0000 code segment para public "code" ; начало сегмента кода 21 0000 main proc ; начало процедуры main 22 assumecs:code,ds:data, ss:stk 23 0000 B8 0000s mov ax,data ; адрес сегмента данных в регистр ах 24 0003 8Е 08 mov ds,ах ;ах в ds 25 0005 В4 09 mov ah,9 26 0007 BA 0000 mov dx,offset messag **Error** Prg_3_1.asm(21) Undefined symbol: MESSAG 27 000А CD 21 int 21h 28 000С 33 СО xor ax, ax ; очистить регистр ax 29 000Е В4 01 mov ah, 1h ;1h в регистр ah 30 0010 CD 21 int 21h ; генерация прерывания с номером 21h 31 0012 8А DO mov dl,al содержимое регистра al в регистр dl 32 0014 80 ЕА 30 sub dl, 30h ; вычитание: (dl)=(dl)-30h 33 0017 80 FA 09 cmp dl,9h сравнить (dl) c 9h 34 001А 7Е Е4 jle MM ; перейти на метку М1 если dl<9h или dl=9h **Е Error** Prg_3_1.asm(29) Undefined symbol: MM 35 001C 80 EA 00 sub dl,777h ;вычитание: (dl)=(dl)-7h **Е Error** Prg_3_1.asm(30) Constant too large 36 001F M1: ; определение метки М1 37 001F B1 04 mov cl,4h ; пересылка 4h в регистр cl 38 0021 D2 E2 shl dl, cl сдвиг содержимого dl на 4 разряда 39 0023 CD 21 int 21h ; вызов прерывания с номером 21h 40 0025 2C 30 sub al, 30h ; вычитание: (dl)=(dl)-30h 41 0027 3C 09 cmp al,9h сравнить (al) c 9h 28 42 0029 7E 02 jle М2 ; перейти на метку М2 если al<9h или al=9h 43 002В 2C 07 sub al,7h ;вычитание: (al)=(al)-7h 44 002D М2: ;определение метки М2 45 002D 02 DO add dl.al сложение: (dl)=(dl)+(al) 46 002F B8 4C00 mov ax,4c00h ; пересылка 4c00h в регистр ах 47 0032 CD 21 int 21h ; вызов прерывания с номером 21h 48 0034 main endp ; конец процедуры main 49 0034 code ends ; конец сегмента кода 50 end main ; конец программы с точкой входа main Turbo Assembler Version 4.1 02/03/98 21:23:43 Page 2 влево Symbol Table
Создание объектного модуля (трансляция программы) 73 Symbol Name Type Value Cref (defined at #) ??DATE Text "02/03/98" ??FILENAME Text "Prg_3_1 ??TIME Text ”21:23:43" ??VERSI0N Number 040A @CPU Text 0101H @CURSEG Text CODE #9 #17 #20 ©FILENAME Text PRG_3_1 GMORDSIZEText 2 #9 #17 #20 M1 Near CODE: 001F #36 М2 Near C0DE:002D 42 #44 MAIN Near CODE: 0000 #21 50 MESSAGE Byte DATA: 0000 #10 Groups & Segments Bit Size Align Combine Class Cref (defined at #) CODE 16 0034 Para Public CODE #20 22 DATA 16 0025 Para Public DATA #9 22 23 STK16 0100 Para Stack 17 22 Turbo Assembler Version 4.1 02/03/98 21:23:43 Page 3 Error Summary “Error** Prg_3_1.asm(21) Undefined symbol: MESSAG “Error** Prg_3_1.asm(29) Undefined symbol: MM “Error** Prg_3_1.asm(30) Constant too large Файл листинга содержит, в частности, код ассемблера исходной программы. Но в листинге приводится расширенная информация об этом коде. Для каждой команды ассемблера указываются ее машинный (объектный) код и смещение в кодовом сегменте. Кроме того, в конце листинга TASM формирует таблицы, которые содержат информацию о метках и сегментах, используемых в програм- ме. Если есть ошибки или сомнительные участки кода, то TASM включает в конец листинга сообщения о них. Если сравнить их с сообщениями, выводимы- ми на экран, то видно, что они совпадают. Кроме того, что очень удобно, эти же сообщения включаются в текст листинга непосредственно после ошибочной строки. Строки в файле листинга имеют следующий формат: <глубина_вложенности> <номер_строки> <смещение> <машинный_код> <исходный_код> Здесь: О <глубина_вложенности> — уровень вложенности включаемых файлов или макрокоманд в файле;
74 Урок 4. Создание программы на ассемблере О <номер_строки> - номер строки в файле листинга. Эти номера используются для локализации ошибок и формирования таблицы перекрестных ссылок. Помните, что эти номера могут не соответствовать номерам строк в исходном файле. В добавление к вышесказанному нужно отметить, что ассемблер имеет директиву INCLUDE, которая позволяет включить в данный файл строки друго- го файла. Нумерация при этом, как и в случае макрокоманд, будет последова- тельная для строк обоих файлов. Факт вложенности кода одного файла в дру- гой фиксируется увеличением значения <глубина_ вложенности> на единицу. Это замечание касается и использования макрокоманд; О <смещение> — смещение в байтах текущей команды относительно начала сег- мента кода. Это смещение называют также счетчиком адреса. Смещение вы- числяет транслятор для адресации в сегменте кода; О <мзшинный_код> - машинное представление команды ассемблера, представ- ленной далее в этой строке полем <исходный_код>; О <исходный_код> — строка кода из исходного файла. Дальнейшие ваши действия зависят от характера ошибки. По мере накопления опыта ошибки будут происходить, скорее всего, в результате простой описки. Пока же ваши действия будут заключаться в выяснении того, насколько пра- вильно написана та или иная синтаксическая конструкция. Исправив несколь- ко первых ошибок, перетранслируйте программу и приступайте к устранению следующих ошибок. Возможно, что этого делать не придется, так как после исправления одной ошибки могут исчезнуть и последующие (так называемые наведенные ошибки). О нормальном окончании процесса трансляции можно судить по сообщению TASM, в котором отсутствуют строки с сообщениями об ошибках и предуп- реждениях. Изучая внимательно файл листинга, вы, наверное, заметили, что не все строки исходной программы имеют соответствующий <машинный_код>. Это обстоя- тельство обусловлено тем, что исходный файл на ассемблере в общем случае может содержать конструкции следующих типов: О команды ассемблера — конструкции, которым соответствуют машинные ко- манды; О директивы ассемблера — конструкции, которые не генерируют машинных команд, а являются указаниями транслятору на выполнение некоторых дей- ствий или служат для задания режима его работы; О макрокоманды — конструкции, которые, будучи представлены одной стро- кой в исходном файле программы, после обработки транслятором генериру- ют в объектном модуле последовательность команд, директив или макроко- манд ассемблера.
Создание загрузочного модуля (компоновка программы) 75 Формат листинга и его полнота не являются жестко регламентированными. Их можно изменить, задавая в исходном файле программы директивы управления листингом. Для знакомства с ними обратитесь к приложению 5. Создание загрузочного модуля (компоновка программы) После того как мы устранили ошибки и получили объектный модуль, можно приступать к следующему шагу — созданию исполняемого (загрузочного) моду- ля, или, как еще называют этот процесс, к компоновке программы. Главная цель этого шага — преобразовать код и данные в объектных файлах в их перемеща- емое выполняемое отображение. Чтобы понять, в чем здесь суть, нужно разо- браться, зачем вообще разделяют процесс создания исполняемого модуля на два шага — трансляцию и компоновку. Это сделано намеренно для того, чтобы можно было объединять вместе несколько модулей (написанных на одном или нескольких языках). Формат объектного файла позволяет, при определенных условиях, объединить несколько отдельно оттранслированных исходных моду- лей в один модуль. При этом в функции компоновщика входит разрешение внешних ссылок (ссылок на процедуры и переменные) в этих модулях. Резуль- татом работы компоновщика является создание загрузочного файла с расшире- нием .ехе. После этого операционная система может загрузить такой файл в память и выполнить его. Полный формат командной строки для запуска компоновщика достаточно сло- жен, но для данной книги вполне достаточно упрощенного формата: TLINK [опции] список_объектных_файлов [,имя_загрузочного_модуля] [,имя_фай- ла_карты] [,имя_файла_библиотеки] Здесь: О опции — необязательные параметры, управляющие работой компоновщика. Список наиболее часто используемых опций приведен в приложении 1; О список_объектных_файлов — обязательный параметр, содержащий список компонуемых файлов с расширением . obj. Файлы должны быть разделены пробелами или знаком «+», к примеру tlink /v prog + mdf + fdr О имя_загрузочного_модуля — необязательный параметр, обозначающий имя целевого исполняемого модуля. Если оно не указано, то имя загрузочного модуля будет совпадать с первым именем объектного файла из списка объектных файлов; О имя_файла_карты — необязательный параметр, наличие которого обязывает компоновщик создать специальный файл с картой загрузки. В ней перечне-
76 Урок 4. Создание программы на ассемблере ляются имена, адреса загрузки и размеры всех сегментов, входящих в про- грамму; О имя_файла_библиотеки — необязательный параметр, который представляет со- бой путь к файлу библиотеки. Этот файл с расширением .lib создается и обслуживается специальной утилитой tlib.exe из пакета TASM. Данная утилита позволяет объединить часто используемые подпрограммы в виде объектных модулей в один файл. В дальнейшем мы можем указывать в ко- мандной строке tlink.exe имена нужных для компоновки объектных моду- лей и имя_файла_библиотеки, в которой следует искать подпрограммы с этими именами. Так же как и для синтаксиса tasm.exe, совсем не обязательно запоминать подроб- но синтаксис команды tlink.exe. Для того чтобы получить список опций про- граммы tlink. ехе, достаточно просто запустить ее без указания параметров. Для выполнения нашего примера запустим программу tlink. ехе командной стро- кой вида tlink.exe /v prg_3_1.obj В результате вы получите исполняемый модуль с расширением . ехе - prg_3_1. ехе. Получив исполняемый модуль, не спешите радоваться. К сожалению, устранение синтаксических ошибок еще не гарантирует того, что программа будет хотя бы запускаться, не говоря уже о правильности работы. Поэтому обязательным эта- пом процесса разработки является отладка. На этапе отладки, используя описание алгоритма, выполняется контроль пра- вильности функционирования как отдельных участков кода, так и всей про- граммы в целом. Но даже успешное окончание отладки еще не является гаран- тией того, что программа будет работать правильно со всеми возможными исходными данными. Поэтому нужно обязательно провести тестирование про- граммы, то есть проверить ее работу на «пограничных» и заведомо некоррект- ных исходных данных. Для этого составляются тесты. Вполне возможно, что результаты тестирования вас не удовлетворят. В этом случае придется вносить поправки в код программы, то есть возвращаться к первому шагу процесса раз- работки (см. рис. 4.1). Специфика программ на ассемблере состоит в том, что они интенсивно работа- ют с аппаратными ресурсами компьютера. Это обстоятельство заставляет про- граммиста постоянно отслеживать содержимое определенных регистров и об- ластей памяти. Естественно, что человеку трудно следить за этой информацией с большой степенью детализации. Поэтому для локализации логических оши- бок в программах используют специальный тип программного обеспечения — программные отладчики. Отладчики бывают двух типов: О интегрированные — отладчик реализован в виде интегрированной среды типа среды для языков Turbo Pascal, Quick Сит. д.; О автономные — отладчик представляет собой отдельную программу.
Отладчик Turbo Debugger 77 Из-за того, что ассемблер не имеет своей интегрированной среды, для отладки написанных на нем программ используют автономные отладчики. К настояще- му времени разработано большое количество таких отладчиков. В общем слу- чае с помощью автономного отладчика можно исследовать работу любой про- граммы, для которой создан исполняемый модуль, независимо от того, на каком языке был написан его исходный текст. В этой книге будет рассмотрен отладчик Turbo Debugger (TD). Принципиально то, что основная информация о нем в той или иной степени относится и к дру- гим отладчикам. Рассмотрим основные моменты работы с отладчиком TD. Отладчик Turbo Debugger Отладчик Turbo Debugger (TD), разработанный фирмой Borland International, представляет собой оконную среду отладки программ на уровне исходного тек- ста на языках Pascal, С, ассемблер. Он позволяет решить две главные задачи: О определить место логической ошибки; О определить причину логической ошибки. Перечислим некоторые возможности TD: О выполнение трассировки программы в прямом направлении, то есть после- довательное исполнение программы, при котором за один шаг выполняется одна машинная инструкция; О выполнение трассировки программы в обратном направлении, то есть вы- полнение программы по одной команде, но в обратном направлении; О просмотр и изменение состояния аппаратных ресурсов микропроцессора во время покомандного выполнения программы. Это позволяет определить место и источник ошибки в программе. Нужно сразу оговориться, что TD не позволяет вносить исправления в исходный текст про- граммы. После определения причины ошибочной ситуации можно, при необхо- димости, не завершая работу отладчика, внести исправления прямо в машин- ный код и запустить программу на выполнение. После завершения работы отладчика эти изменения не будут сохранены, и нужно внести их повторно, но уже в исходный текст, и повторно создать загрузочный модуль. Как правильно организовать процесс получения исполняемого модуля, чтобы можно было выполнять его отладку на уровне исходного текста, мы уже рас- смотрели выше. Вспомним ключевые моменты этого процесса: О В исходной программе должна быть обязательно определена метка для пер- вой команды, с которой начнется выполнение программы. Такая метка мо- жет быть собственно меткой или, как мы видели на примере программы из
78 Урок 4. Создание программы на ассемблере листинга 3.1, именем процедуры. Имя этой метки обязательно нужно указать в конце программы в качестве операнда директивы END: END имя_метки В нашем случае эта метка является именем процедуры main. О Исходный модуль должен быть оттранслирован с опцией /zi: tasm /zi имя_исходного_модуля , , , Применение опции /zi разрешает транслятору сохранить связь символичес- ких имен в программе и их смещений в сегменте кода, что позволит отлад- чику производить отладку, используя оригинальные имена. О Редактирование модуля должно быть осуществлено с опцией /v: tlink /v имя_объектного_модуля Опция /v указывает на необходимость сохранения отладочной информации в исполняемом файле. О Запуск отладчика удобнее производить из командной строки с указанием исполняемого модуля программы, которая подлежит отладке: td имя_исполняемого_модуля Кстати, сам файл отладчика td.exe логично также поместить в наш рабочий каталог WORK. Изначально файлы отладчика находятся в каталоге BIN паке- та TASM. Если все же td. ехе и исполняемый модуль при запуске будут нахо- диться в разных каталогах, то в командной строке необходимо указать путь к этому модулю, например: td с:\1азт>огк\имя_модуля.ехе При правильном выполнении перечисленных выше действий откроется окно отладчика TD под названием Module. В этом окне вы видите исходный текст программы prg_3_1 .asm. Как он здесь оказался, ведь мы для программы td.exe указали только имя исполняемого модуля? Это как раз и есть результат действия опций /zi и /v для tasm и tlink соответственно. Их применение позволило сохранить информацию об исполь- зовавшихся в коде на ассемблере символических именах. Ради полноты экспе- римента вы можете получить исполняемый модуль без задания этих опций. Проанализируйте результат. Но вернемся к окну Module. Здесь вы видите так называемый курсор выполнения (в виде треугольника). Он указывает на первую команду, подлежащую выполнению. Этой команде предшествует имя метки (в нашем случае роль метки выполняет имя процедуры). Это так называемая точ- ка входа в программу. Если вы внимательно посмотрите конец исходного тек- ста программы, то увидите, что это же имя записано в качестве операнда в за- ключительной директиве END. Это единственный способ сообщить загрузчику ОС о том, где в исходном тексте программы расположена точка входа в нее. В более сложных программах обычно вначале могут идти описания процедур,
Отладчик Turbo Debugger 79 макрокоманд, и в этом случае без такого явного указания на первую исполняе- мую команду вам не обойтись. Основную часть экрана отладчика обычно занимают одно или несколько окон. В каждый момент времени активным может быть только одно из них. Активи- зация любого окна производится щелчком мышью в любой видимой точке окна. Управление работой отладчика ведется с помощью системы меню. Имеется два типа таких меню: О глобальное меню — находится в верхней части экрана и доступно постоянно. Вызов меню осуществляется нажатием клавиши F10, после чего следует выбрать нужный пункт этого меню; О локальное меню — для каждого окна отладчика можно вызвать его собствен- ное меню, которое учитывает особенности этого окна. Вызвать данное меню можно, щелкнув в окне правой кнопкой мыши (либо сделав активным окно и нажав клавиши Alt-F10). Теперь можно проверить правильность функционирования нашей программы. Специфика программ на ассемблере в том, что делать выводы о правильности их функционирования можно, только отслеживая работу на уровне микропро- цессора. При этом нас интересует прежде всего то, как программа использует микропроцессор и изменяет состояние его ресурсов и компьютера в целом. Запустить программу на выполнение в отладчике можно в одном из четырех режимов: О режим безусловного выполнения; О выполнение по шагам; О выполнение до текущего положения курсора; О выполнение с установкой точек прерывания. Рассмотрим эти режимы подробнее. Режим безусловного выполнения целесообразно применять, когда требуется по- смотреть на общее поведение программы. Для запуска программы в этом режи- ме необходимо нажать клавишу F9. В точках, где необходимо ввести данные, отладчик, в соответствии с логикой работы применяемого средства ввода, бу- дет осуществлять определенные действия. Аналогичные действия отладчик выполняет для вывода данных. Для просмотра или ввода этой информации можно открыть окно пользователя (Window|User screen)1, например, нажав кла- виши Alt+F5. Если работа программы удовлетворяет вас, то на этом можно и закончить. В случае, если возникают какие-то проблемы или если нужно более 1 В скобках мы будем указывать последовательность обращения к меню для выполнения необходи- мой операции. Так, в этом случае необходимо выбрать в глобальном меню Window пункт User screen.
80 Урок 4. Создание программы на ассемблере детально изучить работу программы, применяются три следующих режима от- ладки. Выполнение по шагам применяется для детального изучения работы программы. В этом режиме вы можете выполнить программу по командам. При этом можно наблюдать результат исполнения каждой команды. Для активизации этого режи- ма нужно нажать клавиши F7 (Run|Trace into) или F8 (Run|Step over). Обе эти клавиши активизируют пошаговый режим; отличие их проявляется в том слу- чае, когда в потоке команд встречаются команды перехода в процедуру или на прерывание. При использовании клавиши F7 отладчик осуществит переход на процедуру или прерывание и выполнит их по шагам. Если же используется кла- виша F8, то вызов процедуры или прерывания отрабатывается как одна обычная команда и управление возвращается следующей команде программы. Здесь нуж- но отметить, что кроме окна Module при работе в этом режиме полезно использо- вать окно CPU, вызвать которое можно через глобальное меню командой View|CPU. Это окно отражает состояние микропроцессора и состоит из 5 подокон: О окна с исходной программой в дизассемблированном виде. Это та же самая программа, что и в окне Module, но уже в машинном виде. Пошаговую отлад- ку можно производить прямо в этом окне; строка с текущей командой под- свечивается; О Registers — окна регистров микропроцессора, отражающего текущее содер- жимое регистров. Заметьте, что по умолчанию отображаются регистры только i8086. Для того чтобы воспользоваться всеми регистрами i486 или Pentium, нужно задать режим их отображения. Для этого щелкните правой кнопкой мыши в области подокна регистров для вызова локального меню. В меню выберите команду Registers 32-bit - Yes; О окна флагов, которые отражает текущее состояние флагов микропроцессора в соответствии с их мнемоническими названиями; О окна стека Stack, отражающего содержимое памяти, выделенной для стека. Адрес области стека определяется содержимым регистров SS и SP; О окна с дампом памяти Dump, отражающего содержимое области памяти по ад- ресу, который формируется из компонентов, указанных в левой части окна. В окне можно увидеть содержимое произвольной области памяти. Для этого нужно в локальном меню, вызываемом по щелчку правой кнопки мыши, выбрать нужную команду. Некоторые из этих подокон можно использовать по-другому. Все-таки удобнее работать с исходным текстом в окне Module, чем с его дизассемблированным вариантом в окне CPU. Но в то же время нужно отслеживать и состояние мик- ропроцессора, используя информацию из подокон окна CPU. Совместить воз- можности окон Module и CPU можно посредством пункта глобального меню View, в котором необходимо выбрать нужные имена подокон CPU (эти имена мы ука- зали при перечислении подокон выше).
Отладчик Turbo Debugger 81 Выполнение до текущего положения курсора позволяет выполнить программу по шагам, начиная с произвольного места программы. Этот режим целесооб- разно использовать в том случае, если вас интересует только правильность функционирования некоторого участка программы. Для активизации этого ре- жима необходимо установить курсор на нужную строку программы и нажать клавишу F4. Программа начнет выполнение и остановится на отмеченной ко- манде, не выполнив ее. Далее вы можете использовать, при необходимости, по- шаговый режим. Выполнение с установкой точек прерывания позволяет выполнить программу с остановкой ее в строго определенных точках прерывания (breakpoints). Перед выполнением программы необходимо установить эти точки в программе, для чего следует перейти в нужную строку и нажать клавишу F2. Выбранные стро- ки подсвечиваются другим цветом. Установленные ранее точки прерывания можно убрать — для этого нужно повторно выбрать нужные строки и нажать клавишу F2. После установки точек прерывания программа запускается на вы- полнение клавишей F9. На первой точке прерывания программа остановится. Теперь можно посмотреть состояние микропроцессора и памяти, а затем про- должить выполнение программы. Сделать это можно или в пошаговом режиме, или выполнить программу до следующей точки прерывания. Прервать выполнение программы в любом из этих режимов можно, нажав Ctrl+F2. Мы описали все основные моменты, связанные с отладкой программы, начиная с ее загрузки в отладчик и заканчивая способами исследования ее работы. Те- перь вам нужно немного попрактиковаться, запуская программу prg_3_1.exe в различных режимах отладчика или используя комбинацию этих режимов. Ког- да вы почувствуете, что освоились, переходите к следующему уроку. Подведем некоторые итоги: 0 Наиболее развитым и удобным для использования ассемблерным пакетом является транслятор фирмы Borland — TASM. Большое количество опций и директив управления листингом программы на ассемблере позволяют задать достаточно гибкую обработку исходного кода транслируемой программы. 0 Процесс создания программ на ассемблере напоминает процесс создания программ на большинстве языков высокого уровня. Последние версии транс- лятора фирмы Borland, начиная с TASM 3.0, поддерживают все основные существующие на настоящий момент времени технологии программирова- ния, включая объектно-ориентированную. 0 Специфика разработки программы на ассемблере в том, что программист должен уделять внимание не только и не столько особенностям предметной области, сколько тому, как при моделировании наиболее эффективно и кор- ректно использовать ресурсы микропроцессора.
82 Урок 4. Создание программы на ассемблере 0 В результате работы транслятора создается листинг программы, содержащий разнообразную информацию о программе: объектный код, сообщения о син- таксических ошибках, таблицу символов и т. д. Имея небольшой опыт, из листинга можно извлечь массу полезной информации. 0 Целями трансляции программы являются обнаружение синтаксических оши- бок и генерация объектного модуля. После того как получен корректный объектный модуль, программу необходимо скомпоновать. Для этого приме- няется утилита TLINK, входящая в состав пакета TASM. Одно из основных ее назначений — разрешение внешних ссылок. Если наша целевая програм- ма состоит из нескольких отдельно оттранслированных модулей и в них есть взаимные ссылки на переменные или модули, то TLINK разрешает их, формируя тем самым правильные перемещаемые адреса. 0 Результатом работы утилиты TLINK является исполняемый (загрузочный) модуль, имеющий расширение . ехе. Его уже можно запускать на выполне- ние с надеждой, что он правильно выполнит задуманные вами действия. Так, возможно, и будет, когда у вас появится достаточно опыта и знаний. Пока же чаще всего вы будете сталкиваться с ситуацией, в которой машина погружается в глубокую задумчивость, выдает на экран какую-то бессмыс- лицу или вовсе перезагружается. Все это говорит о том, что вам предстоит долгая работа по поиску самых вредных ошибок — логических. К счастью, в вашем распоряжении есть специальный вид программного обеспечения — отладчики. Постарайтесь уделить им достойное внимание, и вы увидите, что большинство ваших программных проблем будет снято.
УРОК Структура программы на ассемблере □ Структура программы на ассемблере □ Стандартные директивы сегментации □ Упрощенные директивы сегментации □ Представление простых типов данных
На предыдущих уроках мы подробно разобрались с тем, что представляет со- бой микропроцессор изнутри, каковы принципы его работы и какая часть его архитектуры оставлена программируемой. Мы даже разработали одну полез- ную программу. Настало время подробно разобраться с самим языком ассемб- лера и с правилами оформления программ на нем. Коротко вспомним информацию об ассемблере, которой мы обладаем на насто- ящий момент: О ассемблер является символическим аналогом машинного языка. По этой причине программа, написанная на ассемблере, должна отражать все осо- бенности архитектуры микропроцессора: организацию памяти, способы ад- ресации операндов, правила использования регистров и т. д. Из-за необхо- димости учета подобных особенностей ассемблер уникален для каждого типа микропроцессоров; О программа на ассемблере представляет собой совокупность блоков памяти, называемых сегментами памяти. Программа может состоять из одного или нескольких таких блоков-сегментов. Каждый сегмент содержит совокуп- ность предложений языка, каждое из которых занимает отдельную строку кода программы; О предложения ассемблера бывают четырех типов: • команды или инструкции, представляющие собой символические анало- ги машинных команд. В процессе трансляции инструкции ассемблера преобразуются в соответствующие команды системы команд микропро- цессора; • макрокоманды — оформляемые определенным образом предложения текста программы, замещаемые во время трансляции другими предложе- ниями; • директивы, являющиеся указанием транслятору ассемблера на выполне- ние некоторых действий. У директив нет аналогов в машинном представ- лении; • строки комментариев, содержащие любые символы, в том числе и буквы русского алфавита. Комментарии игнорируются транслятором.
Синтаксис ассемблера 85 Синтаксис ассемблера Предложения, составляющие программу, могут представлять собой синтаксичес- кую конструкцию, соответствующую команде, макрокоманде, директиве или комментарию. Для того чтобы транслятор ассемблера мог распознать их, они должны формироваться по определенным синтаксическим правилам. Нам, ко- нечно, было бы неплохо их знать. Лучше всего использовать формальное описа- ние синтаксиса языка наподобие правил грамматики. Наиболее распространен- ные способы подобного описания языка программирования — синтаксические диаграммы и расширенные формы Бэкуса-Наура. Для практического использо- вания более удобны синтаксические диаграммы. К примеру, синтаксис предло- жений ассемблера можно описать с помощью синтаксических диаграмм, пока- занных на следующих рисунках. Рис. 5.1. Формат предложения ассемблера 6 - I—| Комментарий |— Рис. 5.2. Формат директив Рис. 5.3. Формат команд и макрокоманд На этих рисунках: О имя метки — идентификатор, значением которого является адрес первого байта того предложения исходного текста программы, которое он обозначает;
86 Урок 5. Структура программы на ассемблере О имя — идентификатор, отличающий данную директиву от других одно- именных директив. В результате обработки ассемблером определенной ди- рективы этому имени могут быть присвоены определенные характеристики; О код операции (КОП) и директива — это мнемонические обозначения соответ- ствующей машинной команды, макрокоманды или директивы транслятора; О операнды — части команды, макрокоманды или директивы ассемблера, обо- значающие объекты, над которыми производятся действия. Операнды ас- семблера описываются выражениями с числовыми и текстовыми константа- ми, метками и идентификаторами переменных с использованием знаков операций и некоторых зарезервированных слов. Как использовать синтаксические диаграммы? Очень просто: для этого нужно всего лишь найти и затем пройти путь от входа диаграммы (слева) к ее выходу (направо). Если такой путь существует, то предложение или конструкция син- таксически правильны. Если такого пути нет, значит эту конструкцию компи- лятор не примет. При работе с синтаксическими диаграммами обращайте вни- мание на направление обхода, указываемое стрелками, так как среди путей могут быть и такие, по которым можно идти справа налево. По сути, синтаксические диаграммы отражают логику работы транслятора при разборе входных предло- жений программы. В нашей книге синтаксические диаграммы будут помогать в сложных случаях описывать синтаксис конструкций ассемблера. Допустимыми символами при написании текста программ являются: 1. Все латинские буквы A-Z, a-z. При этом заглавные и прописные буквы счита- ются эквивалентными. 2. Цифры от 0 до 9. 3. Знаки ?, @, $, _, &. 4. Разделители: ,.[]()<>{} + /*%!’’” ?\ = #Л Предложения ассемблера формируются из лексем, представляющих собой син- таксически неразделимые последовательности допустимых символов языка, имеющие смысл для транслятора. Лексемами являются: О идентификаторы — последовательности допустимых символов, использую- щиеся для обозначения таких объектов программы, как коды операций, имена переменных и названия меток. Правило записи идентификаторов заключается в следующем. Идентификатор может состоять из одного или нескольких символов. В качестве символов можно использовать буквы ла- тинского алфавита, цифры и некоторые специальные знаки — _, ?, $, Идентификатор не может начинаться символом цифры. Длина идентифика- тора может быть до 255 символов, хотя транслятор воспринимает лишь пер- вые 32, а остальные игнорирует. Регулировать длину возможных идентифи- каторов можно с использованием опции командной строки mv (см. прило- жение 1). Кроме этого существует возможность указать транслятору на то, чтобы он различал прописные и строчные буквы либо игнорировал их раз-
Синтаксис ассемблера 87 личие (что и делается по умолчанию). Для этого применяются опции коман- дной строки /mu, /ml, /тх (см. приложение 1); О цепочки символов — последовательности символов, заключенные в одинарные или двойные кавычки; О целые числа в одной из следующих систем счисления: двоичной, десятичной, шестнадцатеричной. Отождествление чисел при записи их в программах на ассемблере производится по определенным правилам. Для шестнадцатерич- ных чисел эти правила были рассмотрены на уроке 3. Десятичные числа не требуют для своего отождествления указания каких-либо дополнительных символов. Для отождествления в исходном тексте программы двоичных чи- сел необходимо после записи нулей и единиц, входящих в их состав, поста- вить латинское «Ь». К примеру, 10010101b. Таким образом, мы разобрались с тем, как конструируются предложения про- граммы ассемблера. Но это — лишь самый поверхностный взгляд. Практичес- ки каждое предложение содержит описание объекта, над которым или при по- мощи которого выполняется некоторое действие. Эти объекты называются операндами. Их можно определить так: операнды — это объекты (некоторые значения, регистры или ячейки памяти), на которые действуют инструкции или директивы, либо это объекты, которые определяют или уточняют действие инструкций или директив. Операнды могут комбинироваться с арифметическими, логическими, побито- выми и атрибутивными операторами для расчета некоторого значения или оп- ределения ячейки памяти, на которую будет воздействовать данная команда или директива. Рассмотрим классификацию операндов, поддерживаемых транслятором ассем- блера. О Постоянные или непосредственные операнды — число, строка, имя или вы- ражение, имеющие некоторое фиксированное значение. Имя не должно быть перемещаемым, то есть зависеть от адреса загрузки программы в па- мять. К примеру, оно может быть определено операторами eq и или =: equ num-2 mov 5 num imd = al,num •.эквивалентно mov al,5 ;5 здесь непосредственный операнд ; imd=3 - непосредственный операнд ;5 - непосредственный операнд [si],imd al,5 add mov Это первый рассматриваемый нами фрагмент программы, содержащий ма- шинные команды (не считая листинга 3.1, который преследовал другие цели). В свое время все они будут подробно описаны, пока же мы по мере необходимости будем пояснять их в комментариях. Для более подробной
88 Урок 5. Структура программы на ассемблере информации вы всегда можете обратиться к приложению 2, где приведены справочные сведения по всем машинным командам. В данном фрагменте определяются две константы, которые затем используются в качестве непос- редственных операндов в командах пересылки mov и сложения add. О Адресные операнды — задают физическое расположение операнда в памяти с помощью указания двух составляющих адреса: сегмента и смещения (рис. 5.4). Рис. 5.4. Синтаксис описания адресных операндов К примеру: mov ax.OOOOh mov ds,ах mov ax,ds:0000h ;записать слово в ах из области памяти по ;физическому адресу 0000:0000 Здесь третья команда mov имеет адресный операнд. О Перемещаемые операнды — любые символьные имена, представляющие не- которые адреса памяти. Эти адреса могут обозначать местоположение в па- мяти некоторой инструкции (если операнд — метка) или данных (если опе- ранд — имя области памяти в сегменте данных). Перемещаемые операнды отличаются от адресных тем, что они не привязаны к конкретному адресу физической памяти. Сегментная составляющая адреса перемещаемого опе- ранда неизвестна и будет определена после загрузки программы в память для выполнения. К примеру: data segment mas_w dw 25 dup (0) code segment lea si,mas_w ;mas_w - перемещаемый операнд
Синтаксис ассемблера 89 В этом фрагменте mas_w — символьное имя, значением которого является начальный адрес области памяти размером 25 слов. Полный физический адрес этой области памяти будет известен только после загрузки программы в память для выполнения. О Счетчик адреса — специфический вид операнда. Он обозначается знаком $. Специфика этого операнда в том, что когда транслятор ассемблера встреча- ет в исходной программе этот символ, то он подставляет вместо него теку- щее значение счетчика адреса. Значение счетчика адреса, или как его иногда называют счетчика размещения, представляет собой смещение текущей ма- шинной команды относительно начала сегмента кода. Мы упоминали о счетчике адреса на уроке 4, когда обсуждали понятие листинга на примере программы урока 3 (prg_3_1.asm). В формате листинга счетчику адреса со- ответствует вторая или третья колонка (в зависимости от того, присутству- ет или нет в листинге колонка с уровнем вложенности). Как видите из этого примера листинга, при обработке транслятором очередной команды ассемб- лера счетчик адреса увеличивается на длину сформированной машинной команды. Важно правильно понимать этот момент. К примеру, обработка директив ассемблера не влечет за собой изменения счетчика. Подумайте, почему? Директивы, в отличие от команд ассемблера, — это лишь указания транслятору на выполнение определенных действий по формированию ма- шинного представления программы, и для них транслятором не генерирует- ся никаких конструкций в памяти. В качестве примера использования в ко- манде значения счетчика адреса можно привести следующий: jmp $+3 безусловный переход на команду mov old ;длина команды old составляет 1 байт mov al,1 При использовании подобного выражения для перехода не забывайте о дли- не самой команды, в которой это выражение используется, так как значение счетчика адреса соответствует смещению в сегменте команд данной, а не следующей за ней команды. В нашем примере команда jmp занимает 2 бай- та. Но будьте осторожны, длина команды зависит от того, какие в ней ис- пользуются операнды. Команда с регистровыми операндами будет короче команды, один из операндов которой расположен в памяти. В большинстве случаев эту информацию можно получить, зная формат машинной команды (урок 6) и анализируя колонку листинга с объектным кодом команды. О Регистровый операнд — это просто имя регистра. В программе на ассембле- ре можно использовать имена всех регистров общего назначения и боль- шинства системных регистров. mov al,4 ;константу 4 заносим в регистр al mov dl,pass+4 ;байт по адресу pass+4 в регистр dl add al.dl ;команда с регистровыми операндами
90 Урок 5. Структура программы на ассемблере О Базовый и индексный операнды. Этот тип операндов используется для реали- зации косвенной базовой, косвенной индексной адресации или их комбина- ций и расширений. Адресация будет рассмотрена на следующем уроке. О Структурные операнды используются для доступа к конкретному элементу сложного типа данных, называемого структурой. Мы подробно разберемся со структурами на уроке 12. О Записи (аналогично структурному типу) используются для доступа к бито- вому полю некоторой записи (см. урок 12). Операнды являются элементарными компонентами, из которых формируется часть машинной команды, обозначающая объекты, над которыми выполняется операция. В более общем случае операнды могут входить как составные части в более сложные образования, называемые выражениями. Выражения представ- ляют собой комбинации операндов и операторов, рассматриваемые как единое целое. Результатом вычисления выражения может быть адрес некоторой ячей- ки памяти или некоторое константное (абсолютное) значение. Возможные типы операндов мы уже рассмотрели. Перечислим теперь возмож- ные типы операторов ассемблера и синтаксические правила формирования выражений ассемблера. После этого можно будет считать, что мы полностью разобрались с правилами записи команд ассемблера. В табл. 5.2 приведены поддерживаемые языком ассемблера операторы и перечислены их приоритеты. Дадим краткую характеристику операторов. О Арифметические операторы. К ним относятся унарные операторы «+» и «-», бинарные «+» и «-», операторы умножения «*», целочисленного деления «/», получения остатка от деления «mod» (рис. 5.5). Эти операторы расположе- ны на уровнях приоритета 6, 7, 8 в табл. 5.1. Например: tab_size equ 50 ;размер массива в байтах size_el equ 2 ;размер элементов ;вычисляется число элементов массива и заносится в регистр сх mov cx,tab_size / size_el ; оператор "I" Рис. 5.5. Синтаксис арифметических операторов
Синтаксис ассемблера 91 О Операторы сдвига выполняют сдвиг выражения на указанное количество раз- рядов (рис. 5.6). Например: mask.bequ 10111011 mov al.mask-b shr 3 ;al=00010111 Рис. 5.6. Синтаксис операторов сдвига О Операторы сравнения (возвращают значение «истина» или «ложь») предназ- начены для формирования логических выражений (см. рис. 5.7 и табл. 5.1). Логическое значение «истина» соответствует цифровой единице, а «ложь» — нулю. Например: tab_size equ 30 ; размер таблицы mov al.tab-Size ge 50 ; загрузка размера таблицы в al cmp al,0 ;если tab_size < 50, то je ml ; переход на ml ml: ... В этом примере, если значение tab_size больше или равно 50, то результат в al равен Offh, а если tab_size меньше 50, то al равно 00h. Команда стр сравни- вает значение al с нулем и устанавливает соответствующие флаги в flags/ eflags. Команда je на основе анализа этих флагов передает или не передает управление на метку ml Рис. 5.7. Синтаксис операторов сравнения
92 Урок 5. Структура программы на ассемблере Таблица 5.1. Операторы сравнения Оператор Значение (см. рис. 5.7) eq пе It 1е ИСТИНА, если выражение_1 равно выражение_2 ИСТИНА, если выражение_1 не равно выражение^ ИСТИНА, если выражение_1 меньше выражение^ ИСТИНА, если выражение_1 меньше или равно выражение_2 gt ge ИСТИНА, если выражение_1 больше выражение_2 ИСТИНА, если выражение_1 больше или равно выражение_2 О Логические операторы выполняют над выражениями побитовые операции (рис. 5.8). Выражения должны быть абсолютными, то есть такими, числен- ное значение которых может быть вычислено транслятором. Например: flags equ 10010011 mov al.flags xor Olh ;al=10010010 ; пересылка в al поля flags с ;инвертированным правым битом | Выражение^ |. -| Вы ражен ие 2р Рис. 5.8. Синтаксис логических операторов Более подробно о правилах, в соответствии с которыми вычисляется ре- зультат логических операций, мы поговорим на уроке 9. О Индексный оператор [ ]. Не удивляйтесь, но скобки тоже являются опера- тором, и транслятор их наличие воспринимает как указание сложить значе- ние выражение^ за этими скобками с выражение^?, заключенным в скобки (рис. 5.9). Например: mov ax,mas[si] ;пересылка слова по адресу mas+(si) в регистр ах -| Выражение^! |- (Т)-| Выражение 2]—(Т)— Рис. 5.9. Синтаксис индексного оператора Заметим, что в литературе по ассемблеру принято следующее обозначение: когда в тексте речь идет о содержимом регистра, то его название берут в
Синтаксис ассемблера 93 круглые скобки. Мы также будем придерживаться этого обозначения. К при- меру, в нашем случае запись в комментариях последнего фрагмента програм- мы mas + (si) означает вычисление выражения: значение смещения символи- ческого имени mas плюс содержимое регистра si. О Оператор переопределения типа pt г применяется для переопределения или уточ- нения типа метки или переменной, определяемых выражением (рис. 5.10). Тип может принимать одно из следующих значений: byte, word, dword, qword, tbyte, near, far. Что означают эти значения, вы узнаете далее на этом уроке. Например: d_wrd dd О mov al, byte ptr d_wrd+1 ;пересылка второго байта из двойного ;слова Поясним этот фрагмент программы. Переменная d_wrd имеет тип двойного слова. Что делать, если возникнет необходимость обращения не ко всей пе- ременной, а только к одному из входящих в нее байтов (например, ко второ- му)? Если попытаться сделать это командой mov al,d_wrd+1, то транслятор выдаст сообщение о несовпадении типов операндов. Оператор ptr позволяет непосредственно в команде переопределить тип и выполнить команду. Рис. 5.10. Синтаксис оператора переопределения типа О Оператор переопределения сегмента : (двоеточие) заставляет вычислять физический адрес относительно конкретно задаваемой сегментной состав- ляющей: «имя сегментного регистра», «имя сегмента» из соответствующей директивы SEGMENT или «имя группы» (рис. 5.11). Этот момент очень важен, поэтому поясню его подробнее. При обсуждении сег- ментации мы говорили о том, что микропроцессор на аппаратном уровне под- держивает три типа сегментов — кода, стека и данных. В чем заключается та- кая аппаратная поддержка? К примеру, для выборки на выполнение очередной команды микропроцессор должен обязательно посмотреть содержимое сегмент- ного регистра cs и только его. А в этом регистре, как мы знаем, содержится (пока еще не сдвинутый) физический адрес начала сегмента команд. Для полу- чения адреса конкретной команды микропроцессору остается умножить содер- жимое cs на 16 (что означает сдвиг на четыре разряда) и сложить полученное 20-битное значение с 16-битным содержимым регистра ip. Примерно то же са- мое происходит и тогда, когда микропроцессор обрабатывает операнды в ма- шинной команде. Если он видит, что операнд — это адрес (эффективный адрес, который является только частью физического адреса), то он знает, в каком сег-
94 Урок 5. Структура программы на ассемблере менте его искать — по умолчанию это сегмент, адрес начала которого записан в сегментном регистре ds. А что же с сегментом стека? Посмотрите урок 2 там, где мы описывали назначе- ние регистров общего назначения. В контексте нашего рассмотрения нас интере- суют регистры sp и Ьр. Если микропроцессор видит в качестве операнда (или его части, если операнд — выражение) один из этих регистров, то по умолчанию он формирует физический адрес операнда, используя в качестве его сегментной со- ставляющей содержимое регистра ss. Что подразумевает термин «по умолча- нию»? Вспомните «рефлексы», о которых мы говорили на уроке 1. Это набор микропрограмм в блоке микропрограммного управления, каждая из которых выполняет одну из команд в системе машинных команд микропроцессора. Каж- дая микропрограмма работает по своему алгоритму. Изменить его, конечно же, нельзя, но можно чуть-чуть подкорректировать. Делается это с помощью необя- зательного поля префикса машинной команды (см. формат команд в уроке 2). Если мы согласны с тем, как работает команда, то это поле отсутствует. Если же мы хотим внести поправку (если, конечно, она допустима для конкретной коман- ды) в алгоритм работы команды, то необходимо сформировать соответствующий префикс. Префикс представляет собой однобайтовую величину, численное значе- ние которой определяет ее назначение. Микропроцессор распознает по указанно- му значению, что этот байт является префиксом, и дальнейшая работа микро- программы выполняется с учетом поступившего указания на корректировку ее работы. По ходу обсуждения материала книги мы познакомимся с большинством возможных префиксов. Сейчас нас интересует один из них — префикс замены (переопределения) сегмента. Его назначение состоит в том, чтобы указать мик- ропроцессору (а по сути микропрограмме) на то, что мы не хотим использовать сегмент по умолчанию. Возможности для подобного переопределения, конечно, ограничены. Сегмент команд переопределить нельзя, адрес очередной исполняе- мой команды однозначно определяется парой cs: ip. А вот сегменты стека и дан- ных — можно. Для этого и предназначен оператор «:». Транслятор ассемблера, обрабатывая этот оператор, формирует соответствующий однобайтовый префикс замены сегмента. Например, . code jmp met1 ; обход обязателен, иначе поле ind будет трактоваться ;как очередная команда ind db 5 ;описание поля данных в сегменте команд met1: mov al.cs:ind ;переопределение сегмента позволяет работать с ;данными, определенными внутри сегмента кода Продолжим перечисление операторов. О Оператор именования типа структуры . (точка) также заставляет трансля- тор производить определенные вычисления, если он встречается в выраже-
Синтаксис ассемблера 95 нии. Подробно мы обсудим этот оператор, когда введем понятие сложных типов данных на уроке 12. О Оператор получения сегментной составляющей адреса выражения seg возвра- щает физический адрес сегмента для выражения (рис. 5.12), в качестве кото- рого могут выступать метка, переменная, имя сегмента, имя группы или неко- торое символическое имя. Имя сегмента Имя группы Рис. 5.11. Синтаксис оператора переопределения сегмента —| SEG |—[Выражение-]— Рис. 5.12. Синтаксис оператора получения сегментной составляющей О Оператор получения смещения выражения offset позволяет получить значе- ние смещения выражения (рис. 5.13) в байтах относительно начала того сегмента, в котором выражение определено. —| offset}—[Выражение]— Рис. 5.13. Синтаксис оператора получения смещения Например: .data pole dw 5 .code mov ax,seg pole mov es.ax mov dx,offset pole ;теперь в nape es:dx полный адрес pole
96 Урок 5. Структура программы на ассемблере Как и в языках высокого уровня, выполнение операторов ассемблера при вычисле- нии выражений осуществляется в соответствии с их приоритетами (табл. 5.2). Операции с одинаковыми приоритетами выполняются последовательно слева направо. Изменение порядка выполнения возможно путем расстановки круг- лых скобок, которые имеют наивысший приоритет. Таблица 5.2. Операторы и их приоритет Оператор Приоритет length, size, width, mask, (, ), [, ], <, > 1 2 3 ptr, offset, seg, type, this high, low +, - (унарные) *, /, mod, shl, shr +, -, (бинарные) eq, ne, It, le, gt, ge not and 4 5 6 7 8 9 10 11 or, xor short, type 12 13 При необходимости использовать в операциях сложные выражения, синтакси- ческие правила их записи вы можете уточнить в приложении 4. Директивы сегментации В ходе предыдущего обсуждения мы выяснили все основные правила записи команд и операндов в программе на ассемблере. Открытым остался вопрос о том, как правильно оформить последовательность команд, чтобы транслятор мог их обработать, а микропроцессор — выполнить. На предыдущих уроках мы уже касались понятия сегмента. При рассмотрении архитектуры микропро- цессора мы узнали, что он имеет шесть сегментных регистров, посредством ко- торых может одновременно работать: О одним сегментом кода; О одним сегментом стека; О одним сегментом данных; О тремя дополнительными сегментами данных.
Директивы сегментации 97 Еще раз вспомним, что физически сегмент представляет собой область памяти, занятую командами и (или) данными, адреса которых вычисляются относи- тельно значения в соответствующем сегментном регистре. Синтаксическое описание сегмента на ассемблере представляет собой конст- рукцию, изображенную на рис. 5.14. Рис. 5.14. Синтаксис описания сегмента Важно отметить, что функциональное назначение сегмента несколько шире, чем простое разбиение программы на блоки кода, данных и стека. Сегментация является частью более общего механизма, связанного с концепцией модульного программирования. Она предполагает унификацию оформления объектных модулей, создаваемых компилятором, в том числе с разных языков программи- рования. Это позволяет объединять программы, написанные на разных языках. Именно для реализации различных вариантов такого объединения и предназ- начены операнды в директиве SEGMENT. Рассмотрим их подробнее: О Атрибут выравнивания сегмента (тип выравнивания) сообщает компонов- щику о том, что нужно обеспечить размещение начала сегмента на заданной границе. Это важно, поскольку при правильном выравнивании доступ к данным в процессорах i80x86 выполняется быстрее. Допустимые значения этого атрибута следующие: • BYTE — выравнивание не выполняется. Сегмент может начинаться с лю- бого адреса памяти; • WORD — сегмент начинается по адресу, кратному двум, то есть последний (младший) значащий бит физического адреса равен 0 (выравнивание на границу слова);
98 Урок 5. Структура программы на ассемблере • DWORD — сегмент начинается по адресу, кратному четырем, то есть два пос- ледних (младших) значащих бита равны 0 (выравнивание на границу двойного слова); • PARA — сегмент начинается по адресу, кратному 16, то есть последняя шестнадцатеричная цифра адреса должна быть Oh (выравнивание на гра- ницу параграфа); • PAGE — сегмент начинается по адресу, кратному 256, то есть две послед- ние шестнадцатеричные цифры должны быть 00h (выравнивание на гра- ницу страницы размером 256 байт); • MEM PAGE — сегмент начинается по адресу, кратному 4 Кбайт, то есть три последние шестнадцатеричные цифры должны быть OOOh (адрес следую- щей страницы памяти размером 4 Кбайт); По умолчанию тип выравнивания имеет значение PARA. О Атрибут комбинирования сегментов (комбинаторный тип) сообщает компо- новщику, как нужно комбинировать сегменты различных модулей, имею- щие одно и то же имя. По умолчанию атрибут комбинирования принимает значение PRIVATE. Значениями атрибута комбинирования сегмента могут быть: • PRIVATE — сегмент не будет объединяться с другими сегментами с тем же именем вне данного модуля; • PUBLIC — заставляет компоновщик соединить все сегменты с одинаковым именем. Новый объединенный сегмент будет целым и непрерывным. Все адреса (смещения) объектов, а это могут быть, в зависимости от типа сегмента, команды и данные будут вычисляться относительно начала этого нового сегмента; • COMMON — располагает все сегменты с одним и тем же именем по одному адресу. Все сегменты с данным именем будут перекрываться и совместно использовать память. Размер полученного в результате сегмента будет равен размеру самого большого сегмента; • АТ хххх - располагает сегмент по абсолютному адресу параграфа (пара- граф — объем памяти, кратный 16; потому последняя шестнадцатерич- ная цифра адреса параграфа равна 0). Абсолютный адрес параграфа за- дается выражением ххх. Компоновщик располагает сегмент по заданно- му адресу памяти (это можно использовать, например, для доступа к видеопамяти или области ПЗУ), учитывая атрибут комбинирования. Физически это означает, что сегмент при загрузке в память будет рас- положен, начиная с этого абсолютного адреса параграфа, но для досту- па к нему в соответствующий сегментный регистр должно быть загру- жено заданное в атрибуте значение. Все метки'и адреса в определенном таким образом сегменте отсчитываются относительно заданного абсо- лютного адреса;
Директивы сегментации 99 • 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. Если в качестве
100 Урок 5. Структура программы на ассемблере операнда используется только ключевое слово nothing, то предшествующие на- значения сегментных регистров аннулируются, причем сразу для всех шести сег- ментных регистров. Но ключевое слово nothing можно использовать вместо аргу- мента имя сегмента; в этом случае будет выборочно разрываться связь между сегментом с именем имя сегмента и соответствующим сегментным регистром (см. рис 5.15). Рис. 5.15. Директива ASSUME На уроке 3 мы рассматривали пример программы с директивами сегментации. Эти директивы изначально использовались для оформления программы в трансляторах MASM и TASM. Поэтому их называют стандартными директи- вами сегментации. Для простых программ, содержащих по одному сегменту для кода, данных и стека, хотелось бы упростить ее описание. Для этого в трансляторы MASM и TASM ввели возможность использования упрощенных директив сегментации. Но здесь возникла проблема, связанная с тем, что необходимо было как-то компенсировать невозможность напрямую управлять размещением и комбини- рованием сегментов. Для этого совместно с упрощенными директивами сегмен- тации стали использовать директиву указания модели памяти MODEL, которая частично стала управлять размещением сегментов и выполнять функции ди- рективы ASSUME (поэтому при использовании упрощенных директив сегмента- ции директиву ASSUME можно не использовать). Эта директива связывает сег- менты, которые, в случае использования упрощенных директив сегментации,
Директивы сегментации 101 имеют предопределенные имена с сегментными регистрами (хотя явно инициа- лизировать ds все равно придется). Так, если мы используем упрощенные директивы сегментации для оформления программы из листинга 3.1, то она будет выглядеть следующим образом. Листинг 5.1. Упрощенные директивы сегментации ;----Prg_3_1.asm-------------- masm ;режим работы TASM: ideal или masm model small ;модель памяти .data ;сегмент данных message db "Введите две шестнадцатеричные цифры, $" .stack ;сегмент стека db 256 dup ("?") ;сегмент стека .code ;сегмент кода main proc ;начало процедуры main mov ax,©data ; заносим адрес сегмента данных в регистр ах mov ds,ах ; ах в ds ; далее текст программы идентичен тексту сегмента кода в листинге 3.1 mov ax,4c00h ; пересылка 4c00h в регистр ах int 21h ; вызов прерывания с номером 21h main endp ; конец процедуры main endmain ; конец программы с точкой входа main Синтаксис директивы MODEL показан на рис. 5.16: Рис. 5.16. Синтаксис директивы MODEL Обязательным параметром директивы MODEL является модель памяти. Этот пара- метр определяет модель сегментации памяти для программного модуля. Предпо- лагается, что программный модуль может иметь только определенные типы сег- ментов, которые определяются так называемыми упрощенными директивами описания сегментов. Эти директивы приведены в табл. 5.3. Наличие в некоторых директивах параметра [имя] говорит о том, что возможно определение нескольких сегментов этого типа. С другой стороны, наличие не- скольких видов сегментов данных обусловлено требованием обеспечить сов- местимость с некоторыми компиляторами языков высокого уровня, которые создают разные сегменты данных для инициализированных и неинициализи- рованных данных, а также констант.
102 Урок 5. Структура программы на ассемблере Таблица 53. Упрощенные директивы определения сегмента Формат директивы (режим MASM) Формат директивы (режим IDEAL) Назиачение .CODE [имя] CODESEG [имя] Начало или продолжение сегмента кода .DATA DATASEG Начало или продолжение сегмента иници- ализированных данных. Также используется для определения данных типа near1 .CONST CONST Начало или продолжение сегмента постоян- ных данных (констант) модуля .DATA? UDATASEG Начало или продолжение сегмента неини- циализированных данных. Также исполь- зуется для определения данных типа near .STACK [размер] STACK [размер] Начало или продолжение сегмента стека мо- дуля. Параметр [размер] задает размер стека .FARDATA [имя] FARDATA [имя] Начало или продолжение сегмента иници- ализированных данных типа far .FARDATA? [имя] UFARDATA [имя] Начало или продолжение сегмента неини- циализированных данных типа far При использовании директивы MODEL транслятор делает доступными несколько идентификаторов, к которым можно обращаться во время работы программы, с тем, чтобы получить информацию о тех или иных характеристиках данной модели памяти (см. табл. 5.5 ниже). Перечислим эти идентификаторы и их значения (табл. 5.4). Таблица 5.4. Идентификаторы, создаваемые директивой MODEL Имя идентификатора Значение переменной ©code Физический адрес сегмента кода ©data Физический адрес сегмента данных типа near ©fardata Физический адрес сегмента данных типа far ©fardata? Физический адрес сегмента неинициализированных данных типа far @curseg Физический адрес сегмента неинициализированных данных типа far ©stack Физический адрес сегмента стека 1 На этом занятии читатель довольно часто встречает служебные слова near и far. Подробно они бу- дут описаны при рассмотрении команд перехода и процедур. Пока же читатель должен представ- лять себе следующее. Слова near и far определяют «удаленность» объекта от места использования. В качестве объектов могут выступать символические имена меток, переменных или процедур. Для до- ступа к ним, естественно, должен быть сформирован адрес. Так вот, если объект расположен «близ- ко» (near) и для доступа к нему нет необходимости менять значение соответствующего сегментного регистра, то говорят, что это объект типа near; в противном случае - это объект типа far (дальний).
Директивы сегментации 103 Если вы посмотрите на текст листинга 5.1, то увидите пример использования одного из этих идентификаторов. Это @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 Код в нескольких сегментах, по одному на каж- дый объединяемый программный модуль Параметр модификатор директивы MODEL позволяет уточнить некоторые особен- ности использования выбранной модели памяти (табл. 5.6). Таблица 5.6. Модификаторы модели памяти Значение модификатора Назиачеиие usel6 Сегменты выбранной модели используются как 16-бито- вые (если соответствующей директивой указан процессор i80386 или 180486) use32 Сегменты выбранной модели используются как 32-бито- вые (если соответствующей директивой указан процессор i80386 или i80486) dos Программа будет работать в MS-DOS Необязательные параметры язык и модификатор языка определяют некоторые особенности вызова процедур. Необходимость в использовании этих парамет- ров появляется при написании и связывании программ на различных языках
104 Урок 5. Структура программы на ассемблере программирования. Этого вопроса мы еще коснемся на уроке 14 при обсужде- нии средств модульного программирования на ассемблере. Описанные нами стандартные и упрощенные директивы сегментации не ис- ключают друг друга. Стандартные директивы используются, когда програм- мист желает получить полный контроль над размещением сегментов в памяти и их комбинированием с сегментами других модулей. Упрощенные директивы целесообразно использовать для простых программ и программ, предназначен- ных для связывания с программными модулями, написанными на языках высо- кого уровня. Это позволяет компоновщику эффективно связывать модули раз- ных языков за счет стандартизации связей и управления. Таким образом, в ходе предыдущего изложения мы разобрались со структурой программы на ассемблере. Мы выяснили, что программа разбивается на не- сколько сегментов, каждый из которых имеет свое функциональное назначе- ние. Для такого разбиения нужно использовать директивы сегментации. TASM предоставляет два типа таких директив: стандартные и упрощенные. Упрощен- ные директивы сегментации мы и будем использовать в дальнейшем, если, ко- нечно, у нас не возникнет необходимость применить стандартные директивы. Описание простых типов данных ассемблера Вторую часть урока мы посвятим описанию данных в программе на ассембле- ре. Любая программа предназначена для обработки некоторой информации, поэтому вопрос о том, как описать данные с использованием средств языка, обычно встает одним из первых. TASM предоставляет очень широкий набор средств описания и обработки данных, который вполне сравним с аналогичны- ми средствами некоторых языков высокого уровня. Начнем обсуждение с правил описания простых типов данных, которые явля- ются базовыми для описания более сложных типов. Для описания простых типов данных в программе используются специальные директивы резервирова- ния и инициализации данных, которые по сути являются указаниями транслято- ру на выделение определенного объема памяти. Если проводить аналогию с языками высокого уровня, то директивы резервирования и инициализации данных являются определениями переменных. Машинного эквивалента этим директивам нет; просто транслятор, обрабатывая каждую такую директиву, выделяет необходимое количество байт памяти и при необходимости инициа- лизирует эту область некоторым значением. Директивы резервирования и ини- циализации данных простых типов имеют формат, показанный на рис. 5.17. На рис. 5.17 использованы следующие обозначения. О ? показывает, что содержимое поля не определено, то есть при задании ди- рективы с таким значением выражения содержимое выделенного участка
Описание простых типов данных ассемблера 105 физической памяти изменяться не будет. Фактически создается неинициали- зированная переменная; О значение инициализации — значение элемента данных, которое будет занесе- но в память после загрузки программы. Фактически создается инициализиро- ванная переменная, в качестве которой могут выступать константы, строки символов, константные и адресные выражения в зависимости от типа данных. Подробная информация приведена в приложении 6; О выражение — итеративная конструкция с синтаксисом, описанным на рис. 5.17. Эта конструкция позволяет повторить последовательное занесе- ние в физическую память выражения в скобках п раз. О имя — некоторое символическое имя метки или ячейки памяти в сегменте данных, используемое в программе. h Имя <2) ----------------------- Значение инициализации Имя |---------------------------- - повторений |~ ---------->----------О----------------- Рис. 5.17. Директивы описания данных простых типов На рис. 5.17 представлены следующие поддерживаемые TASM директивы ре- зервирования и инициализации данных: О db — резервирование памяти для данных размером 1 байт; О dw — резервирование памяти для данных размером 2 байта; О dd — резервирование памяти для данных размером 4 байта; О df — резервирование памяти для данных размером 6 байт; О dp — резервирование памяти для данных размером 6 байт; О dq — резервирование памяти для данных размером 8 байт; О dt — резервирование памяти для данных размером 10 байт. Очень важно уяснить себе порядок размещения данных в памяти. Он напря- мую связан с логикой работы микропроцессора с данными. Микропроцессоры Intel требуют следования данных в памяти по принципу: младший байт по младшему адресу.
106 Урок 5. Структура программы на ассемблере Для иллюстрации данного принципа рассмотрим листинг 5.2, в котором опреде- лим сегмент данных. В этом сегменте данных приведено несколько директив опи- сания простых типов данных. Используя последовательность шагов, описанную на уроке 3, получим загрузочный модуль. Листинг 5.2. Пример использования директив резервирования и инициализации данных tnasm model small .stack 10Oh .data message db "Запустите эту программу в отладчике", perem_1 db Offh perem_2 dw 3a7fh perem_3 dd 0f54d567ah masdb 10 dup (" ") pole_1 db 5 dup (?) adrdw perem_3 adr_full dd perem_3 findb "Конец сегмента данных программы $" .code start: mov ax,@data mov ds,ax mov ah,09h mov dx,offset message int 21 h mov ax,4c00h int 21h end start Теперь наша цель — посмотреть, как выглядит сегмент данных программы лис- тинга 5.2 в памяти компьютера. Это даст нам возможность обсудить практичес- кую реализацию обозначенного нами принципа размещения данных. Для этого запустим отладчик TD. ЕХЕ, входящий в комплект поставки TASM. Опишем этот процесс по шагам. Введем код из листинга 5.2 в файл с названием prg_5_2.asm. Как мы договори- лись раньше, все манипуляции с файлом будем производить в директории work, где уже содержатся все необходимые для компиляции, компоновки и от- ладки файлы пакета TASM. Запустим процесс трансляции файла следующей командой: tasm.exe /zi prg_5_2.asm , , ,
Описание простых типов данных ассемблера 107 После устранения синтаксических ошибок запустим процесс компоновки объек- тного файла: tlink.exe /v prg_5_2.obj Теперь можно производить отладку: td prg_5_2.exe Если все было сделано правильно, то в отладчике откроется окно Module с исход- ным текстом программы. Для того чтобы с помощью отладчика просмотреть об- ласть памяти, содержащую наш сегмент данных, необходимо открыть окно Dump. Это делается с помощью команды главного меню View|Dump. Но одного открытия окна недостаточно; нужно еще настроить его на адрес начала сегмента данных. Этот адрес должен содержаться в сегментном регистре ds, но, как сказано выше, перед началом выполнения программы адрес в ds не соответ- ствует началу сегмента данных. Нужно перед первым обращением к любому сим- волическому имени произвести загрузку действительного физического адреса сегмента данных. Обычно это действие не откладывают в долгий ящик и про- изводят первыми двумя командами в сегменте кода. Действительный физичес- кий адрес сегмента данных извлекают как значение предопределенной перемен- ной ©data. В нашей программе эти действия выполняют команды mov ах,©data mov ds, ах Для того чтобы посмотреть содержимое нашего сегмента данных, нужно остано- вить выполнение программы после этих двух команд. Это можно сделать, если перевести отладчик в пошаговый режим с помощью клавиш F7 или F8. Нажмите два раза F8. Теперь можно открыть окно Dump. В окне Dump вызовите контекстное меню, щелкнув правой кнопкой мыши. В появившемся контекстном меню выберите команду Goto. Появится диалого- вое окно, в котором нужно ввести начальный адрес памяти, начиная с которого будет выводиться информация в окне Dump. Синтаксис задания этого адреса должен соответствовать синтаксису задания адресного операнда в программе на ассемблере (см. начало этого урока). Мы бы хотели увидеть содержимое памяти для нашего сегмента данных, начиная с его начала, поэтому введем ds:0000. Для удобства, если сегмент достаточно велик, это окно можно распах- нуть на весь экран. Для этого нужно щелкнуть на символе в правом верх- нем углу окна Dump. Вид экрана показан на рис. 5.18. Обсудим рис. 5.18. На нем вы видите данные вашего сегмента в двух представ- лениях: шестнадцатеричном и символьном. Видно, что со смещением 0000 рас- положены символы, входящие в строку message. Она занимает 34 байта. После нее следует байт, имеющий в сегменте данных символическое имя perem_1, со- держимое этого байта offh. Теперь обратите внимание на то, как размещены в памяти байты, входящие в слово, обозначенное символическим именем регеш_2.
108 Урок 5. Структура программы на ассемблере Сначала следует байт со значением 7f h, а затем со значением 3ah. Как видите, в памяти действительно сначала расположен младший байт значения, а затем старший. Теперь посмотрите и самостоятельно проанализируйте размещение байтов для поля, обозначенного символическим именем регет.З. Оставшуюся часть сегмента данных вы можете теперь проанализировать самостоятельно. Остановимся лишь на двух специфических особенностях использования ди- ректив резервирования и инициализации памяти. Речь идет о случае использо- вания в поле операндов директив dw и dd символического имени из поля имя этой или другой директивы резервирования и инициализации памяти. В на- шем примере сегмента данных это директивы с именами ad г и adr_full. Когда транслятор встречает директивы описания памяти с подобными операндами, то он формирует в памяти значения адресов тех переменных, чьи имена были указаны в качестве операндов. В зависимости от директивы, применяемой для получения такого адреса, формируется либо полный адрес (директива dd) в виде двух байтов сегментного адреса и двух байтов смещения, либо только сме- щение (директива dw). Найдите в дампе на рис. 5.18 поля, соответствующие именам ad г и adr_full, и проанализируйте их содержимое. Пуск| [Д Window. Command Б имени Pamt ||Л TD 8'44 Рис. 5.18. Окно дампа памяти
Описание простых типов данных ассемблера 109 Любой переменной, объявленной с помощью директив описания простых ти- пов данных, ассемблер присваивает три атрибута: 1. Сегмент (seg) — адрес начала сегмента, содержащего переменную. 2. Смещение (offset) в байтах от начала сегмента с переменной. 3. Тип (type) — определяет количество памяти, выделяемой переменной в со- ответствии с директивой объявления переменной. Получить и использовать значение этих атрибутов в программе можно с помо- щью рассмотренных нами выше операторов ассемблера seg, offset и type. В заключение рекомендую вам обратить внимание на приложение 6, где при- ведены диапазоны значений для тех простых типов данных, директивы описа- ния которых мы обсудили на данном уроке. Посмотрите эту информацию и используйте ее при необходимости явного задания значений для данных раз- личных типов. Подведем некоторые итоги: 0 Программа на ассемблере, отражая особенности архитектуры микропроцес- сора, состоит из сегментов — блоков памяти, допускающих независимую адресацию. 0 Каждый сегмент может состоять из предложений языка ассемблера четырех типов: команд ассемблера, макрокоманд, директив ассемблера и строк ком- ментариев. 0 Ассемблер допускает большое разнообразие типов операндов, которые могут содержаться непосредственно в команде, в регистрах и в памяти. 0 Операнды в команде могут быть выражениями. 0 Исходный текст программы разбивается на сегменты с помощью директив сегментации, которые делятся на стандартные, поддерживаемые всеми транс- ляторами ассемблера, и упрощенные, поддерживаемые транслятором TASM. 0 Упрощенные директивы сегментации позволяют унифицировать интерфейс с языками высокого уровня и облегчают разработку программ, повышая на- глядность кода. 0 Существует два режима работы TASM: MASM и IDEAL. Назначение режи- ма MASM — обеспечить полную совместимость с транслятором MASM фирмы Microsoft. Назначение режима IDEAL — упростить синтаксис кон- струкций языка, повысить эффективность и скорость работы транслятора.
110 Урок 5. Структура программы на ассемблере 0 TASM поддерживает разнообразные типы данных, которые делятся на прос- тые (базовые) и сложные. Простые типы служат как бы «кирпичиками» для построения сложных типов данных. 0 Директивы описания простых типов данных позволяют зарезервировать и при необходимости инициализировать области памяти заданной длины. 0 Каждой переменной, объявленной с помощью директивы описания данных, TASM назначает атрибуты, доступ к которым можно получить с помощью соответствующих операторов ассемблера.
УРОК Система команд микропроцессора □ Представление информации в компьютере □ Структура машинной команды □ Способы адресации операндов □ Общая характеристика системы команд
На этом уроке мы закончим обсуждение общих вопросов, необходимых для понимания программ на ассемблере. Будет рассмотрен формат машинной ко- манды, мы научимся адресовать операнды в памяти и разберем основные спо- собы адресации. Кроме того, мы покажем, как адресация операндов машинной команды отражается на ее структуре. В заключение будет рассмотрена класси- фикация команд по их функциональному назначению. Это даст нам возмож- ность оценить возможности микропроцессора по обработке данных. Но прежде чем приступить к обсуждению, необходимо выяснить еще один принципиальный для программирования на ассемблере момент — работу с различными системами счисления. На уроке 3 при разборе программы мы за- тронули эту проблему. Актуальность ее при программировании на низком уровне очевидна из следующих положений: О компьютер работает с двоичной информацией; О человеку удобнее интерпретировать двоичную информацию посредством шестнадцатеричной системы счисления; О человеку удобно производить вычисления, используя десятичную систему счисления. Вам не раз в дальнейшем придется убедиться в истинности этих положений при работе с дампами памяти в отладчике или в других ситуациях. На практи- ке при отладке или исследовании работы некоторой программы часто прихо- дится заниматься интерпретацией содержимого нужного регистра или участка памяти, и поначалу проблема перевода чисел, как правило, вызывает опреде- ленные трудности. В связи с этим я привожу справочную информацию об ис- пользуемых при работе с компьютером системах счисления и алгоритмы вза- имного преобразования чисел для этих систем счисления. Системы счисления Как известно, системой счисления называется совокупность правил записи чисел. Системы счисления подразделяются на позиционные и непозиционные. Как позиционные, так и непозиционные системы счисления используют опре- деленный набор символов — цифр, последовательное сочетание которых обра- зует число. Непозиционные системы счисления появились раньше позицион-
Системы счисления 113 ных. Они характеризуются тем, что в них символы, обозначающие то или иное число, не меняют своего значения в зависимости от местоположения в записи этого числа. Классическим примером такой системы счисления является рим- ская. В ней для записи чисел используются буквы латинского алфавита. При этом буква I означает единицу, V — пять, X — десять, L — пятьдесят, С — сто, D — пятьсот, М — тысячу. Для получения количественного эквивалента числа в римской системе счисления необходимо просто просуммировать количе- ственные эквиваленты входящих в него цифр. Исключение из этого правила составляет случай, когда младшая цифра идет перед старшей, — в этом случае нужно не складывать, а вычитать число вхождений этой младшей цифры. К примеру, количественный эквивалент числа 577 в римской системе счисле- ния — это DLXXVII = 500 + 50 + 10 + 10 + 5+1 + 1 = 577. Другой пример: CDXXIX = 500 - 100 + 10 + 10 - 1 + 10 = 429. В позиционной системе счисления количество символов в наборе равно осно- ванию системы счисления. Место каждой цифры в числе называется позицией. Номер позиции символа (за вычетом единицы) в числе называется разрядом. Разряд 0 называется младшим разрядом. Каждой цифре соответствует опреде- ленный количественный эквивалент. Введем обозначение — запись Аф> будет означать количественный эквивалент числа А, состоящего из п цифр ak (где k = 0...п-1) в системе счисления с основанием р. Это число можно представить в виде последовательности цифр: А^ = ап_{ап_2... ataa. При этом, конечно, всегда выполняется неравенство ak < р. В общем случае, количественный эквивалент некоторого положительного числа А в позиционной системе счисления можно представить выражением А(₽) “ ап-1*Р"'1 + ап-2*Р"'2 + - + а*Р1 + а*Р°< (6-1) где: р — основание системы счисления (некоторое целое положительное число); а — цифра данной системы счисления; п — номер старшего разряда числа. Для получения количественного эквивалента числа в некоторой позиционной системе счисления необходимо сложить произведения количественных значе- ний цифр на степени основания, показатели которых равны номерам разрядов (обратите внимание, что нумерация разрядов начинается с нуля). После такого формального введения можно приступить к обсуждению некото- рых позиционных систем счисления, наиболее часто используемых при разра- ботке программ на ассемблере. Двоичная система счисления Набор цифр для двоичной системы счисления: {0, 1}, основание степени р - 2. Количественный эквивалент некоторого целого п-значного двоичного числа вы- числяется согласно формуле (6.1): А(2) 5=1 ая-1*2” 1 + a»-2*2”’2 +- + fli*21 + ао*2°- (6.2)
114 Урок 6. Система команд микропроцессора Как мы уже отмечали, наличие этой системы счисления обусловлено тем, что компьютер построен на логических схемах, имеющих в своем элементарном виде только два состояния — включено и выключено. Производить счет в дво- ичной системе просто для компьютера, но сложно для человека. Например, рассмотрим двоичное число 10100111 Вычислим количественный эквивалент этого двоичного числа. Согласно фор- муле (6.2), это будет величина, равная следующей сумме: 1*27 + 0*26 + 1*25 4- 0*24 4- 0*23 4- 1*22 4- Р21 4- 1*2°. Посчитайте сами, сколько получится. Сложение и вычитание двоичных чисел (рис. 6.1) выполняется так же, как и для других позиционных систем счисления, например десятичной. Точно так же выполняются заем и перенос единицы из (в) старший разряд. К примеру: 11 11111 перенос 11 1 заем 110011011 _ 1 1 01 001 001 1 + 1 1 001 01 01 001 1 1 01 1 01 1 1 1 001 1 0000 1 001 01 1 1 000 Рис, 6.1. Сложение и вычитание двоичных чисел Приведем степени двойки (табл. 6.1) и соответствие двоичных чисел и их деся- тичных и шестнадцатеричных эквивалентов (табл. 6.2). Таблица 6.1. Степени двойки к 2к 1 2 3 4 5 6 7 8 9 10 И 12 2 4 8 16 32 64 128 256 512 1024 2048 4096 Шестнадцатеричная система счисления Данная система счисления имеет следующий набор цифр: {0, 1, 2, 9, А, В, С, D, Е, F}, основание системы р - 16.
Системы счисления 115 Количественный эквивалент некоторого целого n-значного шестнадцатерично- го числа вычисляется согласно формуле (6.1): А(1в) - + ап./16"-2 +... + а/161 + а0*16°. К примеру, количественный эквивалент шестнадцатеричного числа f45ed23c равен 15*16,+4*16в+5*16’+14*1б4+13*16»+2*16г+ЗП6‘+12*16Р. Посчитайте сами, сколько получится. Таблица 6.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 А, а И 1011 В, b 12 1100 С, с 13 1101 D, d 14 1110 Е,е 15 1111 F, f 16 10000 10 Запомнить поначалу эти соотношения сложно, поэтому полезно иметь под рука- ми некоторую справочную информацию. Табл. 6.2 содержит представления де- сятичных чисел из диапазона 0-16 в двоичной и шестнадцатеричной систе- мах счисления. Табл. 6.2 удобно использовать для взаимного преобразования чисел в рассматриваемых нами трех системах счисления. Шестнадцатеричная система счисления при производстве вычислений несколько сложнее, чем дво- ичная, в частности, в том, что касается правил переносов в старшие разряды (заемов из старших разрядов). Главное здесь помнить следующее равенство: (1 + F = 1О)16
116 Урок 6. Система команд микропроцессора Эти переходы очень важны при выполнении сложения и вычитания шестнад- цатеричных чисел. Пример приведен на рис. 6.2: 11 EF1 5 + С1 Е8 перенос 1 слагаемое 2 слагаемое 1 1 BCD8 ~ 5EF4 заем уменьшаемое вычитаемое 1 B0FD результат 5DE4 результат Рис. 6.2. Сложение и вычитание шестнадцатеричных чисел Десятичная система счисления Это наиболее известная система счисления, так как она постоянно использует- ся нами в повседневной жизни. Данная система счисления имеет следующий набор цифр: {О, 1, 2, 3, 4, 5, 6, 7, 8, 9}, основание степени р = 10. Количественный эквивалент некоторого целого n-значного десятичного числа вычисляется согласно формуле (6.1): А(ю> “ ^п-Г10"’1 + ал-2*10л'2 + - + a*iQl + ао*1О°. К примеру, значение числа А(10) = 4523 равно 4*103+ 5*102+ 2*10» +3*10°. Перевод чисел из одной системы счисления в другую Рассмотрение данных систем счисления само по себе еще только полдела. Для того чтобы в полной мере использовать их в своей практической работе при программировании на ассемблере, необходимо научиться выполнять вза- имное преобразование чисел между тремя системами счисления. Этим мы и займемся в дальнейшем. Кроме того, дополнительно будут рассмотрены некото- рые особенности микропроцессоров Intel при работе с числами со знаком. Перевод в десятичную систему счисления Этот тип перевода наиболее прост. Обычно его производят с помощью так на- зываемого алгоритма замещения, суть которого заключается в следующем: сна- чала в десятичную систему счисления переводится основание степени р, а за- тем — цифры исходного числа. Результаты подставляются в формулу (6.1). Полученная сумма и будет искомым результатом. Неявно при обсуждении дво- ичной и шестнадцатеричной систем счисления мы производили как раз такое преобразование.
Перевод чисел из одной системы счисления в другую 117 Перевод в двоичную систему счисления Перевод из десятичной системы счисления 1. Разделить десятичное число А на 2. Запомнить частное q и остаток а. 2. Если в результате шага 1 частное q 0, то принять его за новое делимое и отметить остаток а, который будет очередной значащей цифрой числа, вер- нуться к шагу 1, на котором в качестве делимого (десятичного числа) уча- ствует полученное на шаге 2 частное. 3. Если в результате шага 1 частное q - 0, алгоритм прекращается. Выписать ос- татки в порядке обратном их получению. Получится двоичный эквивалент исходного числа. К примеру, требуется перевести число 247 в двоичную систему счисления (рис. 6.3): Рис. 6.3. Перевод в двоичную систему счисления Порядок обхода остатков для получения результата показан стрелками, и резуль- тат преобразования равен 111101112. Перевод из шестнадцатеричной системы счисления Этот переход мы уже обсуждали выше. Суть его заключается в последователь- ной замене шестнадцатеричных цифр соответствующими двоичными тетрада- ми согласно табл. 6.2. К примеру, e4d516 —> 1110 0100 1101 01012.
118 Урок 6. Система команд микропроцессора Перевод в шестнадцатеричную систему счисления Перевод из десятичной системы счисления Общая идея такого перевода аналогична рассмотренной выше в алгоритме пере- вода в двоичную систему счисления из десятичной. 1. Разделить десятичное число А на 16. Запомнить частное q и остаток а. 2. Если в результате шага 1 частное q 0, то принять его за новое делимое, за- писать остаток и вернуться к шагу 1. 3. Если частное q - 0, прекратить работу алгоритма. Выписать остатки в порядке обратном их получению. Получится шестнадцатеричный эквивалент исходно- го десятичного числа. К примеру, требуется преобразовать 32 76710 в шестнадцатеричную систему счис- ления (рис. 6.4). 32767 I 16 32 2047 I 16 76 16 127 16 Рис. 6.4. Перевод в шестнадцатеричную систему счисления Порядок обхода остатков для получения результата показан на рис. 6.4 стрелка- ми. Результат преобразования равен 7fff16. Перевод из двоичной системы счисления Идея алгоритма аналогична идее перевода из двоичной системы счисления в шестнадцатеричную. Суть в том, что двоичное число разбивается на тетрады, начиная с младшего разряда. Далее каждая тетрада приводится к соответству- ющему шестнадцатеричному числу согласно табл. 6.2. К примеру, требуется перевести число 111001011010111101011000110110001111010101011012 в шестнадцатеричную систему счисления. Разобьем его на тетрады: 0111 0010 1101 0111 1010 1100 ОНО 1100 0111 1010 1010 1101. По тетрадам приводим последовательности нулей и единиц к шестнадцатерич- ному представлению: 72d7ac6c7aad
Перевод чисел из одной системы счисления в другую 119 В итоге мы получили результат преобразования: 11100101101011110101100011011000111101010101101,-*72d7ac6c7aad,e 4 10 Числа со знаком До сих пор предполагалось, что числа положительные. А как представляются в компьютере числа со знаком? На уроке 2, обсуждая типы данных, мы отмечали, что целые числа могут пред- ставляться как числа со знаком и без знака. В чем отличие? Положительные целые со знаком — это 0 и все положительные числа. Отрицательные целые со знаком — это все числа, меньшие 0. Отличительным признаком числа со знаком является особая трактовка старшего бита поля, представляющего число. В качестве поля могут выступать байт, слово или двойное слово. Естественно, что физически этот бит ничем не отличается от других — все зависит от команды, работающей с данным полем. Если в ее ал- горитме заложена работа с целыми числами со знаком, то она будет по-особо- му трактовать старший бит поля. В случае, если бит равен 0, число считается положительным и его значение вычисляется по правилам, которые мы рас- смотрели выше. В случае, если этот бит равен 1, число считается отрицатель- ным и предполагается, что оно записано в так называемом дополнительном коде. Разберемся в том, что он собой представляет. Дополнительный код некоторого отрицательного числа представляет собой ре- зультат инвертирования (замены 1 на 0 и наоборот) каждого бита двоичного числа, равного модулю исходного отрицательного числа, плюс единица. К при- меру, рассмотрим десятичное число -18510. Модуль данного числа в двоичном представлении равен 101110012. Сначала нужно дополнить это значение слева нулями до нужной размерности — байта, слова и т. д. В нашем случае допол- нить нужно до слова, так как диапазон представления знаковых чисел в байте составляет -128...127. Следующее действие — получить двоичное дополнение. Д.ля этого все разряды двоичного числа нужно инвертировать: 00000000101110012 -> 11111111010001102 Теперь прибавляем единицу: 11111111010001102 + 00000000000000012 - 11111111010001112 Результат преобразования равен 11111111010001112. Именно так и представля- ется число —18510 в компьютере. При работе с числами со знаком от вас наверняка потребуется умение выполнять обратное действие — имея двоичное дополнение числа, определить значение его модуля. Для этого необходимо выполнить два действия: 1. Выполнить инвертирование битов двоичного дополнения. 2. К полученному двоичному числу прибавить двоичную единицу.
120 Урок 6. Система команд микропроцессора К примеру, определим модуль двоичного представления числа —18510 “ - 11111Ш01000Ш2: 11111111010001112 инвертируем биты 00000000101110002. Добавляем двоичную единицу: 00000000101110002 + 00000000000000012 - 00000000101110012= |-185| Теперь вам должно быть понятно, откуда появилась разница в диапазонах зна- чений для чисел со знаком и без знака простых типов данных, обсуждавшихся на уроке 2. Теперь мы почти готовы разговаривать с компьютером на его языке, состоящем из команд и данных. С данными мы уже разобрались, теперь давайте обратим- ся к командам. Структура машинной команды Машинная команда представляет собой закодированное по определенным пра- вилам указание микропроцессору на выполнение некоторых операции или дей- ствия. Каждая команда содержит элементы, определяющие: О что делать? (ответ на этот вопрос дает элемент команды, называемый кодом операции (КОП)); О объекты, над которыми нужно что-то делать (эти элементы называются операндами); О как делать? (эти элементы называются типами операндов — обычно зада- ются неявно). Приведенный на рис. 6.5 формат машинной команды является самым общим. Максимальная длина машинной команды — 15 байт. Реальная команда может содержать гораздо меньшее количество полей, вплоть до одного — только КОП. Однобайтовые префиксы: повторения |- раэмера адреса |- размера операнда |- замены сегмента |- блокировки шины |- Количество байт 1 или 2 0 или 1 0 или 1 0,1,2 или 4 0,1,2 или4 код операции байт modr/m байт sib смещение в команде непосред- ственный операнд index base г/m mod reg/КОП ss 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 Рис. 65. Формат машинной команды
Структура машинной команды 121 Опишем назначения полей машинной команды. Префиксы. Необязательные элементы машинной команды, каждый из которых состоит из одного байта или может отсутствовать. В памяти префиксы предшест- вуют команде. Назначение префиксов — модифицировать операцию, выпол- няемую командой. Прикладная программа может использовать следующие типы префиксов: Префикс замены сегмента. В явной форме указывает, какой сегментный регистр используется в данной команде для адресации стека или данных. Префикс отме- няет выбор сегментного регистра по умолчанию. Префиксы замены сегмента име- ют следующие значения: 2eh — замена сегмента cs, 36h — замена сегмента ss, 3eh — замена сегмента ds, 26h — замена сегмента es, 64h — замена сегмента fs, 65h — замена сегмента gs. Префикс разрядности адреса уточняет разрядность адреса (32 или 16-разряд- ный). Каждой команде, в которой используется адресный операнд, ставится в соответствие разрядность адреса этого операнда. Этот адрес может иметь раз- рядность 16 или 32 бит. Если разрядность адреса для данной команды 16 бит, это означает, что команда содержит 16-разрядное смещение (см. рис. 6.5) и оно соответствует 16-разрядному смещению адресного операнда относительно начала некоторого сегмента. В контексте рис. 2.3 это смещение называется эффек- тивным адресом. Если разрядность адреса 32 бит, это означает, что команда содержит 32-разрядное смещение (см. рис. 6.5), оно соответствует 32-разряд- ному смещению адресного операнда относительно начала сегмента и по его зна- чению формируется 32-битное смещение в сегменте. С помощью префикса разрядности адреса можно изменить действующее по умолчанию значение раз- рядности адреса. Это изменение будет касаться только той команды, которой предшествует префикс. Префикс разрядности операнда аналогичен префиксу разрядности адреса, но указывает на разрядность операндов (32 или 16-разрядные), с которыми рабо- тает команда. В соответствии с какими правилами устанавливаются значения атрибутов разрядности адреса и операндов по умолчанию? В реальном режиме и режиме виртуального i8086 значения этих атрибутов — 16 бит. В защищен- ном режиме значения атрибутов зависят от состояния бита D в дескрипторах исполняемых сегментов (см. урок 16). Если D = 0, то значения атрибутов, действующие по умолчанию, равны 16 бит; если D = 1, то 32 бит. Значения префиксов разрядности операнда 66h и разрядности адреса 67h. Вы можете с помощью префикса разрядности адреса в реальном режиме использовать 32-разрядную адресацию, но при этом необходимо помнить об ограниченности размера сегмента величиной 64 Кбайт. Аналогично префиксу разрядности адре- са вы можете использовать префикс разрядности операнда в реальном режиме для работы с 32-разрядными операндами (к примеру, в арифметических ко- мандах). Префикс повторения используется с цепочечными командами (командами обра- ботки строк). Этот префикс «зацикливает* команду для обработки всех элемен-
122 Урок 6. Система команд микропроцессора тов цепочки. Система команд поддерживает два типа префиксов: безусловные (rep — 0f3h), заставляющие повторяться цепочечную команду некоторое количе- ство раз, и условные (repe/repz - 0f3h, repne/repnz - 0f2h), которые при зацикли- вании проверяют некоторые флаги, — и в результате проверки возможен досроч- ный выход из цикла. Код операции. Обязательный элемент, описывающий операцию, выполняемую командой. Многим командам соответствует несколько кодов операций, каждый из которых определяет нюансы выполнения операции. Последующие поля машинной команды определяют местоположение операн- дов, участвующих в операции, и особенности их использования. Рассмотрение этих полей связано со способами задания операндов в машинной команде и потому будет выполнено ниже. Байт режима адресации modr/m. Значение этого байта определяет используе- мую форму адреса операндов. Операнды могут находиться в памяти, в одном или двух регистрах. Если операнд находится в памяти, то байт modr/m опреде- ляет компоненты (смещение, базовый и индексный регистры), используемые для вычисления его эффективного адреса (см. рис. 2.3). В защищенном режи- ме для определения местоположения операнда в памяти может дополнительно использоваться байт sib (Scale-Index-Base — масштаб-индекс-база). Байт modr/m состоит из трех полей (см. рис. 6.5): О поле mod определяет количество байт, занимаемых в команде адресом опе- ранда (см. рис. 6.5, поле смещение в команде). Поле mod используется сов- местно с полем r/m, которое указывает способ модификации адреса операнда смещение в команде. К примеру, если mod = 00, это означает, что поле смещение в команде отсутствует и адрес операнда определяется содержимым базового и (или) индексного регистра. Какие именно регистры будут использоваться для вычисления эффективного адреса, определяется значением этого байта. Если mod = 01, это означает, что поле смещение в команде присутствует, занимает один байт и модифицируется содержимым базового и (или) индексного регистра. Если mod = 10, это означает, что поле смещение в команде присутствует, занима- ет два или четыре байта (в зависимости от действующего по умолчанию или определяемого префиксом размера адреса) и модифицируется содержимым базового и (или) индексного регистра. Если mod = 11, это означает, что опе- рандов в памяти нет; они находятся в регистрах. Это же значение байта mod используется в случае, когда в команде применяется непосредственный опе- ранд; О поле reg/коп определяет либо регистр, находящийся в команде на месте пер- вого операнда, либо возможное расширение кода операции; О поле r/m используется совместно с полем mod и определяет либо регистр, нахо- дящийся в команде на месте первого операнда (если mod = 11), либо использу- емые для вычисления эффективного адреса (совместно с полем смещение в команде) базовые и индексные регистры.
Структура машинной команды 123 Байт масштаб-индекс-база (байт sib) используется для расширения возможно- стей адресации операндов. На наличие байта sib в машинной команде указывает сочетание одного из значений 01 или 10 поля mod и значения поля г/m = 100. Байт sib состоит из трех полей: О поле масштаба ss. В этом поле размещается масштабный множитель для индексного компонента index, занимающего следующие три бита байта sib. В поле ss может содержаться одно из следующих значений: 1, 2, 4, 8. При вы- числении эффективного адреса на это значение будет умножаться содержи- мое индексного регистра. Более подробно с практической точки зрения эта расширенная возможность индексации рассматривается на уроке 12 при обсуждении вопросов работы с массивами; О поле index используется для хранения номера индексного регистра, который применяется для вычисления эффективного адреса операнда; О поле base используется для хранения номера базового регистра, который также применяется для вычисления эффективного адреса операнда. Напом- ню, что в качестве базового и индексного регистров могут использоваться практически все регистры общего назначения. Поле смещения в команде. 8, 16 или 32-разрядное целое число со знаком, пред- ставляющее собой, полностью или частично (с учетом вышеприведенных рас- суждений), значение эффективного адреса операнда. Поле непосредственного операнда. Необязательное поле, представляющее собой 8,16 или 32-разрядный непосредственный операнд. Наличие этого поля, конечно, отражается на значении байта mod г/т. Способы задания операндов команды В ходе предыдущего изложения мы поневоле касались вопроса о том, где распо- лагаются операнды, с которыми работает машинная команда, и как это отра- жается на содержимом ее полей. В этой части урока мы рассмотрим этот воп- рос более систематизированно и в полном объеме. Это позволит нам уже со следующего урока перейти непосредственно к практическим вопросам про- граммирования на языке ассемблера. Операнд задается неявно на микропрограммном уровне. В этом случае команда явно не содержит операндов. Алгоритм выполнения команды использует неко- торые объекты по умолчанию (регистры, флаги в eflags и т. д.). Например, команды cli и sti неявно работают с флагом прерывания if в регистре eflags, а команда xlat неявно обращается к регистру al и строке в памяти по адресу, определяемому парой регистров ds:bx. Операнд задается в самой команде (непосредственный операнд). Операнд нахо- дится в коде команды, то есть является ее частью. Для хранения такого операнда в команде выделяется поле длиной до 32 бит (см. рис. 6.5). Непосредственный операнд может быть только вторым операндом (источником). Операнд-получа-
124 Урок 6. Система команд микропроцессора тель может находиться либо в памяти, либо в регистре. Например, mov ax,0ffffh пересылает в регистр ах шестнадцатеричную константу ffff. Команда add sum,2 складывает содержимое поля по адресу sum с целым числом 2 и записывает ре- зультат по месту первого операнда, то есть в память. Операнд находится в одном из регистров. Регистровые операнды указываются именами регистров. В качестве регистров могут использоваться: О 32-разрядные регистры EAX, ЕВХ, ЕСХ, EDX, ESI, EDI, ESP, EBP; О 16-разрядные регистры АХ, ВХ, СХ, DX, SI, DI, SP, ВР; О 8-разрядные регистры АН, AL, ВН, BL, CH, CL, DH, DL; О сегментные регистры CS, DS, SS, ES, FS, GS. Например, команда add ax,bx складывает содержимое регистров ах и Ьх и запи- сывает результат в Ьх. Команда dec si уменьшает содержимое si на 1. Операнд располагается в памяти. Это наиболее сложный и в то же время наи- более гибкий способ задания операндов. Он позволяет реализовать следующие два основных вида адресации: прямую адресацию и косвенную адресацию. В свою очередь, косвенная адресация имеет следующие разновидности: О косвенная базовая адресация; другое ее название — регистровая косвенная адресация; О косвенная базовая адресация со смещением; О косвенная индексная адресация со смещением; О косвенная базовая индексная адресация; О косвенная базовая индексная адресация со смещением. Операндом является Порт ввода/вывода. Как мы уже отмечали, помимо адрес- ного пространства оперативной памяти микропроцессор поддерживает адрес- ное пространство ввода-вывода, которое используется для доступа к устрой- ствам ввода-вывода. Объем адресного пространства ввода-вывода составляет 64 Кбайт. Для любого устройства компьютера в этом пространстве выделяют- ся адреса. Конкретное значение адреса в пределах этого пространства называ- ется портом ввода-вывода. Физически порту ввода-вывода соответствует ап- паратный регистр (не путать с регистром микропроцессора), доступ к которому осуществляется с помощью специальных команд ассемблера in и out. Например: in al, 60h; ввести байт из порта 60h Регистры, адресуемые с помощью порта ввода-вывода, могут иметь разряд- ность 8, 16 или 32 бит, но для конкретного порта разрядность регистра фикси- рована. Команды in и out работают с фиксированной номенклатурой объектов. В качестве источника информации или получателя применяются так называе- мые регистры-аккумуляторы eax, ах, al. Выбор регистра определяется разряд- ностью порта. Номер порта может задаваться непосредственным операндом в
Структура машинной команды 125 командах in и out или значением в регистре dx. Последний способ позволяет динамически определить номер порта в программе. Например: mov dx, 20h ; записать номер порта 20h в регистр dx mov al, 20h; записать значение 20h в регистр al out dx, al ; вывести значение 20h в порт 20Н Операнд находится в стеке. Команды могут совсем не иметь операндов, иметь один или два операнда. Большинство команд требуют двух операндов, один из которых является операндом-источником, а второй — операндом назначения. Важно то, что один операнд может располагаться в регистре или памяти, а второй операнд обяза- тельно должен находиться в регистре или непосредственно в команде. Непос- редственный операнд может быть только операндом-источником. В двухоперандной машинной команде возможны следующие сочетания опе- рандов: О регистр—регистр; О регистр—память; О память—регистр; О непосредственный операнд—регистр; О непосредственный операнд—память. Для данного правила есть исключения, которые касаются: О команд работы с цепочками, которые могут перемещать данные из памяти в память; О команд работы со стеком, которые могут переносить данные из памяти в стек, также находящийся в памяти; О команд типа умножения, которые кроме операнда, указанного в команде, используют еще и второй, неявный операнд. Из перечисленных сочетаний операндов наиболее часто употребляются ре- гистр-память и память—регистр. Ввиду их важности рассмотрим их под- робнее. Обсуждение мы будем сопровождать примерами команд ассемблера, которые будут показывать, как изменяется формат команды ассемблера при применении того или иного вида адресации. В связи с этим посмотрите еще раз на рис. 2.3, на котором показан принцип формирования физического адре- са на адресной шине микропроцессора. Видно, что адрес операнда формирует- ся как сумма двух составляющих — сдвинутого на 4 бит содержимого сегмент- ного регистра и 16-битного эффективного адреса, который в общем случае вычисляется как сумма трех компонентов: базы, смещения и индекса. Рассмотрим особенности основных видов адресации операндов в памяти.
126 Урок 6. Система команд микропроцессора Прямая адресация Это простейший вид адресации операнда в памяти, так как эффективный адрес содержится в самой команде и для его формирования не используется никаких дополнительных источников или регистров. Эффективный адрес берется не- посредственно из поля смещения машинной команды (см. рис. 6.5), которое может иметь размер 8, 16, 32 бит. Это значение однозначно определяет байт, слово или двойное слово, расположенные в сегменте данных. Прямая адресация может быть двух типов: О Относительная прямая адресация. Используется для команд условных пе- реходов, для указания относительного адреса перехода. Относительность такого перехода заключается в том, что в поле смещения машинной ко- манды содержится 8,16 или 32-битное значение, которое в результате работы команды будет складываться с содержимым регистра указателя команд ip/eip. В результате такого сложения получается адрес, по которому и осуще- ствляется переход. К примеру: jc ш1 ; переход на метку ш1, если флаг cf = 1 mov al, 2 ml: Несмотря на то, что в команде указана некоторая метка в программе, ассемб- лер вычисляет смещение этой метки относительно следующей команды (в нашем случае это mov al,2) и подставляет его в формируемую машинную ко- манду j с. О Абсолютная прямая адресация. В этом случае эффективный адрес является частью машинной команды, но формируется этот адрес только из значения поля смещения в команде. Для формирования физического адреса операнда в памяти микропроцессор складывает это поле со сдвинутым на 4 бит зна- чением сегментного регистра. В команде ассемблера можно использовать несколько форм такой адресации. К примеру: mov ах, dword pt г [0000] ; записать слово по адресу ;ds: 0000 в регистр ах Но такая адресация применяется редко; обычно используемым ячейкам в про- грамме присваиваются символические имена. В процессе трансляции ассемб- лер вычисляет и подставляет значения смещений этих имен в формируемую им машинную команду в поле смещение в команде (см. рис. 6.5). В итоге по- лучается так, что машинная команда прямо адресует свой операнд, имея, фак- тически, в одном из своих полей значение эффективного адреса. К примеру: data segment perl dw 5
Структура машинной команды 127 data ends code segment mov ax,data mov ds, ax mov ax.perl ; записать слово perl (его физический адрес ds:0000) в ах Мы получим тот же результат, что и при использовании команды mov ах, dword pt г [0000] Остальные виды адресации относятся к косвенным. Слово «косвенный* в на- звании этих видов адресации означает то, что в самой команде может нахо- диться лишь часть эффективного адреса, а остальные его компоненты находят- ся в регистрах, на которые указывают своим содержимым байт modr/m и, возможно, байт sib. Косвенная базовая (регистровая) адресация При такой адресации эффективный адрес операнда может находиться в любом из регистров общего назначения, кроме sp/esp и bp/ebp (это специфические регистры для работы с сегментом стека). Синтаксически в команде этот режим адресации выражается заключением име- ни регистра в квадратные скобки [ ]. К примеру, команда mov ах,[есх] помеща- ет в регистр ах содержимое слова по адресу из сегмента данных со смещением, хранящимся в регистре есх. Так как содержимое регистра легко изменить в ходе работы программы, дан- ный способ адресации позволяет динамически назначить адрес операнда для некоторой машинной команды. Это свойство очень полезно, например, для организации циклических вычислений и для работы с различными структура- ми данных типа таблиц или массивов. Косвенная базовая (регистровая) адресация со смещением Этот вид адресации является дополнением предыдущего и предназначен для доступа к данным с известным смещением относительно некоторого базового адреса. Этот вид адресации удобно использовать для доступа к элементам структур данных, когда смещение элементов известно заранее, на стадии разра- ботки программы, а базовый (начальный) адрес структуры должен вычислять- ся динамически, на стадии выполнения программы. Модификация содержимого базового регистра позволяет обратиться к одноименным элементам различных экземпляров однотипных структур данных. К примеру, команда mov ax,[edx+3h] пересылает в регистр ах слова из области памяти по адресу: содержимое edx + 3h. Команда mov ax,mas[dx] пересылает
128 Урок 6. Система команд микропроцессора в регистр ах слово по адресу: содержимое dx плюс значение идентификатора mas (не забывайте, что транслятор присваивает каждому идентификатору значение, равное смещению этого идентификатора относительно начала сегмента данных). Косвенная индексная адресация со смещением Этот вид адресации очень похож на косвенную базовую адресацию со смеще- нием. Здесь также для формирования эффективного адреса используется один из регистров общего назначения. Но индексная адресация обладает одной ин- тересной особенностью, которая очень удобна для работы с массивами. Она связана с возможностью так называемого масштабирования содержимого ин- дексного регистра. Что это такое? Посмотрите на рис. 6.5. Нас интересует байт sib. При обсуждении структуры этого байта мы отмечали, что он состоит из трех полей. Одно из этих полей — поле масштаба ss, на значение которого умно- жается содержимое индексного регистра. К примеру, в команде mov ax,mas[si*2] значение эффективного адреса второго операнда вычисляется выражением mas+(si)*2. В связи с тем, что в ассемблере нет средств для организации индек- сации массивов, то программисту своими силами приходится ее организовы- вать. Наличие возможности масштабирования существенно помогает в реше- нии этой проблемы, но при условии, что размер элементов массива составляет 1, 2, 4 или 8 байт. Косвенная базовая индексная адресация При этом виде адресации эффективный адрес формируется как сумма содер- жимого двух регистров общего назначения: базового и индексного. В качестве этих регистров могут применяться любые регистры общего назначения, при этом часто используется масштабирование содержимого индексного регистра. Например: mov eax,[esi][edx] В данном примере эффективный адрес второго операнда формируется из двух компонентов, (esi)+(edx). Косвенная базовая индексная адресация со смещением Этот вид адресации является дополнением косвенной индексной адресации. Эффективный адрес формируется как сумма трех составляющих: содержимого базового регистра, содержимого индексного регистра и значения поля смеще- ния в команде. К примеру, команда mov eax,[esi+5][edx] пересылает в регистр еах двойное слово по адресу: (esi) + 5 + (edx). Команда add ax,array[esi][ebx] производит сложение содержимого регистра ах с содержимым слова по адресу: значение идентификатора array + (esi) + (ebx).
Функциональная классификация машинных команд 129 Функциональная классификация машинных команд Система команд микропроцессора содержит около 130 машинных команд. С появлением каждой новой модели микропроцессора их количество, как пра- вило, возрастает, отражая тем самым архитектурные нововведения, отличаю- щие эту модель от ее предшественниц. Набор машинных команд можно струк- турировать по группам и подгруппам. Очень полезно перед началом изучения работы отдельных команд получить общее представление о всей системе ко- манд микропроцессора (рис. 6.6). Рис. 6.6. Машинные команды и их функциональные группы Детальная информация о всех командах микропроцессора содержится в прило- жении 2. Это приложение, как и все приложения данной книги, предназначено для получения справки при практической работе. Поэтому не пытайтесь сейчас досконально разобраться со всей информацией, которая там приведена. Всему свое время! На этом урок 6 можно закончить. Начиная со следующего урока 7 мы присту- пим к практическим вопросам программирования на ассемблере. Подведем некоторые итоги: £1 Специфика работы программиста предполагает хорошее владение счетом в трех системах счисления: двоичной, шестнадцатеричной и десятичной. При этом программист должен достаточно бегло производить перевод чисел из одной системы счисления в другую.
130 Урок 6. Система команд микропроцессора 0 Числа со знаком представляются в компьютере особым образом: положитель- ные числа в виде обычного двоичного числа, а отрицательные — в дополни- тельном коде. 0 Структура команд микропроцессора позволяет обеспечить большую гибкость при обработке операндов и разнообразие режимов адресации.
УРОК Команды обмена данными □ Линейные алгоритмы □ Команды пересылки данных □ Ввод-вывод в порт □ Команды работы с адресами памяти □ Команды работы со стеком
Наверняка вы уже знакомы с понятием алгоритма, представляющего собой формализованное описание логики работы программы. Способы такой форма- лизации весьма разнятся: от использования бумаги с ручкой до блок-схем и развитых case-систем. Последовательность действий, описываемых алгоритмом, может быть: О линейной — все действия выполняются поочередно, друг за другом; О нелинейной — в алгоритме есть точки ветвления, в которых должно прини- маться решение о месте, с которого программа продолжит свое выполнение. Решение может носить условный или безусловный характер. Линейные участки алгоритма обычно содержат команды манипуляции данны- ми, вычисления значений выражений, преобразования данных. В точках ветв- ления размещают команды сравнения, различных видов перехода, вызова под- программ и некоторые другие. Обратимся к схеме на рис. 6.6, приведенной в конце урока 6. На ней показаны группы команд микропроцессора. Из всей их совокупности на линейных участ- ках работают следующие группы: О команды пересылки данных; О арифметические команды; О логические команд: i; О команды управления состоянием микропроцессора. На этом уроке мы рассмотрим только группу команд пересылки данных. Эти команды осуществляют пересылку данных из одного места в другое, запись и чтение информации из портов ввода/вывода, преобразование информации, ма- нипуляции с адресами и указателями, обращение к стеку. Для некоторых из этих команд операция пересылки является только частью алгоритма. Другая его часть выполняет некоторые дополнительные операции над пересылаемой информацией. Поэтому для удобства практического применения и отражения их специфики данные команды будут рассмотрены в соответствии с их функ- циональным назначением, согласно которому они делятся на команды: О пересылки данных: О ввода-вывода в порт;
Команды пересылки данных 133 о о о работы с адресами и указателями; преобразования данных; работы со стеком. Команды пересылки данных К этой группе относятся следующие команды: mov <операнд назначениям <операнд-источник> xchg <операнд1>,<операнд2> mov — это основная команда пересылки данных. Она реализует самые разнооб- разные варианты пересылки. Убедитесь в этом, посмотрев справочник команд в приложении 2. Отметим особенности применения этой команды: О командой mov нельзя осуществить пересылку из одной области памяти в другую. Если такая необходимость возникает, то нужно использовать в ка- честве промежуточного буфера любой доступный в данный момент регистр общего назначения. К примеру, рассмотрим фрагмент программы для пере- сылки байта из ячейки fIs в ячейку fid: masm model small .data fls db 5 fid db ? .code start: mov al,fls mov fid,al end start О нельзя загрузить в сегментный регистр значение непосредственно из памя- ти. Поэтому для выполнения такой загрузки нужно использовать промежу- точный объект. Это может быть регистр общего назначения или стек. Если вы посмотрите листинги 3.1 и 5.1, то увидите в начале сегмента кода две команды mov, выполняющие настройку сегментного регистра ds. При этом из- за невозможности загрузить впрямую в сегментный регистр значение адреса сегмента, содержащееся в предопределенной переменной ©data, приходится использовать регистр общего назначения ах. О нельзя переслать содержимое одного сегментного регистра в другой сегмент- ный регистр. Это объясняется тем, что в системе команд нет соответствую-
134 Урок 7. Команды обмена данными щего кода операции. Но необходимость в таком действии часто возникает. Выполнить такую пересылку можно, используя в качестве промежуточных все те же регистры общего назначения. Вот пример инициализации регистра es значением из регистра ds: mov ах, ds mov es.ax Но есть и другой, более красивый способ выполнения данной операции — использование стека и команд push и pop: push ds поместить значение регистра ds в стек pop es ;записать в es число из стека О нельзя использовать сегментный регистр cs в качестве операнда назначения. Причина здесь простая. Дело в том, что в архитектуре микропроцессора пара cs:ip всегда содержит адрес команды, которая должна выполняться следующей. Изменение командой mov содержимого регистра cs фактически означало бы операцию перехода, а не пересылки, что недопустимо. В связи с командой mov отметим один тонкий момент. Пусть в регистре Ьх со- держится адрес некоторого поля (то есть мы используем косвенную базовую адресацию). Его содержимое нужно переслать в регистр ах. Очевидно, что нуж- но применить команду mov в форме mov ах,[Ьх] Транслятор задает себе вопрос: что адресует регистр Ьх в памяти — слово или байт? Обычно в этом случае он принимает решение сам, по размеру большего операнда, но может и выдать предупреждающее сообщение о возможном несов- падении типов операндов. Или другой случай, возможно, более показательный. Команды инкремента inc (увеличения операнда на 1) и декремента dec (уменьшения операнда на 1): inc [Ьх] dec [Ьх] Что адресуется регистром Ьх в памяти — байт, слово или двойное слово? Допустим также, что требуется инициализировать поле, адресуемое Ьх, значени- ем 0. Очевидно, что одно из решений — применение mov: mov[bx],0 И опять у транслятора вопрос: какую машинную команду ему конструировать — для инициализации байта, слова или двойного слова? Во всех этих случаях необходимо уточнять тип используемых операндов. Для этого существует специальный оператор ассемблера ptr (см. урок 5). Правиль- но записать вышеприведенные команды можно следующим образом: mov ах,word ptr [Ьх] ;если [Ьх] адресует слово в памяти
Команды ввода-вывода в порт 135 inc byte ptr [bx] ; если [bx] адресует байт в памяти dec dword ptr [bx] ;если [bx] адресует двойное слово в памяти mov word ptr [bx],0 ;если [bx] адресует слово в памяти Можно рекомендовать использовать оператор ptr во всех сомнительных отно- сительно согласования размеров операндов случаях. Также этот оператор нуж- но применять, когда требуется принудительно поменять размерность операндов. К примеру, требуется переслать значение Offh во второй байт поля flp: masm model small .data flpdw 0 .code start: mov byte pt г (flp+1),Offh end start Несмотря на то, что поле flp имеет тип word, мы сообщаем ассемблеру, что поле нужно трактовать как байтовое, и заставляем вычислить значение эффек- тивного адреса второго операнда как смещение flp плюс единица. Тем самым мы получаем доступ ко второму байту поля flp. Для двунаправленной пересылки данных применяют команду xchg. Для этой операции можно, конечно, применить последовательность из нескольких команд mov, но из-за того, что операция обмена используется довольно часто, разработ- чики системы команд микропроцессора посчитали нужным ввести отдельную команду обмена xchg. Естественно, что операнды должны иметь один тип. Не допускается (как и для всех команд ассемблера) обменивать между собой со- держимое двух ячеек памяти. К примеру: xchg ах,Ьх ; обменять содержимое регистров ах и bx xchg ах,word ptr [si] ; обменять содержимое регистра ах ;и слова в памяти по адресу в [si] Команды ввода-вывода в порт На уроке 6 при обсуждении вопроса о том, где могут находиться операнды машинной команды, мы упоминали порт ввода-вывода. Напомню основные моменты. Каждое устройство ввода/вывода, каждое системное устройство име- ет один или несколько регистров, доступ к которым осуществляется через ад-
136 Урок 7. Команды обмена данными ресное пространство ввода/вывода. Эти регистры имеют разрядность 8, 16 или 32 бит. Адресное пространство ввода/вывода физически независимо от про- странства оперативной памяти и имеет ограниченный объем, составляющий 216, или 65 536 адресов ввода/вывода. Таким образом, понятие порта ввода-вывода можно определить как 8, 16 или 32-разрядный аппаратный регистр, имеющий определенный адрес в адресном пространстве ввода/вывода. Вся работа систе- мы с устройствами на самом низком уровне выполняется с использованием портов ввода-вывода. Посмотрите на рис. 7.1. На нем показана сильно упро- щенная, концептуальная схема управления оборудованием компьютера. Рис. 7.1. Концептуальная схема управления оборудованием компьютера Как видно из рис. 7.1, самым нижним уровнем является уровень BIOS, на кото- ром работа с оборудованием ведется напрямую через порты. Тем самым реали- зуется концепция независимости от оборудования. При замене оборудования необходимо будет лишь подправить соответствующие функции BIOS, пере- ориентировав их на новые адреса и логику работы портов. Принципиально управлять устройствами напрямую через порты несложно. Сведения о номерах портов, их разрядности, формате управляющей информа- ции приводятся в техническом описании на устройство. Необходимо знать лишь конечную цель своих действий, алгоритм, в соответствии с которым ра- ботает конкретное устройство, и порядок программирования его портов. То есть, фактически, нужно знать, что и в какой последовательности нужно по- слать в порт (при записи в него) или считать из него (при чтении) и как следу- ет трактовать эту информацию. Для этого достаточно всего двух команд, при- сутствующих в системе команд микропроцессора: in аккумулятор, номер_порта — ввод в аккумулятор из порта с номером номер_ порта; out порт, аккумулятор - вывод содержимого аккумулятора в порт с номером но- мер,. порта. Возможные значения операндов этих команд приведены, как обычно, в «Спра- вочнике команд* (см. приложение 2).
Команды ввода-вывода в порт 137 В качестве примера рассмотрим, как на уровне аппаратуры заставить компью- тер издавать звук сирены. Вначале мы перечислим, какие аппаратные ресурсы при этом будут задействованы и как ими надо управлять. Вам, наверное, известно, что у компьютера есть внутренний динамик. Как это ни удивительно, но специальной схемы генерации звука для него нет. Сигнал для управления динамиком формируется в результате совместной работы мик- росхем: О программируемого периферийного интерфейса (ППИ) 18255; О таймера 18253. Общая схема формирования такого сигнала показана на рис. 7.2. Рис. 7.2. Схема формирования звука для встроенного динамика Обсудим схему на рис. 7.2. Основная работа по генерации звука производится микросхемой таймера. Микросхема таймера (далее просто таймер) имеет три канала с совершенно одинаковыми внутренней структурой и принципом работы. На каналы таймера подаются импульсы от микросхемы системных часов, кото- рые, по сути, представляют собой генератор импульсов, работающий с частотой 1,19 МГц. Каждый канал имеет два входа и один выход. Выходы канала замк-
138 Урок 7. Команды обмена данными нуты на вполне определенные устройства компьютера. Так, канал 0 замкнут на контроллер прерываний и является источником аппаратного прерывания от таймера, которое возникает 18,2 раза в секунду. Канал 1 связан с микросхемой прямого доступа к памяти (DMA). И наконец, канал 2 выходит на динамик компьютера. Как мы отметили, каналы таймера имеют одинаковую структуру, основу которой составляют три регистра: регистр ввода-вывода разрядностью 8 бит, регистр-защелка (latch register) и регистр-счетчик, оба по 16 бит. Все регистры связаны между собой следующим образом. В регистр ввода-вывода извне помещается некоторое значение. Источником этого значения может быть либо системное программное обеспечение, либо программа пользователя. Каж- дый регистр ввода-вывода имеет адрес в адресном пространстве ввода-вывода (номер порта ввода-вывода). Регистр ввода-вывода канала 2 имеет номер порта ввода-вывода 42h. Помещаемые в него значения немедленно попадают в ре- гистр-защелку или, как его еще называют, регистр-фиксатор, где значение со- храняется до тех пор, пока в регистр ввода-вывода не будет записано новое значение. Но как, спросите вы, согласуются эти регистры по их разрядности, ведь один из них 8, а другой 16-разрядный? Для этого предназначен регистр управления (ему соответствует порт 43h), который является частью механизма управления всей микросхемой таймера. Он содержит слово состояния, с помо- щью которого производятся выбор канала, задание режима работы канала и тип операции передачи значения в канал. Слово состояния имеет структуру: О бит 0 определяет тип константы пересчета: 0 — константа задана двоичным числом, 1 — константа задана двоично-десятичным (BCD) числом. Кон- станта пересчета — значение, загружаемое извне в регистр-защелку; в на- шем случае загружаться будет двоичное число, поэтому значение этого поля будет 0; О биты 1-3 определяют режим работы микросхемы таймера. Всего можно определить шесть режимов, но обычно используется третий, поэтому для нашего случая значение поля — 011; О биты 4-5 определяют тип операции: 00 — передать значение счетчика в ре- гистр-задвижку (то есть возможна не только операция записи значения в канал, но и извлечения значения регистра-счетчика из него), 10 — записать в регистр-задвижку только старший байт, 01 — записать в регистр-задвижку только младший байт, И — записать в регистр-задвижку сначала старший байт, затем младший. В нашем случае значение поля будет И. Поэтому формирование 16-битного регистра-защелки через 8-битный регистр ввода- вывода производится следующим образом: запись производится в два приема, первый байт из регистра ввода-вывода записывается на место стар- шего байта регистра-защелки, второй байт — на место младшего байта. Не- трудно догадаться, что в регистр ввода-вывода эти байты помещаются ко- мандами in и out. О биты 6-7 определяют номер программируемого канала. В нашем случае они равны 10.
Команды ввода-вывода в порт 139 Для формирования любого звука необходимо задать его длительность и высо- ту. После того как значение из регистра ввода-вывода попало в регистр-защел- ку, оно моментально записывается в регистр-счетчик. Сразу же после этого значение регистра-счетчика начинает уменьшаться на единицу с приходом каждого импульса от системных часов. На выходе любого из трех каналов тай- мера стоит схема логического умножения. Эта схема имеет два входа и один выход. Значение регистра-счетчика участвует в формировании сигнала на од- ном из входов схемы логического умножения И. Сигнал на втором входе этой схемы зависит от состояния бита 0 регистра микросхемы интерфейса с пери- ферией (порт 61h). В свое время мы подробно разберемся с логическими опе- рациями, сейчас следует лишь пояснить, что единица на выходе схемы логи- ческого умножения может появиться только в одном случае — когда на обоих входах единицы. Когда значение в регистре-счетчике становится равным нулю, на соответствующем входе схемы И формируется такая единица. И если при этом на втором входе, значение которого зависит от бита 0 порта 61h, также 1, то импульс от системных часов проходит на выход канала 2. Одно- временно с пропуском импульса в канале 2 немедленно производится загруз- ка содержимого регистра-защелки (которое не изменилось, если его не изме- нили извне) в регистр-счетчик. Весь процесс с уменьшением содержимого регистра-счетчика повторяется заново. Теперь вы понимаете, что чем меньшее значение загружено в регистр-защелку, тем чаще будет обнуление регистра- счетчика и тем чаще импульсы будут проходить на выход канала 2. А это озна- чает, что высота звука будет выше. Понятно, что максимальное значение час- тоты на входе 1 динамика — 1,19 МГц. Таким образом импульс с выхода канала 2 попадает на динамик, и если на последний подан ток, то возникает долгожданный звук. Подачей тока на динамик управляет бит 1 порта 61h. Как прервать звучание? Очевидно, что для этого возможны два пути: первый — отключить ток, сбросив бит 1 порта 61h, второй — сбросить бит 0 порта 61h. Эти две возможности используют для создания различных звуковых эф- фектов. Выполняя эти разрывы, мы фактически определяем длительность звучания. Если вы внимательно следили за всеми рассуждениями, то, наверное, без труда сможете понять, почему первый канал таймера формирует сигналы аппаратно- го прерывания от таймера 18,2 раза в секунду (на основании этих сигналов программы отслеживают время). Для этого BIOS во время загрузки после включения компьютера загружает в первый канал соответствующее значение. Таким образом, наметились три последовательных действия, необходимые для программирования звукового канала таймера (они применимы и к остальным каналам): 1. Посредством порта 43h выбрать канал, задать режим работы и тип опера- ции передачи значения в канал. В нашем случае соответствующее значение будет равно 10110110 = 0b6h. 2. Подать ток на динамик, установив бит 1 порта 61h.
140 Урок 7. Команды обмена данными 3. Используя регистр ах, поместить нужное значение в порт 42h, определив тем самым нужную высоту тона. Ниже в листинге 7.1 приведена программа, реализующая звук сирены. Многие команды вам уже знакомы, некоторые мы пока еще не рассматривали, поэтому поясним их функциональное назначение. Подробно они будут рассмотрены в свое время. Для удобства работы в программе была использована макрокоман- да delay, выполняющая задержку работы программы на заданное время. Не стоит пока пытаться разбираться с механизмом макроподстановок — этим мы также займемся в свое время. Сейчас только отметьте для себя, что введенная таким образом макрокоманда в тексте программы синтаксически ничем не от- личается от других команд ассемблера и это позволяет программисту, при не- обходимости, расширить стандартный набор команд ассемблера. Введите текст макрокоманды delay (строки 11-25) и воспринимайте ее чисто по функцио- нальному назначению (задержка выполнения программы на промежуток време- ни, задаваемый значением ее операнда). Стоит отметить, что данная макро- команда чувствительна к производительности микропроцессора, из-за чего звуки на компьютерах с разными моделями микропроцессоров могут не совпа- дать. Сегмент кода, как обычно, начинается с настройки сегментного регистра ds на начало сегмента данных (строки 32-33). После этого строками 37-38 мы выполняем действия первого этапа — настройку канала 2, которая заключается в записи в регистр управления (порт 43h) байта состояния 0B6h. На втором шаге мы должны установить биты 0 и 1 порта 61h. Предварительно необходи- мо извлечь содержимое этого порта. Это делается для того, чтобы выполнять установку бит 0 и 1, не изменяя содержимого остальных бит порта 61h (строки 39-41). Принцип формирования сигнала сирены заключается в том, что в цик- ле на единицу изменяется содержимое регистра счетчика и делается небольшая задержка для того, чтобы сигнал некоторое время звучал с нужной высотой. Постепенное повышение, а затем понижение высоты и дает нам эффект сирены. Строки 43-53 соответствуют циклу, в теле которого высота повышается, а строки 55-62 — циклу понижения тона. Оба цикла повторяются последова- тельно 5 раз. Контроль за этим осуществляется с помощью переменной ent, содержимое которой увеличивается на 1 в строке 69 и контролируется на ра- венство 5 в строках 71-72. Если ent = 5, то команда стр устанавливает опреде- ленные флаги. Последующая команда условного перехода jne анализирует эти флаги и в зависимости от их состояния передает управление либо на метку, указанную в качестве операнда этой команды, либо на следующую за jne ко- манду. Цикл в программе ассемблера можно организовать несколькими спосо- бами; все они будут подробно разобраны на уроке 9. В данном случае цикл организуется командой loop, которая в качестве операнда имеет имя метки. На эту метку и передается управление при выполнении команды loop. Но до того как передать управление, команда loop анализирует содержимое регистра есх/сх, и, если оно равно нулю, управление передается не на метку, а на следу- ющую за loop команду. Если содержимое регистра есх/сх не равно нулю, то оно уменьшается на единицу и управление передается на метку. Таким образом
Команды ввода-вывода в порт 141 в есх/сх хранится счетчик цикла. В нашей программе он загружается в есх/сх в строках 42 и 54. Листинг 7.1. Сирена < 1> ;----Prg_7_1.asm------ < 2> ; Программа, имитирующая звук сирены. < з> ; Изменение высоты звука от 450 Гц до 2100 Гц. < 4> ;Используется макрос delay (задержка). < 5> ;При необходимости < б> ;можно поменять значение задержки (по умолчанию - для процессора Pentium). < 7> ;------------------ <8> masm <9> model small <10> stack 100h <11> delay macro time <12> ;макрос задержки, его текст ограничивается директивами macro и endm. <13> ; На входе - значение задержки (в мкс) <14> local ext,iter <15> push ex <1б> mov ex,time <17> ext: <18> push ex <19> mov ex,5000 <20> iter: <21> loop iter <22> pop ex <23> loop ext <24> pop ex <25> endm <2б> .data ;сегмент данных <27> tonelow dw 2651 ;нижняя граница звучания = 450 Гц <28> ent db 0 ;счётчик для выхода из программы <29> temp dw ? ;верхняя граница звучания <30> .code ;сегмент кода <31> main: ;точка входа в программу <32> mov ах,©data ;связываем регистр ds с сегментом <33> mov ds,ах ;данных через регистр ах <34> хог ах,ах ;очищаем ах продолжение &
142 Урок 7. Команды обмена данными <35> до: <3б> ;заносим слово состояния 10110110b(0B6h) в командный регистр (порт 43h) <37> mov al,0B6h <38> out 43h,al <39> in al,61h ; получим значение порта 61h в al <40> or al, 3 ; инициализируем динамик и подаем ток в порт 61 h <41> out 61h,al <42> mov ex, 2083 ; количество шагов ступенчатого изменения тона <43> musicup: <44> ;в ах значение нижней границы частоты <45> mov ax.tonelow <4б> out 42h, al; в порт 42h младшее слово ах : 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> musicdown: <5б> mov ах,temp ;в ах верхнее значение высоты <57> out 42h, al ; в порт 42h младшее слово ах :а1 <58> mov al,ah ; обмен между al и ah <59> out 42h, al ; в порт 42h старшее слово ах :ah <6О> sub temp,1 ; понижаем высоту <б1> delay 1 ;задержка на 1 мкс <62> loop musicdown ; повторить цикл понижения <63> nosound: <б4> in al, 61h ; получим значение порта 61h в AL <б5> and al,OFCh ; выключить динамик <бб> out 61h,al ;впорт61Ь <67> mov dx,2651 ; для последующих циклов <б8> mov tonelow,dx <б9> inc ent ; увеличиваем счётчик проходов, то есть <70> ; количество звучаний сирены <71> cmp ent,5 ;5 раз ?
Команды работы с адресами и указателями 143 <72> jne go ; если нет, идти на метку до <73> exit: <74> mov ax,4c00h ;стандартный выход <75> int 21h <7б> end main ; конец программы Команды работы с адресами и указателями При написании программ на ассемблере производится интенсивная работа с ад- ресами операндов, находящимися в памяти. Для поддержки такого рода опе- раций есть специальная группа команд, в которую входят следующие команды: lea назначение, источник — загрузка эффективного адреса; Ids назначение, источник — загрузка указателя в регистр сегмента данных ds; les назначение, источник — загрузка указателя в регистр дополнительного сегмента данных es; Igs назначение, источник — загрузка указателя в регистр дополнительного сегмента данных gs; Ifs назначение, источник — загрузка указателя в регистр дополнительного сегмента данных fs; Iss назначение, источник — загрузка указателя в регистр сегмента стека ss. Команда lea похожа на команду mov тем, что она также производит пересылку. Однако, обратите внимание, команда lea производит пересылку не данных, а эффективного адреса данных (то есть смещения данных относительно начала сегмента данных) в регистр, указанный операндом назначение. Часто для выполнения некоторых действий в программе недостаточно знать значение одного лишь эффективного адреса данных, а необходимо иметь пол- ный указатель на данные. Вы помните, что полный указатель на данные состо- ит из сегментной составляющей и смещения. Все остальные команды этой группы позволяют получить в паре регистров такой полный указатель на опе- ранд в памяти. При этом имя сегментного регистра, в который помещается сег- ментная составляющая адреса, определяется кодом операции. Соответственно, смещение помещается в регистр общего назначения, указанный операндом на- значение. Но не все так просто с операндом источник. На самом деле в команде в качестве источника нельзя указывать непосредственно имя операнда в памя- ти, на который мы бы хотели получить указатель. Предварительно необходимо получить само значение полного указателя в некоторой области памяти и ука- зать в команде получения полного адреса имя этой области. Для выполнения этого действия необходимо вспомнить директивы резервирования и инициали- зации памяти (см. урок 5). При применении этих директив возможен частный случай, когда в поле операндов указывается имя другой директивы определе- ния данных (фактически, имя переменной). В этом случае в памяти формиру-
144 Урок 7. Команды обмена данными ется адрес этой переменной. Какой адрес будет сформирован (эффективный или полный), зависит от применяемой директивы. Если это dw, то в памяти форми- руется только 16-битное значение эффективного адреса, если же dd - в память записывается полный адрес. Размещение этого адреса в памяти следую- щее: в младшем слове находится смещение, в старшем — 16-битная сегментная составляющая адреса. Посмотрите на листинг 5.2 и рис. 5.18. В сегменте дан- ных программы листинга 5.2 переменные ad г и adr_full иллюстрируют наш случай получения частичного и полного указателей на данные в памяти. Например, при организации работы с цепочкой символов удобно поместить ее начальный адрес в некоторый регистр и далее в цикле модифицировать это значение для последовательного доступа к элементам цепочки. В листинге 7.2 производится копирование строки байт str_1 в строку байт str_2. В строках 12 и 13 в регистры si и di загружаются значения эффективных адресов переменных str_1 и str_2. В строках 16, 17 производится пересылка очередного байта из одной строки в другую. Указатели на позиции байтов в строках определяются содержимым регистров si и di. Для пересылки очередного байта необходимо увеличить на единицу регистры si и di, что и делается командами сложения inc (строки 18, 19). После этого программу необходимо зациклить до обработки всех символов строки. Листинг 7.2. Копирование строки < 1> ;----Prg_7_2.asm------- < 2> tnastn < з> model small < 4> .data < 5> < б> str_1 db "Ассемблер - базовый язык компьютера” < 7> str_2 db 50 dup (" ") < e> full_pnt dd str_1 < 9> <10> .code <11> start: <12> <13> lea si,str_1 <14> lea di,str_2 <15> les bx.full-pnt ; полный указатель на str1 в пару es:bx <16> ml: <17> mov al,[si] <18> mov [di],al <19> inc si <20> inc di
Команды преобразования данных 145 <21> ;цикл на метку ml до пересылки всех символов <22> <2з> end start Необходимость использования команд получения полного указателя данных в памяти, то есть адреса сегмента и значения смещения внутри сегмента, возни- кает, в частности, при работе с цепочками. Мы рассмотрим этот вопрос на уро- ке 11. В строке 8 листинга 7.2 в двойном слове full_pnt формируются сегмент- ная часть адреса и смещение для переменной str_1. При этом 2 байта смещения занимает младшее слово full_pnt, а значение сегментной составляющей адреса — старшее слово full_pnt. В строке 15 командой les эти компоненты адреса поме- щаются в регистры Ьх и es. Команды преобразования данных К этой группе можно отнести множество команд микропроцессора, но боль- шинство из них имеют те или иные особенности, которые требуют отнести их к другим функциональным группам (рис 6.6). Поэтому из всей совокупности ко- манд микропроцессора непосредственно к командам преобразования данных можно отнести только одну команду: xlat [адрес_таблицы_перекодировки] Это очень интересная и полезная команда. Ее действие заключается в том, что она замещает значение в регистре al другим байтом из таблицы в памяти, рас- положенной по адресу, указанному операндом адрес_таблицы_перекодировки. Слово «таблица» весьма условно; по сути, это просто строка байт. Адрес байта в строке, которым будет производиться замещение содержимого регистра al, определяется суммой (bx) + (al), то есть содержимое al выполняет роль ин- декса в байтовом массиве. При работе с командой xlat обратите внимание на следующий тонкий момент. Несмотря на то, что в команде указывается адрес строки байт, из которой должно быть извлечено новое значение, этот адрес должен быть предварительно загружен (например, с помощью команды lea) в регистр Ьх. Таким образом, операнд адрес_таблицы_перекодировки на самом деле не нужен (необязательность операнда показана заключением его в квадратные скобки). Что касается строки байт (таблицы перекодировки), то она представляет собой область памяти раз- мером от 1 до 255 байт (диапазон числа без знака в 8-битном регистре). В качестве иллюстрации работы данной команды мы рассмотрим программу из листинга 3.1. Вы помните, что эта программа преобразовывала двузначное шестнадцатеричное число, вводимое с клавиатуры (то есть в символьном виде), в эквивалентное двоичное представление в регистре al. Ниже (листинг 7.2) приведен вариант этой программы с использованием команды xlat.
146 Урок 7. Команды обмена данными Листинг 7.3. Использование таблицы перекодировки <1> ; Prg_7_3.asm <2> ; Программа преобразования двузначного шестнадцатеричного числа <3> ;в двоичное представление с использованием команды xlat. <4> ;Вход: исходное шестнадцатеричное число; вводится с клавиатуры. <5> ; Выход: результат преобразования в регистре аЪ <б> .data ;сегмент данных <7> message db "Введите две шестнадцатеричные цифры, $" <8> tabl db 48 dup (0),0,1,2,3,4,5,6,7,8,9, 8 dup (0), <9> db Oah,Obh,Och,odh,Oeh,Ofh,27 dup (0) <10> db Oah, Obh, Och,odh,Oeh,Ofh, 153 dup (0) <11> . stack 256 ; сегмент стека <12> .code <13> ;начало сегмента кода <14> proc main ; начало процедуры main <15> mov ax, ©data ; физический адрес сегмента данных в регистр ах <1б> mov ds,ах ;ax записываем в ds <17> lea bx,tabl ; загрузка адреса строки байт в регистр Ьх <18> mov ah, 9 <19> mov dx,offset message <20> int 21h ; вывести приглашение к вводу <21> xor ах, ах ; очистить регистр ах <22> mov ah,1h ; значение 1h в регистр ah <23> int 21h ; вводим первую цифру в al <24> xlat ; перекодировка первого введенного символа в al <25> mov dl.al <2б> shl dl,4 ;сдвиг dl влево для освобождения места для младшей ;цифры <27> int 21h ;ввод второго символа в al <28> xlat перекодировка второго введенного символа в al <29> add al.dl ;складываем для получения результата <30> mov ax,4c00h ;пересылка 4c00h в регистр ах <31> int 21h ;завершение программы <32> endp main ;конец процедуры main <33> code ends ;конец сегмента кода <34> end main ;конец программы с точкой входа main Сама по себе программа проста; сложность вызывает обычно формирование таблицы перекодировки. Обсудим этот момент подробнее. Прежде всего нужно
Команды работы со стеком 147 определиться со значениями тех байтов, которые вы будете изменять. В нашем случае это символы шестнадцатеричных цифр. На уроке 3 мы рассматривали их коды ASCII. Поэтому мы конструируем в сегменте данных таблицу, в кото- рой на места байтов, соответствующих символам шестнадцатеричных цифр, по- мещаем их новые значения, то есть двоичные эквиваленты шестнадцатеричных цифр. Строки 8-10 листинга 7.3 демонстрируют, как это сделать. Байты этой таблицы, смещения которых не совпадают со значением кодов шест- надцатеричных цифр, нулевые. Таковыми являются первые 48 байт таблицы, промежуточные байты и часть в конце таблицы. Желательно определить все 256 байт таблицы. Дело в том, что если мы ошибочно поместим в al код сим- вола, отличный от символа шестнадцатеричной цифры, то после выполнения команды xlat получим непредсказуемый результат. В случае листинга 7.3 это будет ноль, что не совсем корректно, так как непонятно, что же в действитель- ности было в al: код символа «0» или что-то другое. Поэтому, наверное, есть смысл здесь поставить -«защиту от дурака*, поместив в неиспользуемые байты таблицы какой-нибудь определенный символ. После каждого выполнения xlat нужно будет просто контролировать значение в al на предмет совпадения с этим символом, и если оно произошло, выдавать сообщение об ошибке. После того как таблица составлена, с ней можно работать. В сегменте команд строка 17 инициализирует регистр Ьх значением адреса таблицы tabl. Далее все очень просто. Поочередно вводятся символы двух шестнадцатеричных цифр и производится их перекодировка в соответствующие двоичные эквиваленты. В остальном программа аналогична листингу 3.1. Для закрепления знаний и исследования трудных моментов выполните програм- му из листинга 7.2 под управлением отладчика. Команды работы со стеком Эта группа представляет собой набор специализированных команд, ориентиро- ванных на организацию гибкой и эффективной работы со стеком. Стек — это область памяти, специально выделяемая для временного хранения данных про- граммы. Важность стека определяется тем, что для него в структуре программы предусмотрен отдельный сегмент. На тот случай, если программист забыл опи- сать сегмент стека в своей программе, компоновщик tlink выдаст предупреж- дающее сообщение. Для работы со стеком предназначены три регистра: О ss — сегментный регистр стека; О sp/esp — регистр указателя стека; О bp/ebp — регистр указателя базы кадра стека. Размер стека зависит от режима работы микропроцессора и ограничивается 64 Кбайт (или 4 Гбайт в защищенном режиме). В каждый момент времени доступен только один стек, адрес сегмента которого содержится в регистре ss.
148 Урок 7. Команды обмена данными Этот стек называется текущим. Для того чтобы обратиться к другому стеку («пе- реключить стек»), необходимо загрузить в регистр ss другой адрес. Регистр ss автоматически используется процессором для выполнения всех команд, работаю- щих со стеком. Перечислим еще некоторые особенности работы со стеком: О запись и чтение данных в стеке осуществляется в соответствии с принципом LIFO (Last In First Out — «последним пришел, первым ушел»); О по мере записи данных в стек последний растет в сторону младших адресов. Эта особенность заложена в алгоритм команд работы со стеком; О при использовании регистров esp/sp и ebp/bp для адресации памяти ассемб- лер автоматически считает, что содержащиеся в нем значения представляют собой смещения относительно сегментного регистра ss. В общем случае стек организован так, как показано на рис. 7.3. Оперативная память 0000:0000 Сегмент кода Сегмент данных Сегмент стека ---> Вершина стека Т Направление I роста стека Дно стека Старшие адреса ОП Рис. 7.3. Концептуальная схема организации стека Для работы со стеком предназначены регистры ss, esp/sp и ebp/bp1. Эти регист- ры используются комплексно, и каждый из них имеет свое функциональное назначение. Регистр esp/sp всегда указывает на вершину стека, то есть содер- жит смещение, по которому в стек был занесен последний элемент. Команды работы со стеком неявно изменяют этот регистр так, чтобы он указывал всегда на последний записанный в стек элемент. Если стек пуст, то значение esp равно адресу последнего байта сегмента, выделенного под стек. При занесении эле- мента в стек процессор уменьшает значение регистра esp, а затем записывает элемент по адресу новой вершины. При извлечении данных из стека процессор копирует элемент, расположенный по адресу вершины, а затем увеличивает значение регистра указателя стека esp. Таким образом получается, что стек растет вниз, в сторону уменьшения адресов. 1 Какой из регистров применяется для адресации, 32-битный или 16-битный, зависит от значения модификатора use 16 или use32 в директиве сегментации segment (см. урок 6).
Команды работы со стеком 149 Что делать, если нам необходимо получить доступ к элементам не на вершине, а внутри стека? Для этого применяют регистр ebp. Регистр ebp — регистр ука- зателя базы кадра стека. Например, типичным приемом при входе в подпро- грамму является передача нужных параметров путем записи их в стек. Если подпрограмма тоже активно работает со стеком, то доступ к этим параметрам становится проблематичным. Выход в том, чтобы после записи нужных данных в стек сохранить адрес вершины стека в указателе кадра (базы) стека — ре- гистре ebp. Значение в ebp в дальнейшем можно использовать для доступа к пе- реданным параметрам. Начало стека расположено в старших адресах памяти. На рис. 7.3 этот адрес обо- значен парой ss: ffff. Смещение ffff приведено здесь условно. Реально это зна- чение определяется величиной, которую программист задает при описании сег- мента стека в своей программе. К примеру, для программы в листинге 7.1 началу стека будет соответствовать пара ss:0100h. Адресная пара ss:ffff — это максимальное для реального режима значение адреса начала стека, так как раз- мер сегмента в нем ограничен величиной 64 Кбайт (Offffh). Для организации работы со стеком существуют специальные команды записи и чтения. push источник — запись значения источник в вершину стека. Интерес представляет алгоритм работы этой команды, который включает следу- ющие действия (рис. 7.4): О (sp) = (sp) - 2; значение sp уменьшается на 2; О значение из источника записывается по адресу, указываемому парой ss: sp. Оперативная память Оперативная память Рис. 7.4. Принцип работы команды push pop назначение — запись значения из вершины стека по месту, указанному опе- рандом назначение. Значение при этом «снимается» с вершины стека.
150 Урок 7. Команды обмена данными Алгоритм работы команды pop обратен алгоритму команды push (рис. 7.5): О запись содержимого вершины стека по месту, указанному операндом назначе- ние; О (sp) = (sp) + 2; увеличение значения sp. Оперативная память Оперативная память назначение Рис. 7.5. Принцип работы команды pop pusha — команда групповой записи в стек. По этой команде в стек последова- тельно записываются регистры ах, ex, dx, bx, sp, bp, si, di. Заметим, что записывается оригинальное содержимое sp, то есть то, которое было до выдачи команды pusha (рис. 7.6). Оперативная память Оперативная память Старшие адреса ОП Стек до команды pusha Старшие адреса ОП Стек после команды pusha Рис. 7.6. Принцип работы команды pusha
Команды работы со стеком 151 pushaw — почти синоним команды pusha. В чем разница? На уроке 5 мы обсуждали один из атрибутов сегмента — атри- бут разрядности. Он может принимать значение use16 или use32. Рассмотрим работу команд pusha и pushaw при каждом из этих атрибутов: О use16 — алгоритм работы pushaw аналогичен алгоритму pusha; О use32 — pushaw не изменяется (то есть она нечувствительна к разрядности сегмента и всегда работает с регистрами размером в слово — ах, ex, dx, Ьх, sp, bp, si, di). Команда pusha чувствительна к установленной разрядности сегмента и при указании 32-разрядного сегмента работает с соответст- вующими 32-разрядными регистрами, то есть eax, есх, edx, ebx, esp, ebp, esi, edi. pushad — выполняется аналогично команде pusha, но есть некоторые особенности, которые вы можете узнать из «Справочника команд» (см. приложе-ние 2). Следующие три команды выполняют действия, обратные вышеописанным ко- мандам: О рора; О popaw; О popad. Группа команд, описанная ниже, позволяет сохранить в стеке регистр флагов и записать слово или двойное слово в стеке. Отметим, что перечисленные ниже команды — единственные в системе команд микропроцессора, которые позволя- ют получить доступ (и которые нуждаются в этом доступе) ко всему содержи- мому регистра флагов. pushf — сохраняет регистр флагов в стеке. Работа этой команды зависит от атрибута размера сегмента: use16 — в стек записывается регистр flags, размером 2 байта; use32 — в стек записывается регистр eflags, размером 4 байта; pushfw — сохраняет в стеке регистр флагов размером в слово. Всегда работает как pushf с атрибутом use16; pushfd — сохраняет в стеке регистр флагов flags или eflags в зависимости от атрибута разрядности сегмента (то есть то же, что и pushf). Аналогично, следующие три команды выполняют действия, обратные рассмот- ренным выше операциям: О popf; О popfw; О popfd.
152 Урок 7. Команды обмена данными Работать со стеком приходится постоянно, поэтому к этому вопросу мы будем возвращаться еще не раз. Отметим основные виды операций, когда использова- ние стека практически неизбежно: О вызов подпрограмм; О временное сохранение значений регистров; О определение локальных переменных. Подведем некоторые итоги: 0 Основная команда пересылки данных — mov. Операнды этой команды (как и большинства других команд, берущих значения из памяти) должны быть согласованы по разрядности. Несмотря на то, что в некоторых случаях дей- ствуют правила умолчания, в сомнительных ситуациях лучше явно указы- вать разрядность операндов с помощью оператора pt г. И Управление периферией компьютера осуществляется, в общем случае, с ис- пользованием всего двух команд ввода/вывода, in и out. El В процессе работы программы динамически можно получить как эффектив- ный, так и полный (физический) адрес памяти. Для этого язык ассемблера предоставляет группу команд получения указателей памяти. 0 Архитектура микропроцессора предоставляет в распоряжение программиста специфическую, но весьма эффективную структуру — стек. Система команд поддерживает все необходимые операции со стеком. Подробнее со стеком мы познакомимся при изучении модульного программирования на ассемб- лере.
УРОК Арифметические команды □ Форматы арифметических данных □ Арифметические операции над двоичными числами □ Арифметические операции над десятичными (BCD) числами
На уроке 1 при обсуждении истории компьютеров мы упомянули причину, побудившую человека искать себе помощника. Вспомнили? Совершенно вер- но, — это было желание эффективно решать счетные задачи. Правда, путь к собственно эффективному решению оказался несколько дольше, чем хотелось бы, но, тем не менее, именно благодаря этому стремлению человечество имеет сегодня определенные достижения. Любой компьютер, от самого примитивного до супермощного, имеет в своей системе команд команды для выполнения арифметических действий. Работая с компьютером при помощи языков высо- кого уровня, мы воспринимаем возможность проведения расчетных действий как должное, забывая при этом, что компилятор даже очень развитого языка программирования превращает все самые высокоуровневые действия в унылую последовательность машинных команд. Конечно, мало кому придет в голову писать серьезную расчетную задачу на ассемблере. Но даже в системных про- граммах часто требуется проведение небольших вычислений. Поэтому важно разобраться с этой группой команд. К тому же она на удивление очень компак- тна и не избыточна. Микропроцессор может выполнять целочисленные операции и операции с пла- вающей точкой. Для этого в его архитектуре есть два отдельных блока: О устройство для выполнения целочисленных операций; О устройство с плавающей точкой. Каждое из этих устройств имеет свою систему команд. В принципе, целочис- ленное устройство может взять на себя многие функции устройства с плаваю- щей точкой, но это потребует больших вычислительных затрат. Мы не будем рассматривать устройство с плавающей точкой и его систему команд — это тема отдельного большого разговора. Для большинства задач, использующих язык ассемблера, достаточно целочисленной арифметики. Обзор группы арифметических команд и данных Целочисленное вычислительное устройство поддерживает чуть больше десятка арифметических команд. На рис. 8.1 приведена классификация команд этой группы.
Обзор группы арифметических команд и данных 155 Преобразования типов - cbw - cwd - cwde - cdq - movsx - movzx Целочисленные арифметические команды I " Двоичной арифметики Сложения -add -adc -inc Вычитания Десятичной арифметики Коррекция сложения Еааа daa -sub -sbb -dec Коррекция вычитания -aas -das Умножения -imul -mul Коррекция умножения Laam Деления -idiv -div Коррекция деления Laad Изменения знака L-neg Рис. 8.1. Классификация арифметических команд Группа арифметических целочисленных команд работает с двумя типами чисел: О целыми двоичными числами. Числа могут иметь знаковый разряд или не иметь такового, то есть быть числами со знаком или без знака; О целыми десятичными числами. На уроке 2 мы обсуждали вопрос о данных, с которыми работают арифмети- ческие команды. Вспомним некоторые ключевые моменты. Целые двоичные числа Целое двоичное число с фиксированной точкой — это число, закодированное в двоичной системе счисления. Размерность целого двоичного числа может со- ставлять 8, 16 или 32 бит. Знак двоичного числа определяется тем, как интер- претируется старший бит в представлении числа. Это 7-й, 15-й или 31-й биты для чисел соответствующей размерности (см. урок 2). При этом интересно то, что среди арифметических команд есть всего две команды, которые действи- тельно учитывают этот старший разряд как знаковый, — это команды целочис- ленного умножения и деления imul и idiv. В остальных случаях ответствен- ность за действия со знаковыми числами и, соответственно, со знаковым разрядом ложится на программиста. К этому вопросу мы вернемся чуть позже.
156 Урок 8. Арифметические команды Диапазон значений двоичного числа зависит от его размера и трактовки старше- го бита либо как старшего значащего бита числа, либо как бита знака числа (табл. 8.1). Таблица 8.1. Диапазон значений двоичных чисел Размерность поля Целое без знака Целое со знаком Байт Слово Двойное слово 0...255 -128...+127 0...65535 -32 768...+32 767 0...4 294 967 295 -2 147 483 648...+2 147 483 647 Как описать числа с фиксированной точкой в программе? Это делается с ис- пользованием директив описания данных, рассмотренных на уроке 6. В прило- жении 6 описаны возможные варианты содержимого полей операндов этих директив и диапазоны их значений. К примеру, последовательность описаний двоичных чисел из сегмента данных листинга 8.1 (помните о принципе «млад- ший байт по младшему адресу») будет выглядеть в памяти так, как показано на рис. 8.2. a^o 3 ESI а| [READ E File Edit ie* Run reakpoxnts Data ptionз xndow Hel -----Module: prg_8_l File: C:\TASM\WORK\prg_8_l.asm 15----------------1- ;prg_8_l.asm masm model < stack 1 .data , per_l < per_2 < per_3 ( per_4 < .code , main: mov mov exit: * mov int end main small 256 ;сегмент данных db dw dd dw ;сегмент кода ;точка входа в программу г ax,@data ;связываем регистр dx с сегментом г ds,ах ;данных через регистр ах ;посмотрите в отладчике дамп сегмента данных г ax,4c00h ;стандартный выход : 21h ;конец программы 23 9056 9875645 29857 — [BJ—Dump 3- [ f ] [ | ] ds:0000 17 80 26 BD BO 96 DO Al jA&J#U 6 i ds:0008 74 00 00 00 00 00 00 00 t * ds:0010 00 00 00 00 00 00 00 00 ds:0018 00 00 00 00 00 00 00 00 —mtcnes....-.........-.....—...-............... -..—...1..... j . ___7..................................................... I ►Help К -Bkpt *3-44od F4-Here F5~Eoom F6-Hext F'-Trac Step F9-Run F12-Menu Puc. 8.2. Дамп памяти для сегмента данных листинга 8.1
Обзор группы арифметических команд и данных 157 Листинг 8.1. Числа с фиксированной точкой :prg_8_1. asm masm model small stack 256 .data ; сегмент данных рег_1 db 23 per_2 dw 9856 per_3 dd 9875645 per_4 dw 29857 .code ; сегмент кода main: ; точка входа в программу mov ax,@data ; связываем регистр dx с сегментом mov ds,ах ; данных через регистр ах exit: ; посмотрите в отладчике дамп сегмента данных mov ax,4c00h стандартный выход int 21h end main ; конец программы Десятичные числа Десятичные числа — специальный вид представления числовой информации, в основу которого положен принцип кодирования каждой десятичной цифры числа группой из четырех бит. При этом каждый байт числа содержит одну или две десятичные цифры в так называемом двоично-десятичном коде (BCD - Binary-Coded Decimal). Микропроцессор хранит BCD-числа в двух форматах (рис. 8.3): О упакованном формате — в этом формате каждый байт содержит две деся- тичные цифры. Десятичная цифра представляет собой двоичное значение в диапазоне от 0 до 9 размером 4 бита. При этом код старшей цифры числа занимает старшие 4 бита. Следовательно, диапазон представления десятич- ного упакованного числа в одном байте составляет от 00 до 99; О неупакованном формате — в этом формате каждый байт содержит одну де- сятичную цифру в четырех младших битах. Старшие четыре бита имеют нулевое значение. Это так называемая зона. Следовательно, диапазон пред- ставления десятичного неупакованного числа в одном байте составляет от 0 до 9.
158 Урок 8. Арифметические команды 2i® ®£ Упакованное десятичное число 5674304: 0,5 6,7 4,3 0,4 0000 0101 I 0110^111 0100 001- 0000 0100 I Рис. 8.3. Представление BCD-чисел Как описать двоично-десятичные числа в программе? Для этого можно исполь- зовать только две директивы описания и инициализации данных — db и dt (см. приложение 6). Возможность применения только этих директив для опи- сания BCD-чисел обусловлена тем, что к таким числам также применим прин- цип «младший байт по младшему адресу», что, как мы увидим далее, очень удобно для их обработки. И вообще, при использовании такого типа данных, как BCD-числа, порядок описания этих чисел в программе и алгоритм их обра- ботки — это дело вкуса и личных пристрастий программиста. Это станет более ясным после того, как мы ниже рассмотрим основы работы с BCD-числами. К примеру, приведенная в сегменте данных листинга 8.2 последовательность описаний BCD-чисел будет выглядеть в памяти так, как показано на рис. 8.4. Листинг 8.2. BCD-числа ;prg_8_2.asm masm model small stack 256 .data ; сегмент данных per_1 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 21 h end main ; конец программы
Арифметические операции над целыми двоичными числами 159 TD ГП |l-] a| Мд а| I’-jjjja.jr-i ,'т1 туя."’.. 'J ’«д-р.ц i г,——— Д Пу ск | В Window Com, | ЗУ Microsoft Wor | Q CorelDRAW 7 | R| Ri$8J)2 -Пр.. |||й TD Z 10 Л б Рис. 8.4. Дамп памяти для сегмента данных листинга 82 После столь подробного обсуждения объектов, с которыми работают арифмети- ческие операции, можно приступить к рассмотрению средств их обработки на уровне системы команд микропроцессора. Арифметические операции над целыми двоичными числами В данном разделе мы рассмотрим особенности каждого из четырех основных арифметических действий для двоичных чисел со знаком и без знака. Сложение двоичных чисел без знака Микропроцессор выполняет сложение операндов по правилам сложения двоич- ных чисел. Проблем не возникает до тех пор, пока значение результата не превы- шает размерности поля операнда (см. табл. 8.1). Например, при сложении опе- рандов размером в байт результат не должен превышать число 255. Если это
160 Урок 8. Арифметические команды происходит, то результат оказывается неверен. Рассмотрим, почему так про- исходит. К примеру, выполним сложение: 254 + 5 = 259 в двоичном виде. 11111110 + 0000101 = 1 00000011. Результат вышел за пределы восьми бит, и правильное его значение укладывается в 9 бит, а в 8-битовом поле операнда ос- талось значение 3, что, конечно, неверно. В микропроцессоре этот исход сложе- ния прогнозируется, и предусмотрены специальные средства для фиксирования подобных ситуаций и их обработки. Так, для фиксирования ситуации выхода за разрядную сетку результата, как в данном случае, предназначен флаг переноса cf. Он располагается в бите 0 регистра флагов ef lags/f lags. Именно установкой это- го флага фиксируется факт переноса единицы из старшего разряда операнда. Ес- тественно, что программист должен предусматривать возможность такого исхода операции сложения и средства для корректировки. Это предполагает включение участков кода после операции сложения, в которых анализируется флаг cf. Ана- лиз этого флага можно провести различными способами. Самый простой и дос- тупный — использовать команду условного перехода jc. Эта команда в качестве операнда имеет имя метки в текущем сегменте кода. Переход на эту метку осу- ществляется в случае, если в результате работы предыдущей команды флаг cf установился в 1. Команды условных переходов будут рассматриваться на уро- ке 10. Если теперь посмотреть на рис. 8.1, то видно, что в системе команд микропро- цессора имеется три команды двоичного сложения: О inc операнд - операция инкремента, то есть увеличения значения операнда на 1; О add операнд^ 1,операнд_2 - команда сложения с принципом действия: операнд_1 = операнд_1 + операнд_2 О adc операнда,операнд__2 - команда сложения с учетом флага переноса cf. Принцип действия команды: операнд_1 = операнд_1 + операнд_2 + значение-Cf Обратите внимание на последнюю команду — это команда сложения, учитываю- щая перенос единицы из старшего разряда. Механизм появления такой едини- цы мы уже рассмотрели. Таким образом, команда adc является средством микро- процессора для сложения длинных двоичных чисел, размерность которых превосходит поддерживаемые микропроцессором длины стандартных полей. Рассмотрим пример вычисления суммы чисел (листинг 8.3). Листинг 8.3. Вычисление суммы чисел <1> ;prg_8_3.asm <2> masm <3> model small <4> stack 256 <5> .data <б> a db 254 <7> .code ;сегмент кода
Арифметические операции над целыми двоичными числами 161 <8> main: <9> mov ax,@data <10> mov ds, ax <11> <12> xor ax,ax <13> add al, 17 <14> add al,a <15> jne ml ;если нет переноса, то перейти на ml <1б> adc ah, 0 ; в ах сумма с учетом переноса <17> ml: ... <18> exit: <19> mov ax,4c00h ; стандартный выход <20> int 21h <21> end main ; конец программы В листинге 8.3 в строках 13—14 создана ситуация, когда результат сложения выходит за границы операнда. Эта возможность учитывается строкой 15, где ко- манда jne (хотя можно было обойтись и без нее) проверяет состояние флага cf. Если он установлен в 1, то это признак того, что результат операции получился больше по размеру, чем размер операнда, и для его корректировки необходимо выполнить некоторые действия. В данном случае мы просто полагаем, что гра- ницы операнда расширяются до размера ах, для чего учитываем перенос в стар- ший разряд командой adc (строка 16). Если у вас остались вопросы, исследуйте работу команд сложения без учета знака, для чего введите листинг 8.3, получите исполняемый модуль, запустите отладчик и откройте в нем окна View|Dump и View | Registers. Сложение двоичных чисел со знаком Теперь настала пора раскрыть небольшой секрет. Дело в том, что на самом деле микропроцессор не подозревает о различии между числами со знаком и без знака. Вместо этого у него есть средства фиксирования возникновения харак- терных ситуаций, складывающихся в процессе вычислений. Некоторые из них мы рассмотрели при обсуждении сложения чисел без знака — это флаг перено- са cf, установка которого в 1 говорит о том, что произошел выход за пределы разрядности операндов, и команда adc, которая учитывает возможность такого выхода (перенос из младшего разряда). Другое средство — это регистрация со- стояния старшего (знакового) разряда операнда, которое осуществляется с помощью флага переполнения of в регистре eflags (бит И). Из материала урока 6 вы помните, как представляются числа в компьютере: положительные числа — в двоичном коде, а отрицательные — в дополнитель- ном коде. Рассмотрим различные варианты сложения чисел. Примеры призва-
162 Урок 8. Арифметические команды ны показать поведение двух старших битов операндов и правильность резуль- тата операции сложения: Пример 8.1. Сложение чисел 1 30566 = 01110111 01100110 + 00687 - 00000010 10101111 31253 - 01111010 00010101 Следим за переносами из 14-го и 15-го разрядов и правильностью результата: переносов нет, результат правильный. Пример 8.2. Сложение чисел 2 30566 = 01110111 01100110 + 30566 = 01110111 01100110 61132 = ИЮНЮ 11001100 Произошел перенос из 14-го разряда; из 15-го разряда переноса нет. Результат неправильный, так как имеется переполнение — значение числа получилось больше, чем то, которое может иметь 16-битное число со знаком (+32 767). Пример 8.3. Сложение чисел 3 -30566 = 10001000 10011010 + -04875 = 11101100 11110101 -35441 = 01110101 10001111 Произошел перенос из 15-го разряда, из 14-го разряда нет переноса. Результат неправильный, так как вместо отрицательного числа получилось положитель- ное (в старшем бите находится 0). Пример 8.4. Сложение чисел 4 -4875 - 11101100 11110101 + -4875 = 11101100 11110101 -9750 = 11011001 11101010 Есть переносы из 14-го и 15-го разрядов. Результат правильный. Таким образом, мы исследовали все случаи и выяснили, что ситуация перепол- нения (установка флага of в 1) происходит при переносе: О из 14-го разряда (для положительных чисел со знаком); О из 15-го разряда (для отрицательных чисел).
Арифметические операции над целыми двоичными числами 163 И наоборот, переполнения не происходит (то есть флаг of сбрасывается в 0), если есть перенос из обоих разрядов или перенос отсутствует в обоих разрядах. Итак, переполнение регистрируется с помощью флага переполнения of. Допол- нительно к флагу of при переносе из старшего разряда устанавливается в 1 и флаг переноса cf. Так как микропроцессор не знает о существовании чисел со знаком и без знака, то вся ответственность за правильность действий с полу- чившимися числами ложится на программиста. Теперь, наверное, понятно, по- чему мы столько внимания уделили тонкостям сложения чисел со знаком. Учтя все это, мы сможем организовать правильный процесс сложения чисел — будем анализировать флаги cf и of и принимать правильное решение! Проана- лизировать флаги cf и of можно командами условного перехода jc\jnc и jo\j по соответственно. Что же касается команд сложения чисел со знаком, то вы уже, наверное, дога- дались, что сами команды сложения чисел со знаком те же, что и для чисел без знака. Вычитание двоичных чисел без знака Как и при анализе операции сложения, порассуждаем над сутью процессов, происходящих при выполнении операции вычитания: О Если уменьшаемое больше вычитаемого, то проблем нет, — разность поло- жительна, результат верен. О Если уменьшаемое меньше вычитаемого, возникает проблема: результат мень- ше 0, а это уже число со знаком. В этом случае результат необходимо завер- нуть. Что это означает? При обычном вычитании (в столбик) делают заем 1 из старшего разряда. Микропроцессор поступает аналогично, то есть занимает 1 из разряда, следующего за старшим, в разрядной сетке операнда. Поясним на примере. Пример 8.5. Вычитание чисел 1 05 - 00000000 00000101 -10 - 00000000 00001010 Для того чтобы произвести вычитание, произведем воображаемый заем из стар- шего разряда: 1 00000000 00000101 00000000 00001010 11111111 11111011 Тем самым, по сути, выполняется действие (65 536 + 5) - 10 - 65 531, 0 здесь как бы эквивалентен числу 65 536. Результат, конечно, неверен, но микропро- цессор считает, что все нормально, хотя факт заема единицы он фиксирует
164 Урок 8. Арифметические команды установкой флага переноса cf. Но посмотрите еще раз внимательно на результат операции вычитания. Это же -5 в дополнительном коде! Проведем эксперимент: представим разность в виде суммы 5 + (-10). Пример 8.6. Вычитание чисел 2 5 = 00000000 00000101 + (-10) = 11111111 11110110 1111111111111011, то есть мы получили тот же результат, что и в предыдущем примере. Таким образом, после команды вычитания чисел без знака нужно анализировать состояние флага cf. Если он установлен в 1, то это говорит о том, что произошел заем из старшего разряда и результат получился в дополнительном коде. Аналогично командам сложения, группа команд вычитания состоит из мини- мально возможного набора. Эти команды выполняют вычитание по алгорит- мам, которые мы сейчас рассматриваем, а учет особых ситуаций должен произ- водиться самим программистом. К командам вычитания относятся следующие: О dec операнд - операция декремента, то есть уменьшения значения операнда на Г, О sub операнду!,операнда — команда вычитания; ее принцип действия: операнд_1 = операнд_1 - операнд_2 О sbb операнду,операнду — команда вычитания с учетом заема (флага cf): операнд_1 = операнд_1 - операнд_2 - значение-Cf Как видите, среди команд вычитания есть команда sbb, учитывающая флаг пе- реноса cf. Эта команда подобна adc, но теперь уже флаг cf выполняет роль индикатора заема 1 из старшего разряда при вычитании чисел. Рассмотрим пример (листинг 8.4) программной обработки ситуации, разоб- ранной в примере 8.6. Листинг 8.4. Проверка при вычитании чисел без знака <1> ;prg_8_4.asm <2> masm <3> model small <4> stack 256 <5> .data <б> .code ;сегмент кода <7> main: ; точка входа в программу <8> <9> xor ах, ах <10> mov al, 5
Арифметические операции над целыми двоичными числами 165 <11> <12> <13> sub al,10 jnc ml ;нет переноса? neg al ; в al модуль результата <14> <15> ml: ... exit: <1б> mov ax,4c00h стандартный выход <17> int 21 h <18> end main ; конец программы В этом примере в строке 11 выполняется вычитание. С указанными для этой команды вычитания исходными данными результат получается в дополнитель- ном коде (отрицательный). Для того чтобы преобразовать результат к нор- мальному виду (получить его модуль), применяется команда neg, с помощью которой получается дополнение операнда. В нашем случае мы получили до- полнение дополнения, или модуль отрицательного результата. А тот факт, что это на самом деле число отрицательное, отражен в состоянии флага cf. Дальше все зависит от алгоритма обработки. Исследуйте программу в отладчике. Вычитание двоичных чисел со знаком Здесь все несколько сложнее. Последний пример показал то, что микропроцес- сору незачем иметь два устройства — сложения и вычитания. Достаточно на- личия только одного — устройства сложения. Но для вычитания способом сложения чисел со знаком в дополнительном коде необходимо представлять оба операнда — и уменьшаемое, и вычитаемое. Результат тоже нужно рассмат- ривать как значение в дополнительном коде. Но здесь возникают сложности. Прежде всего они связаны с тем, что старший бит операнда рассматривается как знаковый. Рассмотрим пример вычитания 45 — (-127). Пример 8.7. Вычитание чисел со знаком 1 45 = 0010 1101 -127 = 1000 0001 -44 = 1010 1100 Судя по знаковому разряду, результат получился отрицательный, что, в свою очередь, говорит о том, что число нужно рассматривать как дополнение, равное -44. Правильный результат должен быть равен 172. Здесь мы, как и в случае знакового сложения, встретились с переполнением мантиссы, когда значащий разряд числа изменил знаковый разряд операнда. Отследить такую ситуацию можно по содержимому флага переполнения of. Его установка в 1 говорит о том, что результат вышел за диапазон представления знаковых чисел (то есть изменился старший бит) для операнда данного размера, и программист должен предусмотреть действия по корректировке результата.
166 Урок 8. Арифметические команды Другой пример разности рассматривается в примере 8.7, но выполним мы ее способом сложения. Пример 8.7. Вычитание чисел со знаком 2 -45 - 45 = -45 + (-45)= -90. -415 = 1101 ООН + -45 = 1101 ООН -90 = 1010 ОНО Здесь все нормально, флаг переполнения of сброшен в 0, а 1 в знаковом разряде говорит о том, что значение результата — число в дополнительном коде. Вычитание и сложение операндов большой размерности Если вы заметили, команды сложения и вычитания работают с операндами фиксированной размерности: 8, 16, 32 бит. А что делать, если нужно сложить числа большей размерности, например 48 бит, используя 16-разрядные опе- ранды? К примеру, сложим два 48-разрядных числа (рис. 8.5): 1 слагаемое 2 слагаемое 0010001110010101 0100010010001011 01001011 11111000 1010010100100100 1111111100110001 0100100110000110 1 шаг: сложение младших 16 бит -------------------------> 1 0100100010110111 3 шаг: сложение старших 16 бит (переноса из младшего разряда нет): 11110001 00011101 0010001110010101 0100010010001011 Результат сложения: 0110100000100000 11110001 00011101 0100100010110111 Рис. 8.5. Сложение операндов большой размерности На рис. 8.5 по шагам показана технология сложения длинных чисел. Видно, что процесс сложения многобайтных чисел происходит так же, как и при сложении двух чисел «в столбик» — с осуществлением, при необходимости, переноса 1 в старший разряд. Если нам удастся запрограммировать этот процесс, то мы значи- тельно расширим диапазон двоичных чисел, над которыми мы сможем выпол- нять операции сложения и вычитания.
Арифметические операции над целыми двоичными числами 167 Принцип вычитания чисел с диапазоном представления, превышающим стан- дартные разрядные сетки операндов, тот же, что и при сложении, то есть используется флаг переноса cf. Нужно только представлять себе процесс вычи- тания в столбик и правильно комбинировать команды микропроцессора с ко- мандой sbb. Чтобы написать достаточно интересную программу, моделирую- щую этот процесс, необходимо привлечь те конструкции языка ассемблера, которые мы еще не обсуждали. По этой причине подпрограммы, реализующие четыре основные арифметические действия для двоичных операндов произ- вольной размерности, вынесены в отдельное приложение 7. Не поленитесь внимательно изучить исходные тексты этих подпрограмм, так как они являют- ся хорошей иллюстрацией к материалу, изучаемому на этом и последующих уроках. К этим примерам можно будет обратиться в полной мере после того, как будут изучены механизмы процедур и макрокоманд (уроки 10 и 13). В завершение обсуждения команд сложения и вычитания отметим, что кроме флагов cf и of в регистре eflags есть еще несколько флагов, которые можно использовать с двоичными арифметическими командами. Речь идет о следую- щих флагах: О zf — флаг нуля, который устанавливается в 1, если результат операции ра- вен 0, и в 1, если результат не равен 0; О sf — флаг знака, значение которого после арифметических операций (и не только) совпадает со значением старшего бита результата, то есть с битом 7, 15 или 31. Таким образом, этот флаг можно использовать для операций над числами со знаком. Умножение двоичных чисел без знака Для умножения чисел без знака предназначена команда mul сомножитель_1 Как видите, в команде указан всего лишь один операнд-сомножитель. Второй операнд-сомножитель_2 задан неявно. Его местоположение фиксировано и за- висит от размера сомножителей. Так как в общем случае результат умножения больше, чем любой из его сомножителей, то его размер и местоположение дол- жны быть тоже определены однозначно. Варианты размеров сомножителей и размещения второго операнда и результата приведены в табл. 8.2. Из таблицы видно, что произведение состоит из двух частей и в зависимости от размера операндов размещается в двух местах — на месте сомножитель_2 (младшая часть) и в дополнительном регистре ah, dx, edx (старшая часть). Как же динамически (то есть во время выполнения программы) узнать, что резуль- тат достаточно мал и уместился в одном регистре или что он превысил размер- ность регистра и старшая часть оказалась в другом регистре? Для этого при-
168 Урок 8. Арифметические команды влекаются уже известные нам по предыдущему обсуждению флаги переноса cf и переполнения of: О если старшая часть результата нулевая, то после операции произведения фла- ги cf = 0 и of = 0; О если же эти флаги ненулевые, то это означает, что результат вышел за пре- делы младшей части произведения и состоит из двух частей, что и нужно учитывать при дальнейшей работе. Таблица 8.2. Расположение операндов и результата при умножении сомножитель_1 сомножитель_2 Результат Байт al 16 бит в ах: al — младшая часть результата; ah — старшая часть результата Слово ах 32 бит в паре dx:ax: ах — младшая часть резуль- тата; dx — старшая часть результата Двойное слово еах 64 бит в паре edx:eax: еах — младшая часть ре- зультата; edx — старшая часть результата Рассмотрим следующий пример программы. Листинг 8.5. Умножение <1> ;prg_8_5.asm <2> masm <3> model small <4> stack 256 <5> .data <б> rez label word <7> rez_l db 45 <8> rez_h db 0 <9> .code <10> main: <11> <12> xor ax, ax <13> mov al, 25 <14> mul rez_l <15> jnc ml <1б> mov rez_h,ah <17> ml: <18> mov rez_l,al <19> exit: <20> mov ax,4c00h <21> int 21 h <22> end main ;сегмент данных ; сегмент кода ; точка входа в программу ;если нет переполнения, то на м1 ; старшую часть результата в rez_h ;стандартный выход ;конец программы
Арифметические операции над целыми двоичными числами 169 В этой программе в строке 14 производится умножение значения в rez_l на число в регистре al. Согласно информации в табл. 8.2, результат умножения будет располагаться в регистре al (младшая часть) и регистре ah (старшая часть). Для выяснения размера результата в строке 15 командой условного пе- рехода jne анализируется состояние флага cf, и если оно не равно 1, то резуль- тат остался в рамках регистра al. Если же cf = 1, то выполняется команда в строке 16, которая формирует в поле rez_h старшее слово результата. Команда в строке 18 формирует младшую часть результата. Теперь обратите внимание на сегмент данных, а именно на строку 6. В этой строке содержится директива label. Мы еще не раз будем сталкиваться с этой директивой. В данном случае она назначает еще одно символическое имя rez адресу, на который уже указы- вает другой идентификатор rez_l. Отличие заключается в типах этих иденти- фикаторов — имя rez имеет тип слова, который ему назначается директивой label (имя типа указано в качестве операнда label). Введя эту директиву в программе, мы подготовились к тому, что, возможно, результат операции умно- жения будет занимать слово в памяти. Обратите внимание, что мы не наруши- ли принципа: младший байт по младшему адресу. Далее, используя имя rez, можно обращаться к значению в этой области как к слову. В заключение вам осталось исследовать в отладчике программу на разных набо- рах сомножителей. Умножение двоичных чисел со знаком Для умножения чисел со знаком предназначена команда imul операнд_1 [,операнд_2,операнд_3] Эта команда выполняется так же, как и команда mul. Отличительной особеннос- тью команды imul является только формирование знака. Если результат мал и умещается в одном регистре (то есть если cf = of = 0), то содержимое другого регистра (старшей части) является расширением знака — все его биты равны старшему биту (знаковому разряду) младшей части результата. В противном случае (если cf = of = 1) знаком результата является знаковый бит старшей части результата, а знаковый бит младшей части является значащим битом двоичного кода результата. Если вы посмотрите в 4 Справочнике команд» ко- манду imul, то увидите, что она допускает более широкие возможности по зада- нию местоположения операндов. Это сделано для удобства использования. Деление двоичных чисел без знака Для деления чисел без знака предназначена команда div делитель Делитель может находиться в памяти или в регистре и иметь размер 8, 16 или 32 бит. Местонахождение делимого фиксировано и так же, как в команде ум-
170 Урок 8. Арифметические команды ножения, зависит от размера операндов. Результатом команды деления являются значения частного и остатка. Варианты местоположения и размеров операндов операции деления показаны в табл. 8.3. Таблица 8.3. Расположение операндов и результата при делении Делимое Делитель Частное Остаток Слово 16 бит в регистре ах Байт — регистр или ячейка памяти Байт в регистре al Байт в регистре ah 32 бит 16 бит Слово 16 бит Слово 16 бит dx — старшая часть ах — младшая часть регистр или ячейка памяти в регистре ах в регистре dx 64 бит Двойное слово Двойное слово Двойное слово edx — старшая часть 32 бит 32 бит 32 бит еах — младшая часть регистр или ячейка памяти в регистре еах в регистре edx После выполнения команды деления содержимое флагов неопределенно, но возможно возникновение прерывания с номером 0, называемого «деление на ноль». Этот вид прерывания относится к так называемым исключениям. Эта разновидность прерываний возникает внутри микропроцессора из-за некото- рых аномалий во время вычислительного процесса. К вопросу об исключениях мы еще вернемся. Прерывание 0 — «деление на ноль» при выполнении команды div может возникнуть по одной из следующих причин: О делитель равен нулю; О частное не входит в отведенную под него разрядную сетку, что может слу- читься в следующих случаях: • при делении делимого величиной в слово на делитель величиной в байт, причем значение делимого в более чем 256 раз больше значения делителя; • при делении делимого величиной в двойное слово на делитель величи- ной в слово, причем значение делимого в более чем 65 536 раз больше значения делителя; • при делении делимого величиной в учетверенное слово на делитель ве- личиной в двойное слово, причем значение делимого в более чем 4 294 967 296 раз больше значения делителя. К примеру, выполним деление значения в области del на значение в области delt (листинг 8.6). Листинг 8.6. Деление чисел < i> ;prg_8.6.asm < 2> masm < з> model small < 4> stack 256
Арифметические операции над целыми двоичными числами 171 <5> .data <б> del_b label byte <7> del dw 29876 <8> delt db 45 <9> .code ;сегмент кода <10> main: ;точка входа в программу <11> <12> хог ах, ах <13> последующие две команды можно заменить одной mov ах, del <14> mov ah,del_b ; старший байт делимого в ah <15> mov al,del_b+1 ; младший байт делимого в al <16> div delt ;в al - частное, в ah - остаток <17> <18> end main ; конец программы Деление двоичных чисел со знаком Для деления чисел со знаком предназначена команда id iv делитель Для этой команды справедливы все рассмотренные положения, касающиеся ко- манд и чисел со знаком. Отметим лишь особенности возникновения исключения О «деление на ноль» в случае чисел со знаком. Оно возникает при выполнении команды idiv по одной из следующих причин: О делитель равен нулю; О частное не входит в отведенную для него разрядную сетку. Последнее, в свою очередь, может произойти: • при делении делимого величиной в слово со знаком на делитель величи- ной в байт со знаком, причем значение делимого в более чем 128 раз больше значения делителя (таким образом, частное не должно находиться вне диапазона от -128 до +127); • при делении делимого величиной в двойное слово со знаком на делитель величиной в слово со знаком, причем значение делимого в более чем 32 768 раз больше значения делителя (таким образом, частное не должно находиться вне диапазона от -32 768 до +32 768); • при делении делимого величиной в учетверенное слово со знаком на де- литель величиной в двойное слово со знаком, причем значение делимого в более чем 2 147 483 648 раз больше значения делителя (таким образом, частное не должно находиться вне диапазона от -2 147 483 648 до +2 147 483 647).
172 Урок 8. Арифметические команды Вспомогательные команды для целочисленных операций В системе команд микропроцессора есть несколько команд, которые могут облег- чить программирование алгоритмов, производящих арифметические вычисле- ния. В них могут возникать различные проблемы, для разрешения которых раз- работчики микропроцессора предусмотрели несколько команд. Рассмотрим их в следующем разделе. Команды преобразования типов Что делать, если размеры операндов, участвующих в арифметических операциях, разные? Например, предположим, что в операции сложения один операнд являет- ся словом, а другой занимает двойное слово. Выше сказано, что в операции сложе- ния должны участвовать операнды одного формата. Если числа без знака, то вы- ход найти просто. В этом случае можно на базе исходного операнда сформировать новый (формата двойного слова), старшие разряды которого просто заполнить нулями. Сложнее ситуация для чисел со знаком: как динамически, в ходе выполне- ния программы, учесть знак операнда? Для решения подобных проблем в системе команд микропроцессора есть так называемые команды преобразования типа. Эти команды расширяют байты в слова, слова — в двойные слова и двойные слова — в учетверенные слова (64-разрядные значения). Команды преобразования типа осо- бенно полезны при преобразовании целых со знаком, так как они автоматически за- полняют старшие биты вновь формируемого операнда значениями знакового бита старого объекта. Эта операция приводит к целым значениям того же знака и той же величины, что и исходная, но уже в более длинном формате. Подобное преобразова- ние называется операцией распространения знака. Существуют два вида команд преобразования типа: 1. Команды без операндов — эти команды работают с фиксированными регист- рами: О cbw (Convert Byte to Word) — команда преобразования байта (в регистре al) в слово (в регистре ах) путем распространения значения старшего бита al на все биты регистра ah; О cwd (Convert Word to Double) — команда преобразования слова (в регистре ах) в двойное слово (в регистрах dx:ax) путем распространения значения старшего бита ах на все биты регистра dx; О cwde (Convert Word to Double) — команда преобразования слова (в ре- гистре ах) в двойное слово (в регистре еах) путем распространения значе- ния старшего бита ах на все биты старшей половины регистра еах; О cdq (Convert Double Word to Quarter Word) — команда преобразования двойного слова (в регистре еах) в учетверенное слово (в регистрах edx:еах) путем распространения значения старшего бита еах на все биты регист- ра edx;
Вспомогательные команды для целочисленных операций 173 2. Команды movsx и movzx, относящиеся к командам обработки строк (см. урок И). Эти команды обладают полезным свойством в контексте на- шей проблемы: О movsx операнд_1,операнд_2 — переслать с распространением знака. Расши- ряет 8 или 16-разрядное значение операнд_2, которое может быть регист- ром или операндом в памяти, до 16- или 32-разрядного значения в од- ном из регистров, используя значение знакового бита для заполнения старших позиций операнд_1. Данную команду удобно использовать для подготовки операндов со знаками к выполнению арифметических дей- ствий; О movzx операнд_1,операнд_2 - переслать с расширением нулем. Расширяет 8 или 16-разрядное значение операнд_2 до 16 или 32-разрядного с очист- кой (заполнением) нулями старших позиций операнд_2. Данную команду удобно использовать для подготовки операндов без знака к выполнению арифметических действий. К примеру, вычислим значение у = (a + Ь)/с, где а, Ь, с — байтовые знаковые переменные (листинг 8.7). Листинг 8.7. Вычисление простого выражения <1> ;prg_8_9.asm <2> masm <3> model small <4> stack 256 <5> .data <6> a db ? <7> b db ? <8> c db ? <9> У dw 0 <10> .code <11> main: ;точка входа в программу <12> <13> xor ax, ax <14> mov al, a <15> cbw <1б> movsx bx, b <17> add ax.bx <18> idiv c ;в al - частное, в ah - остаток <19> exit: <20> mov ax,4c00h стандартный выход <21> int 21 h <22> end main ;конец программы
174 Урок 8. Арифметические команды В этой программе делимое для команды idiv (строка 18) готовится заранее. Так как делитель имеет размер байта, то делимое должно быть словом. С учетом это- го сложение осуществляется параллельно с преобразованием размера результата в слово (строки 14-17). Например, расширение операндов со знаком произво- дится двумя разными командами — cbw и movsx. Другие полезные команды В системе команд микропроцессора есть две команды — xadd и neg, которые мо- гут быть полезны, в частности, для программирования вычислительных дей- ствий. xadd назначение,источник — обмен местами и сложение. Команда позволяет выполнить последовательно два действия: О обменять значения назначение и источник; О поместить на место операнда назначение сумму: назначение = назначение х ис- точник. neg операнд — отрицание с дополнением до двух. Команда выполняет инвертиро- вание значения операнд. Физически команда выполняет одно действие: операнд = О - операнд, то есть вычитает операнд из нуля. Команду neg операнд можно применять для: О смены знака; О выполнения вычитания из константы. Дело в том, что команды sub и sbb не позволяют вычесть что-либо из константы, так как константа не может слу- жить операндом-приемником в этих операциях. Поэтому данную операцию можно выполнить с помощью двух команд: neg ах ; смена знака (ах) add ах,340;фактически вычитание: (ах)=340-(ах) Арифметические операции над двоично-десятичными числами Определение и формат BCD-чисел были рассмотрены в начале этого урока. У вас справедливо может возникнуть вопрос: а зачем нужны BCD-числа? Ответ может быть следующим: BCD-числа нужны в деловых приложениях, то есть там, где числа должны быть большими и точными. Как мы уже убедились на примере двоичных чисел, операции с такими числами довольно проблематич- ны для языка ассемблера. К недостаткам использования двоичных чисел мож- но отнести следующие: О значения величин в формате слова и двойного слова имеют ограниченный диапазон. Если программа предназначена для работы в области финансов,
Арифметические операции над двоично-десятичными числами 175 то ограничение суммы в рублях величиной 65 536 (для слова) или даже 4 294 967 296 (для двойного слова) будет существенно сужать сферу ее при- менения (да еще в наших экономических условиях — тут уж никакая дено- минация не поможет); О наличие ошибок округления. Представляете себе программу, работающую где-нибудь в банке, которая не учитывает величину остатка при действиях с целыми двоичными числами и оперирует при этом миллиардами. Не хоте- лось бы быть автором такой программы. Применение чисел с плавающей точкой не спасет — там существует та же проблема округления; О представление большого объема результатов в символьном виде (ASCII- коде). Деловые программы не просто выполняют вычисления; одной из це- лей их использования является оперативная выдача информации пользова- телю. Для этого, естественно, информация должна быть представлена в символьном виде. Перевод чисел из двоичного кода в ASCII-код, как мы уже видели, требует определенных вычислительных затрат. Число с плаваю- щей точкой еще труднее перевести в символьный вид. А вот если посмот- реть на шестнадцатеричное представление неупакованной десятичной циф- ры (в начале нашего урока) и на соответствующий ей символ в таблице ASCII, то видно, что они отличаются на величину 30h. Таким образом преобра- зование в символьный вид и обратно получается намного проще и быстрее. Наверняка, вы уже убедились в важности овладения хотя бы основами действий с десятичными числами. Далее рассмотрим особенности выполнения основных арифметических операций с десятичными числами. Для предупреждения воз- можных вопросов отметим сразу тот факт, что отдельных команд сложения, вы- читания, умножения и деления BCD-чисел нет. Сделано это по вполне понятным причинам: размерность таких чисел может быть сколь угодно большой. Склады- вать и вычитать можно двоично-десятичные числа как в упакованном формате, так и в неупакованном, а вот делить и умножать можно только неупакованные BCD-числа. Почему это так, будет видно из дальнейшего обсуждения. Арифметические действия над неупакованными BCD-числами Сложение Рассмотрим два случая сложения. Пример 8.8. Результат сложения не больше 9 6 = 0000 оно + 3 = 0000 ООН 9 = 0000 1001 Переноса из младшей тетрады в старшую нет. Результат правильный.
176 Урок 8. Арифметические команды Пример 8.9. Результат сложения больше 9 06 = 0000 оно + 07 = 0000 0111 13 = 0000 1101 То есть мы получили уже не BCD-число. Результат неправильный. Правиль- ный результат в неупакованном BCD-формате должен быть таким: 0000 0001 0000 ООН в двоичном представлении (или 13 в десятичном). Проанализировав данную проблему при сложении BCD-чисел (и подобные проблемы при вы- полнении других арифметических действий) и возможные пути ее решения, разработчики системы команд микропроцессора решили не вводить специаль- ные команды для работы с BCD-числами, а ввести несколько корректировоч- ных команд. Назначение этих команд — в корректировке результата работы обычных арифметических команд для случаев, когда операнды в них являются BCD-числами. В случае вычитания в примере 8.9 видно, что полученный ре- зультат нужно корректировать. Для коррекции операции сложения двух одно- значных неупакованных BCD-чисел в системе команд микропроцессора суще- ствует специальная команда ааа (ASCII Adjust for Addition) — коррекция результата сложения для представления в символьном виде. Эта команда не имеет операндов. Она работает неявно только с регистром al и анализирует значение его младшей тетрады. Если это значение меньше 9, то флаг cf сбрасывается в 0 и осуществляется переход к следующей команде. Если это значение больше 9, то выполняются следующие действия: О к содержимому младшей тетрады al (но не к содержимому всего регистра!) прибавляется 6, тем самым значение десятичного результата корректирует- ся в правильную сторону; О флаг cf устанавливается в 1, тем самым фиксируется перенос в старший разряд, для того чтобы его можно было учесть в последующих действиях. Так, в примере 8.9, предполагая, что значение суммы 0000 1101 находится в al, после команды ааа в регистре будет 1101 + 0110= ООН, то есть двоичное 0000 ООП или десятичное 3, а флаг cf установится в 1, то есть перенос запом- нился в микропроцессоре. Далее программисту нужно будет использовать ко- манду сложения adc, которая учтет перенос из предыдущего разряда. Приведем пример программы сложения двух неупакованных BCD-чисел. Листинг 8.8. Сложение неупакованных BCD-чисел < 1> ;prg_8_8.asm <2> < 3> .data < 4> len equ 2 ; разрядность числа < 5> b db 1,7 ; неупакованное число 71
Арифметические операции над двоично-десятичными числами 177 <6> с db 4,5 ; неупакованное число 54 <7> sum db 3 dup (0) <8> .code <9> main: ;точка входа в программу <10> <11> xor bx, bx <12> mov ex,len <13> ml: <14> mov al,b[bx] <15> adc al,c[bx] <1б> aaa <17> mov sum[bx],al <18> inc bx <19> loop ml <20> adc sum[bx],0 <21> exit: В листинге 8.8 есть несколько интересных моментов, над которыми есть смысл поразмыслить. Начнем с описания BCD-чисел. Из строк 5 и 6 видно, что поря- док их ввода обратен нормальному, то есть цифры младших разрядов располо- жены по меньшему адресу. Но это вполне логично по нескольким причинам: во-первых, такой порядок удовлетворяет общему принципу представления дан- ных для микропроцессоров Intel, во-вторых, это очень удобно для поразрядной обработки неупакованных BCD-чисел, так как каждое из них занимает один байт. Хотя, повторюсь, программист сам волен выбирать способ описания BCD-чисел в сегменте данных. Строки 14-15 содержат команды, которые скла- дывают цифры в очередных разрядах BCD-чисел, при этом учитывается воз- можный перенос из младшего разряда. Команда ааа в строке 16 корректирует результат сложения, формируя в al BCD-цифру и, при необходимости, уста- навливая в 1 флаг cf. Строка 20 учитывает возможность переноса при сложе- нии цифр из самых старших разрядов чисел. Результат сложения формируется в поле sum, описанном в строке 7. Вычитание Ситуация здесь вполне аналогична сложению. Рассмотрим те же случаи. Пример 8.10. Результат вычитания не больше 9 6 = 0000 0110 3 = 0000 ООН 3 = 0000 ООН Как видим заема из старшей тетрады нет. Результат верный и корректировки не требует.
178 Урок 8. Арифметические команды Пример 8.11. Результат вычитания больше 9 6 = 0000 оно 7 = 0000 0111 -1 = 1111 1111 Вычитание проводится по правилам двоичной арифметики. Поэтому результат не является 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 есть смысл использовать только один раз при вычитании самых младших цифр операндов, далее должна применять- ся команда sbb, которая будет учитывать возможный заем из старшего разряда. В листинге 8.9 мы обходимся одной командой sbb, которая в цикле произво- дит поразрядное вычитание двух BCD-чисел. Листинг 8.9. Вычитание неупакованных BCD-чисел <i> ;prg_8_9.asm <2> masm <3> model small <4> stack 256 <5> .data ;сегмент данных <б> b db 1,7 ;неупакованное число 71 <7> c db 4.5 ;неупакованное число 54 <8> subs db 2 dup (0) <9> .code <10> main: ;точка входа в программу
Арифметические операции над двоично-десятичными числами 179 <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 <2б> m2: <27> exit: <28> mov ax,4c00h •«стандартный выход <29> int 21h <30> end main ;конец программы Данная программа не требует особых пояснений, когда уменьшаемое больше вычи- таемого. Поэтому обратите внимание на строку 24. С ее помощью мы предусмат- риваем случай, когда после вычитания старших цифр чисел был зафиксирован факт заема. Это говорит о том, что вычитаемое было больше уменьшаемого, в ре- зультате чего разность будет неправильной. Эту ситуацию нужно как-то обрабо- тать. С этой целью в строке 24 командой jc анализируется флаг cf. По результату этого анализа мы уходим на ветку программы, обозначенную меткой m2, где и бу- дут выполняться некоторые действия. Набор этих действий сильно зависит от конкретного алгоритма обработки, поэтому поясним только суть действий, кото- рые может выполнять соответствующий фрагмент программы. Для этого посмот- рим в отладчике, как наша программа выполнит вычитание 50 - 74 (правильный ответ -24). То, что вы увидите в окне Dump отладчика, в поле, соответствующем адресу subs, будет далеко от истинного ответа. Что делает в этом случае человек? Он просто выполняет вычитание 74 - 50 = 24 и рассматривает результат как име- ющий знак минус. Так как у микропроцессора нет средств обработки подобной ситуации, то фрагмент программы, обозначенный меткой m2, может поменять уменьшаемое и вычитаемое местами, выполнить вычитание и где-то отметить тот факт, что разность, на самом деле, нужно рассматривать как отрицательное число. Но ключевой момент здесь все-таки тот, что микропроцессор с помощью флага cf сигнализирует нам об этой особой ситуации. Умножение На примере сложения и вычитания неупакованных чисел стало понятно, что стандартных алгоритмов для выполнения этих действий над BCD-числами нет и
180 Урок 8. Арифметические команды программист должен сам, исходя из требований к своей программе, реализовать эти операции. Реализация двух оставшихся операций — умножения и деле- ния — еще более сложна. В системе команд микропроцессора присутствуют только средства для производства умножения и деления одноразрядных неупа- кованных BCD-чисел. Для того чтобы умножать числа произвольной размерности, нужно реализовать процесс умножения самостоятельно, взяв за основу некоторый алгоритм умно- жения, например «в столбик». Позже мы рассмотрим пример программы, выпол- няющей умножение десятичных чисел произвольной размерности. Для того чтобы перемножить два одноразрядных BCD-числа, необходимо: О поместить один из сомножителей в регистр al (как того требует команда mul); О поместить второй операнд в регистр или память, отведя байт; О перемножить сомножители командой mul (результат, как и положено, будет в ах); О результат, конечно, получится в двоичном коде, поэтому его нужно скоррек- тировать. Для коррекции результата после умножения применяется специальная команда aam (ASCII Adjust for Multiplication) — коррекция результата умножения для представления в символьном виде. Она не имеет операндов и работает с регистром ах следующим образом: О делит al на 10; О результат деления записывается так: частное — в al, остаток — в ah. В результате после выполнения команды aam в регистрах al и ah находятся правильные двоично-десятичные цифры произведения двух цифр. В листинге 8.10 приведен пример умножения BCD-числа произвольной раз- мерности на однозначное BCD-число. Листинг 8.10. Умножение неупакованных BCD-чисел <1> masm <2> model small <3> stack 256 <4> .data <5> b db 6,7 ; неупакованное число 76 <б> c db 4 ; неупакованное число 4 <7> proizvdb 4 dup (0) <8> .code <9> main: ;точка входа в программу <10> mov ах,©data <11> mov ds, ax
Арифметические опероции нод двоично-десятичными числами 181 <12> ХОГ ax, ax <13> len equ 2 ; размерность сомножителя 1 <14> ХОГ bx.bx <15> ХОГ si, si <1б> ХОГ di,di <17> mov ex, len ; в ex длина наибольшего сомножителя 1 <18> <19> ml: mov al,b[si] <20> mul с <21> aam ; коррекция умножения <22> adc al,dl ;учли предыдущий перенос <23> aaa ; скорректировали результат сложения с переносом <24> mov dl, ah ; запомнили перенос <25> mov proizv[bx],al <2б> inc si <27> inc bx <28> loop ml <29> mov proizv[bx],dl ;учли последний перенос <30> <31> exit: mov ax,4c00h <32> int 21h <33> end main Данную программу можно легко модифицировать для умножения BCD-чисел произвольной длины. Для этого достаточно представить алгоритм умножения в «столбик». Листинг 8.10 можно использовать для получения частичных про- изведений в этом алгоритме. После их сложения со сдвигом получится иско- мый результат. Попробуйте выполнить разработку этой программы самостоя- тельно. Перед окончанием обсуждения команды аат необходимо отметить еще один вариант ее применения. Эту команду можно применять для преобразования двоичного числа в регистре al в неупакованное BCD-число, которое будет раз- мещено в регистре ах: старшая цифра результата — в ah, младшая — в al. Понят- но, что двоичное число должно быть в диапазоне 0...99. Деление Процесс выполнения операции деления двух неупакованных BCD-чисел не- сколько отличается от других, рассмотренных ранее, операций с ними. Здесь также требуются действия по коррекции, но они должны выполняться до ос- новной операции, выполняющей непосредственно деление одного BCD-числа на другое BCD-число. Предварительно в регистре ах нужно получить две
182 Урок 8. Арифметические команды неупакованные BCD-цифры делимого. Это делает программист удобным для него способом. Далее нужно выдать команду aad: aad (ASCII Adjust for Division) — коррекция деления для представления в символьном виде. Команда не имеет операндов и преобразует двузначное неупакованное BCD-чис- ло в регистре ах в двоичное число. Это двоичное число впоследствии будет играть роль делимого в операции деления. Кроме преобразования команда aad помещает полученное двоичное число в регистр al. Делимое, естественно, бу- дет двоичным числом из диапазона 0...99. Алгоритм, по которому команда aad осуществляет это преобразование, состоит в следующем: О умножить старшую цифру исходного BCD-числа в ах (содержимое ah) на 10; О выполнить сложение ah + al, результат которого (двоичное число) занести в al; О обнулить содержимое ah. Далее программисту нужно выдать обычную команду деления div для выполне- ния деления содержимого ах на одну BCD-цифру, находящуюся в байтовом ре- гистре или байтовой ячейке памяти. Деление неупакованных BCD-чисел иллюс- трируется листингом 8.11. Листинг 8.11. Деление неупакованных BCD-чисел < i> ;prg_8_11.asm <2> < з> .data ; сегмент данных < 4> b db 1,7 ; неупакованное BCD-число 71 < 5> с db 4 ; < б> ch db 2 dup (0) < 7 > .code ; се гмент кода < 8> main: ;точка входа в программу <9> < ю> mov al,b < и> aad ; коррекция перед делением < 12> div с ; в al BCD-частное, в ah BCD-остаток <13> < 14> exit: Аналогично aam, команде aad можно найти и другое применение — использо- вать ее для перевода неупакованных BCD-чисел из диапазона 0..99 в их двоич- ный эквивалент. Для деления чисел большей разрядности, так же как и в случае умножения, нужно реализовывать свой алгоритм, например в < столбик», либо найти более оптимальный путь. Любопытный и настойчивый читатель, возможно, самостоя- тельно разработает эти программы. Но это делать совсем необязательно.
Арифметические операции над двоично-десятичными числами 183 В приложении 7 приведены тексты макрокоманд, которые выполняют четыре основных арифметических действия с BCD-числами любой разрядности. Арифметические действия над упакованными BCD-числами Как уже отмечалось выше, упакованные BCD-числа можно только складывать и вычитать. Для выполнения других действий над ними их нужно дополни- тельно преобразовывать либо в неупакованный формат, либо в двоичное пред- ставление. Из-за того, что упакованные BCD-числа представляют не слишком большой интерес, мы их рассмотрим кратко. Сложение Вначале разберемся с сутью проблемы и попытаемся сложить два двузначных упакованных BCD-числа. Пример 8.12. Сложение упакованных BCD-чисел 67 = ОНО 0111 + 75 - 0111 0101 142 = 1101 1100 = 220 Как видим, в двоичном виде результат равен 1101 1100 (или 220 в десятичном представлении), что неверно. Это происходит по той причине, что микропроцес- сор не подозревает о существовании BCD-чисел и складывает их по правилам сложения двоичных чисел. На самом деле результат в двоично-десятичном виде должен быть равен 0001 0100 0010 (или 142 в десятичном представлении). Чи- татель видит, что, как и для неупакованных BCD-чисел, для упакованных BCD- чисел существует необходимость как-то корректировать результаты арифмети- ческих операций. Микропроцессор предоставляет для этого команду daa: daa (Decimal Adjust for Addition) — коррекция результата сложения для представления в десятичном виде. Команда daa преобразует содержимое регистра al в две упакованные десятичные цифры по алгоритму, приведенному в приложении 2. Получившаяся в результате сложения единица (если результат сложения больше 99) запоминается в флаге cf, тем самым учитывается перенос в старший разряд. Проиллюстрируем сказанное на примере сложения двух двузначных BCD-чисел в упакованном формате (листинг 8.12). Листинг 8.12. Сложение упакованных BCD-чисел <i> ;prg_8_12.asm <2> <з> .data ;сегмент данных продолжение &
184 Урок 8. Арифметические команды <4> <5> Ь С db db 17h ; упакованное число 17h 45h ; упакованное число 45 <б> sum db 2 dup (0) <7> .code ; сегмент кода <8> <9> main: ; точка входа в программу <10> mov al, b <11> <12> add daa al, с <13> jne $+6 ; переход через команду, если результат <= 99 <14> mov sum+1,ah ;учет переноса при сложении (результат > 99) <15> <1б> exit: mov sum, al; младшие упакованные цифры результата В приведенном примере все достаточно прозрачно, единственное, на что следует обратить внимание — это описание упакованных BCD-чисел и порядок форми- рования результата. Результат формируется в соответствии с основным принци- пом работы микропроцессоров Intel: младший байт по младшему адресу. Вычитание Аналогично сложению, микропроцессор рассматривает упакованные BCD-числа как двоичные, и, соответственно, выполняет вычитание BCD-чисел как двоич- ных (см. пример 8.13). Выполним вычитание 67-75. Так как микропроцессор выполняет вычитание способом сложения, то и мы последуем этому: Пример 8.13. Вычитание упакованных BCD-чисел 67 = ОНО 0111 + -75 = 1011 0101 -8 = 0001 1100 = 28 ??? Как видим, результат равен 28 в десятичной системе счисления, что является абсурдом. В двоично-десятичном коде результат должен быть равен 0000 1000 (или 8 в десятичной системе счисления). При программировании вычитания упакованных BCD-чисел программист, как и при вычитании неупакованных BCD-чисел, должен сам осуществлять контроль за знаком. Это делается с по- мощью флага cf, который фиксирует заем из старших разрядов. Само вычита- ние BCD-чисел осуществляется простой командой вычитания sub или sbb. Коррекция результата осуществляется командой das: das (Decimal Adjust for Substraction) — коррекция результата вычитания для представления в десятичном виде. Команда das преобразует содержимое регистра al в две упакованные десятичные цифры по алгоритму, описанному в приложении 2.
Арифметические операции над двоично-десятичными числами 185 Подведем некоторые итоги: 0 Микропроцессор имеет достаточно мощные средства для реализации вычис- лительных операций. Для этого у него есть блок целочисленных операций и блок операций с плавающей точкой. Для большинства задач, использующих язык ассемблера, достаточно целочисленной арифметики. 0 Команды целочисленных операций работают с данными двух типов: двоич- ными и двоично-десятичными числами (BCD-числами). 0 Двоичные данные могут либо иметь знак, либо не иметь такового. Микро- процессор, на самом деле, не различает числа со знаком и без. Он помогает лишь отслеживать изменение состояния некоторых битов операндов и со- стояние отдельных флагов. Операции сложения и вычитания чисел со знаком и без знака проводятся одним устройством и по единым правилам. 0 Контроль за правильностью результатов и их надлежащей интерпретацией полностью лежит на программисте. Он должен контролировать состояние флагов cf и of регистра eflags во время вычислительного процесса. 0 Для операций с числами без знака нужно контролировать флаг cf. Установка его в 1 сигнализирует о том, что число вышло за разрядную сетку операндов. 0 Для чисел со знаком установка флага of в 1 говорит о том, что в результате сложения чисел одного знака результат выходит за границу допустимых значений чисел со знаком в данном формате, и сам результат меняет знак (пропадает порядок). 0 По результатам выполнения арифметических операций устанавливаются так- же флаги pf, zf и sf. 0 В отличие от команд сложения и вычитания, команды умножения и деления позволяют учитывать знак операндов. 0 Арифметические команды очень «капризны» к размерности операндов, по- этому в систему команд микропроцессора включены специальные команды, позволяющие отслеживать эту характеристику. 0 Двоичные данные имеют довольно большой, но ограниченный диапазон зна- чений. Для коммерческих приложений этот диапазон слишком мал, поэтому в архитектуру микропроцессора введены средства для работы с так называ- емыми двоично-десятичными (BCD) данными. 0 Двоично-десятичные данные представляются в двух форматах, упакованном и неупакованном. Наиболее универсальным является неупакованный формат.
УРОК Логические команды □ Краткое описание группы логических команд □ Команды для выполнения логических операций □ Организация работы с отдельными битами □ Сдвиги
Наряду со средствами арифметических вычислений система команд микропро- цессора имеет также средства логического преобразования данных. Под логи- ческими понимаются такие преобразования данных, в основе которых лежат правила формальной логики. Формальная логика работает на уровне утверж- дений истинно и ложно. Для микропроцессора это, как правило, означает 1 и О соответственно. Для компьютера язык нулей и единиц является родным, но минимальной единицей данных, с которой работают машинные команды, явля- ется байт. Однако на системном уровне часто необходимо иметь возможность работать на предельно низком уровне — на уровне бит. К средствам логического преобразования данных относятся логические команды и логические операции (см. урок 5). На рис. 9.1 показаны средства микропро- цессора для организации работы с данными по правилам формальной логики. Они разбиты на две группы: команды и операции. Команды рассматриваются на этом уроке. Операции были изучены нами на уроке 5. Напомню, что опе- ранд команды ассемблера в общем случае может представлять собой выраже- ние, которое, в свою очередь, является комбинаций операторов и операндов. Среди этих операторов могут быть и операторы, реализующие логические опе- рации над объектами выражения. Средства логической обработки данных [Операций] | Команды) -| логические -and -or -xor Lnot_______ -| сдвига -shr Lshl_______ I сравнения" -eq -ne -It -le -gt Lge ГОГ Рис. 9.1. Средства микропроцессора для работы с логическими данными
188 Урок 9. Логические команды Перед подробным рассмотрением этих средств давайте посмотрим, что же представляют собой сами логические данные и какие операции над ними про- изводятся. Логические данные Теоретической базой для логической обработки данных является формальная логика. Существует несколько систем логики. Одна из наиболее известных — это исчисление высказываний. Высказывание — это любое утверждение, о кото- ром можно сказать, что оно либо истинно, либо ложно. Исчисление высказы- ваний представляет собой совокупность правил, используемых для определения истинности или ложности некоторой комбинации высказываний. Исчисление высказываний очень гармонично сочетается с принципами работы компьютера и основными методами его программирования. Все аппаратные ком- поненты компьютера построены на логических микросхемах. Система пред- ставления информации в компьютере на самом нижнем уровне основана на понятии бита. Бит, имея всего два состояния — 0 (ложно) и 1 (истинно), естественным образом вписывается в исчисление высказываний. Согласно теории, над высказываниями (над битами) могут выполняться следую- щие логические операции: О отрицание (логическое НЕ) — логическая операция над одним операндом, результатом которой является величина, обратная значению исходного опе- ранда. Эта операция однозначно характеризуется следующей таблицей ис- тинности' (табл. 9.1); Таблица 9.1. Таблица истинности для логического отрицания Значение операнда 0 1 Результат операции 1 О О логическое сложение (логическое включающее ИЛИ) — логическая операция над двумя операндами, результатом которой является «истина» (1), если один или оба операнда имеют значение «истина» (1), и «ложь» (0), если оба операнда имеют значение «ложь» (0). Эта операция описывается с по- мощью следующей таблицы истинности (табл. 9.2); Таблица 9.2. Таблица истинности для логического включающего ИЛИ Значение операнда 1 0 0 11 Значение операнда 2 0 10 1 Результат операции 0 111 Таблица истинности — таблица результатов логических операций в зависимости от значений ис- ходных операндов.
Логические команды 189 О логическое умножение (логическое И) — логическая операция над двумя опе- рандами, результатом которой является «истина» (1) только в том случае, если оба операнда имеют значение «истина» (1). Во всех остальных случаях значение операции «ложь» (0). Эта операция описывается с помощью следу- ющей таблицы истинности (табл. 9.3); Таблица 9.3. Таблица истинности для логического И Значение операнда 1 0 0 11 Значение операнда 2 0 10 1 Результат операции 0 0 0 1 О логическое исключающее сложение (логическое исключающее ИЛИ) — логичес- кая операция над двумя операндами, результатом которой является «истина» (1), если только один из двух операндов имеет значение «истина» (1), и ложь (0), если оба операнда имеют значение «ложь» (0) или «истина» (1). Эта опе- рация описывается с помощью следующей таблицы истинности (табл. 9.4); Таблица 9.4. Таблица истинности для логического исключающего ИЛИ Значение операнда 1 0 0 1 1 Значение операнда 2 0 1 0 1 Результат операции 0 1 1 0 Система команд микропроцессора содержит пять команд, поддерживающих дан- ные операции. Эти команды выполняют логические операции над битами опе- рандов. Размерность операндов, естественно, должна быть одинакова. Например, если размерность операндов равна слову (16 бит), то логическая операция вы- полняется сначала над нулевыми битами операндов и ее результат записывается на место бита 0 результата. Далее команда последовательно повторяет эти дей- ствия над всеми битами с первого до пятнадцатого. Возможные варианты размерности операндов для каждой команды приведены в «Справочнике команд» (приложение 2). Логические команды В системе команд микропроцессора есть следующий набор команд, поддержива- ющих работу с логическими данными: and операнду!,операнду — операция логического умножения. Команда выполняет поразрядно логическую операцию И (конъюнкцию) над битами операндов операнду! и операнд_2. Результат записывается на место опе- ранд^ 7. or операнду,операнду — операция логического сложения. Команда выполняет поразрядно логическую операцию ИЛИ (дизъюнкцию) над
190 Урок 9. Логические команды битами операндов операнд_1 и операнд_2. Результат записывается на место операнд_1. хог операнд_1,операнд_2 — операция логического исключающего сложения. Команда выполняет поразрядно логическую операцию исключающего ИЛИ над битами операндов операнд_1 и операнд_2. Результат записывается на место операнд^ 1. test операнд- 1,операнд_2 — операция «проверить» (способом логического умножения). Команда выполняет поразрядно логическую операцию И над битами операндов операнд_1 и операнд_2. Состояние операндов остается прежним, изменяются только флаги zf, sf, и pf, что дает возможность анализировать состояние отдельных битов операнда без изменения их состояния. not операнд - операция логического отрицания. Команда выполняет по- разрядное инвертирование (замену значения на обратное) каждого бита операнда. Результат записывается на место операнда. Для представления роли логических команд в системе команд микропроцессо- ра очень важно понять области их применения и типовые приемы их использо- вания при программировании. Далее мы будем рассматривать логические ко- манды в контексте обработки последовательности бит. Очень часто некоторая ячейка памяти должна играть роль индикатора, показы- вая, например, занятость некоторого программного или аппаратного ресурса. Так как эта ячейка может принимать только два значения — занято (1) или свободно (0), то отводить под нее целый байт очень расточительно, логичнее для этой цели использовать бит. А если таких индикаторов много? Если их объединить в пределах одного байта или слова, то может получиться довольно существенная экономия памяти. Посмотрим, что могут сделать для этого ло- гические команды. Для лучшего усвоения данного материала можно к абст- рактному мышлению добавить наглядное представление. Возьмите, к примеру, елочную гирлянду. Очень красиво смотрятся гирлянды, управляемые прерыва- телями, особенно если они работают по некоторому алгоритму. Вы можете представить себе прерыватель для гирлянды из 8, 16 или 32 лампочек. Естест- венным является подход, при котором каждой лампочке соответствует опреде- ленный бит 8, 16 или 32-битового поля. Для логической завершенности поста- новки данной задачи можно предложить следующий вариант управления: вход прерывателя подключен к одному из доступных для подключения внешних пор- тов ввода-вывода компьютера, на который и будет выдаваться управляющая битовая последовательность. С помощью логических команд возможно выделение отдельных битов в операн- де с целью их установки, сброса, инвертирования или просто проверки на оп- ределенное значение. Для организации подобной работы с битами операнд_2 обычно играет роль маски, С помощью установленных в 1 битов этой маски и определяются нужные для конкретной операции биты операнд-1. Покажем, ка- кие логические команды могут применяться для этой цели.
Логические команды 191 Для установки определенных разрядов (бит) в 1 применяется команда or операнд_1,операнд_2. В этой команде операнд_2, выполняющий роль маски, должен содержать еди- ничные биты на месте тех разрядов, которые должны быть установлены в 1 в операнд_1. or еах, 10b установить 1-й бит в регистре еах Для сброса определенных разрядов (бит) в 0 применяется команда and операнд_1, операнд_2. В этой команде операнд_2, выполняющий роль маски, должен содержать нулевые биты на месте тех разрядов, которые должны быть установлены в 0 в операнд_1. andeax, fffffffdh ;сбросить в 0 1-й бит в регистре еах Команда хог операнд, 1, операнд_2 применяется: О для выяснения того, какие биты в операнд. 1 и операнд_2 различаются; О инвертирования состояния заданных бит в операнд. 1. Интересующие нас биты маски (рперанд_2) при выполнении команды хог долж- ны быть единичными, остальные — нулевыми. хогеах.ЮЬ .инвертировать 1-й бит в регистре еах jz mes ; переход если 1-й бит в al был единичным Для проверки состояния заданных бит применяется команда test операнд_1, операнд_2 (проверить операнд.!). Проверяемые биты операндов маске (операнд_2) должны иметь единичное значение. Алгоритм работы команды test подобен алго- ритму команды and, но он не меняет значения операнд. 1. Результатом команды является установка значения флага нуля zf: О если zf = 0, то в результате логического умножения получился нулевой ре- зультат, то есть один единичный бит маски не совпал с соответствующим единичным битом операнд_1; О если zf = 1, то в результате логического умножения получился ненулевой результат, то есть хотя бы один единичный бит маски совпал с соответствую- щим единичным битом операнд.!. test еах,OOOOOOlOh jz ml ;переход если 4-й бит равен 1 Как видно из примера, для реакции на результат команды test целесообразно использовать команду перехода j nz метка (Jump if Not Zero) — переход, если флаг нуля zf ненулевой, или команду с обратным действием — jz метка (Jump if Zero) — переход, если флаг нуля zf = 0. Следующие две команды позволяют осуществить поиск первого установленно- го в 1 бита операнда. Поиск можно произвести как с начала, так и от конца операнда: bsf операнд_1,операнд_2 (Bit Scaning Forward) — сканирование битов вперед. Команда просматривает (сканирует) биты операнд_2 от младшего
192 Урок 9. Логические команды к старшему (от бита 0 до старшего бита) в поисках первого бита, установленного в 1. Если таковой обнаруживается, в операнд_1 заносится номер этого бита в виде целочисленного значения. Если все биты опе- ранд_2 равны 0, то флаг нуля zf устанавливается в 1, в противном случае флаг zf сбрасывается в 0. mov al,02h bsf bx.al ;bx=1 jz ml ; переход, если al=00h bsr операнд_1,операнд_2 (Bit Scaning Reset) — сканирование битов в обратном порядке. Команда просматривает (сканирует) биты операнд_2 от старшего к младшему (от старшего бита к биту 0) в поисках первого бита, установленного в 1. Если таковой обнаруживается, в операнд_1 заносится номер этого бита в виде целочисленного значения. При этом важно, что позиция первого единичного бита слева отсчитывается все равно относительно бита 0. Если все биты операнд_2 равны 0, то флаг нуля zf устанавливается в 1, в противном случае флаг zf сбрасывается в 0. Листинг 9.1 демонстрирует пример применения команд bsr и bsf. Введите код и исследуйте работу программы в отладчике (в частности, обратите внимание на то, как меняется содержимое регистра Ьх после команд bsf и bsr). Листинг 9.1. Сканирование битов ;prg_9_1.asm masm model small stack 256 .data ;сегмент данных .code ;сегмент кода main: ;точка входа в программу mov ax,@data mov ds,ах .486 хог ах,ах mov al,02h bsf bx.ax jz ml bsr bx,ax ml: ;это обязательно ;bx=1 ;переход, если al=00h mov ax,4c00h стандартный выход int 21h end main
Команды сдвига 193 В последних моделях микропроцессоров Intel в группе логических команд по- явилось еще несколько команд, которые позволяют осуществить доступ к одно- му конкретному биту операнда. Операнд может находиться как в памяти, так и в регистре общего назначения. Положение бита задается смещением бита отно- сительно младшего бита операнда. Значение смещения может задаваться как в виде непосредственного значения, так и содержаться в регистре общего назна- чения. В качестве значения смещения вы можете использовать результаты ра- боты команд bsr и bsf. Все команды присваивают значение выбранного бита флагу cf. bt операнд,смещение_бита (Bit Test) — проверка бита. Команда переносит значение бита в флаг cf. bt ах, 5 ; проверить значение бита 5 jncml ; переход, если бит = О bts операнд,смещение_бита (Bit Test and Set) — проверка и установка бита. Команда переносит значение бита в флаг cf и затем устанавливает проверяемый бит в 1. movax, 10 btspole.ax проверить и установить 10-й бит в pole jc ml ; переход, если проверяемый бит был равен 1 btг операнд,смещение_бита (Bit Test and Reset) — проверка и сброс бита. Команда переносит значение бита в флаг cf и затем устанавливает этот бит в 0. btc операнд,смещение_бита (Bit Test and Convert) — проверка и инвер- тирование бита. Команда переносит значение бита в флаг cf и затем ин- вертирует значение этого бита. Команды сдвига Команды этой группы также обеспечивают манипуляции над отдельными битами операндов, но иным способом, чем логические команды, рассмотренные выше. Все команды сдвига перемещают биты в поле операнда влево или вправо в зависимости от кода операции. Все команды сдвига имеют одинаковую струк- туру: коп операнд, счетчик_сдвигов Количество сдвигаемых разрядов, счетчик_сдвигов, располагается, как видите, на месте второго операнда и может задаваться двумя способами: О статически; предполагает задание фиксированного значения с помощью не- посредственного операнда; О динамически; занесением значения счетчика_сдвигов в регистр cl перед выпол- нением команды сдвига.
194 Урок 9. Логические команды Исходя из размерности регистра cl, понятно, что значение счетчика_сдвигов мо- жет лежать в диапазоне от 0 до 255. Но на самом деле это не совсем так. В целях оптимизации микропроцессор воспринимает только значение пяти младших би- тов счетчика, то есть значение лежит в диапазоне от 0 до 31. В последних моде- лях микропроцессора, в том числе и в микропроцессоре Pentium, есть дополни- тельные команды, позволяющие делать 64-разрядные сдвиги. Мы их рассмотрим чуть позже. Все команды сдвига устанавливают флаг переноса cf. По мере сдвига битов за пределы операнда они сначала попадают во флаг переноса, устанавливая его равным значению очередного бита, оказавшегося за пределами операнда. Куда этот бит попадет дальше, зависит от типа команды сдвига и алгоритма про- граммы. По принципу действия команды сдвига можно разделить на два типа: О команды линейного сдвига; О команды циклического сдвига. Команды линейного сдвига К командам этого типа относятся команды, осуществляющие сдвиг по следующе- му алгоритму: О очередной «выдвигаемый» бит устанавливает флаг cf; О бит, вводимый в операнд с другого конца, имеет значение 0; О при сдвиге очередного бита он переходит во флаг cf, при этом значение пре- дыдущего сдвинутого бита теряется\ Команды линейного сдвига делятся на два подтипа: О команды логического линейного сдвига; О команды арифметического линейного сдвига. К командам логического линейного сдвига относятся следующие: shl операнд,счетчик_сдвигов (Shift Logical Left) — логический сдвиг влево. Содержимое операнда сдвигается влево на количество битов, определяе- мое значением счетчик_сдвигов. Справа (в позицию младшего бита) впи- сываются нули; shr операнд,счетчик_сдвигов (Shift Logical Right) — логический сдвиг вправо. Содержимое операнда сдвигается вправо на количество битов, определяемое значением счетчик_сдвигов. Слева (в позицию старшего, знакового бита) вписываются нули.
Команды сдвига 195 Ha рис. 9.2 показан принцип работы этих команд. Сдвиг влево логический shl Флаге! Сдвиг вправо логический shr 7 6 5 4 3 2 1 0 <---------------------------- Флаге! Рис. 9.2. Схема работы команд линейного логического сдвига Ниже показан фрагмент программы, который выполняет преобразование двух неупакованных BCD-чисел в слове памяти bcd_dig в упакованное BCD-число в регистре al. bcd_dig dw 0905h ;описание неупакованного BCD-числа 95 mov ax,bcd_dig ;пересылка shl ah,4 ;сдвиг влево add al,ah сложение для получения результата: al=95h Команды арифметического линейного сдвига отличаются от команд логического сдвига тем, что они особым образом работают со знаковым разрядом операнда: sal операнд,счетчик_сдвигов (Shift Arithmetic Left) — арифметический сдвиг влево. Содержимое операнда сдвигается влево на количество би- тов, определяемое значением счетчик_сдвигов. Справа (в позицию млад- шего бита) вписываются нули. Команда sal не сохраняет знака, но уста- навливает флаг cf в случае смены знака очередным выдвигаемым битом. В остальном команда sal полностью аналогична команде shl; sar операнд,счетчик_сдвигов (Shift Arithmetic Right) — арифметический сдвиг вправо. Содержимое операнда сдвигается вправо на количество битов, определяемое значением счетчик_сдвигов. Слева в операнд вписы- ваются нули. Команда sar сохраняет знак, восстанавливая его после сдвига каждого очередного бита.
196 Урок 9. Логические команды На рис. 9.3 показан принцип работы команд линейного арифметического сдвига. Сдвиг влево арифметический sal Флаге! Сдвиг вправо арифметический sar 7 6 5 4 3 2 1 0 <----------------------- Флаге! Рис. 9.3. Схема работы команд линейного арифметического сдвига Команды арифметического сдвига позволяют выполнить умножение и деление операнда на степени двойки. Посмотрите на двоичное представление чисел 75 и 150: 75 01001011 150 10010110 Второе число является сдвинутым влево на один разряд первым числом. Если у вас еще есть сомнения, проделайте несколько умножений на 2, 4, 8 и т. д. Аналогичная ситуация — с операцией деления. Сдвигая вправо операнд, мы, фактически, осуществляем операцию деления на степени двойки 2, 4, 8 и т. д. Преимущество этих команд, по сравнению с командами умножения и деле- ния, — в скорости их исполнения микропроцессором, что может пригодиться при оптимизации программы. Команды циклического сдвига К командам циклического сдвига относятся команды, сохраняющие значения сдвигаемых бит. Есть два типа команд циклического сдвига: О команды простого циклического сдвига (рис. 9.4); О команды циклического сдвига через флаг переноса cf (рис. 9.5). К командам простого циклического сдвига относятся: го1 операнд,счетчик_сдвигов (Rotate Left) — циклический сдвиг влево. Содержимое операнда сдвигается влево на количество бит, определяемое
Команды сдвига 197 операндом счетчик_сдвигов. Сдвигаемые влево биты записываются в тот же операнд справа; гог операнд,счетчик_сдвигов — (Rotate Right) циклический сдвиг вправо. Содержимое операнда сдвигается вправо на количество бит, определяемое операндом счетчик_сдвигов. Сдвигаемые вправо биты записываются в тот же операнд слева. Сдвиг влево циклический rol Флаге! Сдвиг вправо циклический гог Флаге! Рис. 9.4. Схема работы команд простого циклического сдвига Как видно из рис. 9.4, команды простого циклического сдвига в процессе своей работы осуществляют одно полезное действие, а именно: циклически сдвигае- мый бит не только вдвигается в операнд с другого конца, но и одновременно его значение становится значением флага cf. К примеру, для того чтобы обме- нять содержимое двух половинок регистра еах, достаточно выполнить следую- щую последовательность команд: mov еах,ffffOOOOh mov cl,16 rol eax,cl Команды циклического сдвига через флаг переноса cf отличаются от команд простого циклического сдвига тем, что сдвигаемый бит не сразу попадает в операнд с другого его конца, а записывается сначала во флаг переноса cf. Лишь следующее исполнение данной команды сдвига (при условии, что она выполняется в цикле) приводит к помещению выдвинутого ранее бита в дру- гой конец операнда (рис. 9.5). К командам циклического сдвига через флаг пе- реноса cf относятся следующие: rcl операнд,счетчик_сдвигов (Rotate through Carry Left) — циклический сдвиг влево через перенос. Содержимое операнда сдвигается влево на количество бит, определяемое операндом счетчик_сдвигов. Сдвигаемые биты поочередно становятся значением флага переноса cf;
198 Урок 9. Логические команды гсг операнд,счетчик-сдвигов (Rotate through Carry Right) — циклический сдвиг вправо через перенос. Содержимое операнда сдвигается вправо на количество бит, определяемое операндом счетчик_сдвигов. Сдвигаемые биты поочередно становятся значением флага переноса cf. Сдвиг влево циклический гс! Флаге! Сдвиг вправо циклический гсг Флаге! Рис. 9.5. Команды циклического сдвига через флаг переноса cf Из рис. 9.5 видно, что при сдвиге через флаг переноса появляется промежуточ- ный элемент, с помощью которого, в частности, можно производить подмену циклически сдвигаемых битов, в частности, рассогласование битовых последо- вательностей. Под рассогласованием битовой последовательности здесь и далее подразумевается действие, которое позволяет некоторым образом локализовать и извлечь нужные участки этой последовательности и записать их в другое место. Например, рассмотрим, как переписать в регистр Ьх старшую половину регистра еах с одновременным ее обнулением в регистре еах: mov сх, 16 ;кол-во сдвигов для еах clc ;сброс флага cf в 0 rcl еах, 1 ;сдвиг крайнего левого бита из еах в cf ГС1 Ьх ;перемещение бита из cf справа в Ьх loop ml ;цикл 16 раз rol еах,16 ;восстановить правую часть еах Команды простого циклического сдвига можно использовать для операций дру- гого рода. К примеру, подсчитаем количество единичных бит в регистре еах: хог dx,dx ;очистка dx для подсчета единичных бит mov сх,32 ;число циклов подсчета cycl: ;метка цикла
Команды сдвига 199 ГОГ eax,1 •«циклический сдвиг вправо на 1 бит j ПС not_one ; переход, если очередной бит в cf ;не равен единице inc dx увеличение счетчика цикла not.one: loop cycl ; переход на метку cycl, если ;значение в сх не равно 0 Этот фрагмент не требует особых пояснений, единственное, что нужно пом- нить, — особенности работы команды цикла loop. В полном объеме она будет рассмотрена на следующем уроке. Команда loop сравнивает значение регистра сх с нулем и, если оно не равно нулю, выполняет уменьшение сх на единицу и передачу управления на метку в программе, указанную в этой команде в качестве операнда. Дополнительные команды сдвига Система команд последних моделей микропроцессоров Intel, начиная с 80386, содержит дополнительные команды сдвига, расширяющие возможности, рас- смотренные нами ранее. Это — команды сдвигов двойной точности-. shld операнд_1,операнд_2,счегчик_сдвигов — сдвиг влево двойной точнос- ти. Команда shld производит замену путем сдвига битов операнда опе- ранд^ влево, заполняя его биты справа значениями битов, вытесняемых из операнд_2 согласно схеме на рис. 9.6. Количество сдвигаемых бит определяется значением счетчик_сдвигов, которое может лежать в диапа- зоне 0...31. Это значение может задаваться непосредственным операндом или содержаться в регистре cl. Значение операнд_2 не изменяется; операкд_1 операнд_2 Рис. 9.6. Схема работы команды shld sh rd операнду!,операнд_2,счетчик_сдвигов — сдвиг вправо двойной точнос- ти. Команда производит замену путем сдвига битов операнда операнду! вправо, заполняя его биты слева значениями битов, вытесняемых из опе- ранд_2 согласно схеме на рис. 9.7. Количество сдвигаемых бит определя- ется значением счетчнк_сдвигов, которое может лежать в диапазоне 0...31. Это значение может задаваться непосредственным операндом или содер- жаться в регистре cl. Значение операнд_2 не изменяется.
200 Урок 9. Логические команды •••210 210 операнд_2 операнду! Флаг cf Рис. 9.7. Схема работы команды shrd Как мы отметили, команды shld и shrd осуществляют сдвиги до 32 разрядов, но за счет особенностей задания операндов и алгоритма работы эти команды можно использовать для работы с полями длиной до 64 бит. Например, рас- смотрим, как можно осуществить сдвиг влево на 16 бит поля из 64 бит. .data pole_l dd 0b21187f5h pole_h dd 45ff6711h .code .386 movcl,16 ; загрузка счетчика сдвига в cl movеах,pole_h shld pole_l,eax,cl shlpole_h, cl ;pole_l=87f50000h; pole_h=6711b211h Рассмотрим еще некоторые наиболее типичные примеры применения этих ко- манд. Отметим следующий момент. Рассмотренные ниже действия, конечно, можно выполнить и множеством других способов, но эти являются самыми быстрыми. Если ваши программы должны работать максимально быстро, то есть смысл потратить время на разбор этих примеров. Примеры работы с битовыми строками Рассогласование битовых строк Наглядный пример рассогласования последовательностей бит — преобразова- ние неупакованного BCD-числа в упакованное BCD-число. Один из вариантов такого преобразования был рассмотрен нами выше при обсуждении команды линейного сдвига shl. Попробуем выполнить подобное преобразование с ис- пользованием команд сдвига двойной точности. В общем случае длина числа может быть произвольной, но при этом нужно учитывать ограничения, кото- рые накладываются используемыми ресурсами микропроцессора. Ограничения
Примеры работы с битовыми строками 201 связаны в основном с тем, что центральное место в преобразовании занимает регистр еах, поэтому если преобразуемое число имеет размер более четырех байт, то его придется делить на части. Но это уже чисто алгоритмическая зада- ча, поэтому в нашем случае предполагается, что неупакованное BCD-число имеет длину 4 байта. Листинг 9.2. Преобразование BCD-числа (вариант 2) ;prg_9_2.asm masm model stack .data len=4 unpck_BCD dig_BCD small 256 label dword db 2,4,3,6 ;длина неупакованного BCD-числа неупакованное BCD-число 6342 pck_BCD dd 0 ;pck_BCD=00006342 .code main: .386 ml: end ;точка входа в программу mov ах,©data mov ds, ax xor ax, ax mov ex, len ;это обязательно mov eax,unpck_BCD shl еах,Д ;убираем нулевую тетраду shld pck_BCD,eax,4 ;тетраду с цифрой ;заносим в поле pck_BCD shl еах,4 ;убираем тетраду с цифрой из еах loop ml ; цикл exit: ; pck_BCD=00006342 mov ax,4c00h int 21h main Команды сдвига двойной точности shld и shrd позволяют осуществлять с макси- мально возможной скоростью вставку битовой строки из регистра в произволь- ное место другой (большей) строки бит в памяти и извлечение битовой подстро- ки из некоторой строки бит в памяти в регистр. В результате этих операций смежные с подстрокой биты по ее обеим сторонам остаются неизменными.
202 Урок 9. Логические команды Вставка битовых строк Рассмотрим пример вставки битовой строки длиной 16 бит, находящейся в ре- гистре еах, в строку памяти str, начиная с ее 8 бита (листинг 9.3). Вставляемая битовая строка выровнена к левому краю регистра еах. Листинг 9.3. Вставка битовой строки 1> ;prg_9_3.asm 2> masm 3> model small 4> stack 256 5> .data 6> bit_str dd 11010111h ;строка для вставки 7> p_str dd OffffOOOOh ; вставляемая подстрока Offffh 8> .code 9> main: ;точка входа в программу 10> mov ах,©data 11> mov ds, ax 12> xor ax,ax 13> .386 ; это обязательно 14> mov eax.P-Str 15> ; правый край места вставки циклически переместить к краю 16> ;строки bit_str (сохранение правого контекста): 17> гог bit_str,8 18> shr bit_str, 16 ; сдвинуть строку вправо на длину подстроки (16 бит) 19> shld bit.st г,еах,16 ; сдвинуть 16 бит 20> rol bit_str, 8 восстановить младшие 8 бит 21> :... 22> exit: ; bi t _s t r=11 f f f f 11 :23> mov ax,4c00h :24> int 21h :25> end main Листинг 9.3 удобно исследовать в отладчике. При его рассмотрении важно по- нять закономерность между значениями, которые задаются в строках 17-20, и теми значениями, которые присутствуют в постановке задачи. Общая методика такой вставки заключается в следующем: О подогнать к правому краю строки младший бит места вставки в этой строке. Делать это нужно командой циклического сдвига, чтобы сохранить правую
Примеры работы с битовыми строками 203 часть исходной строки. Величина сдвига определяется очень просто — это номер начальной позиции места вставки (см. листинг 9.3, строка 17); О сдвинуть исходную строку вправо на количество бит, равное длине вставляе- мой подстроки (строка 18). Эти биты нам больше не нужны, поэтому для сдвига используется команда простого сдвига shr; О командой shld вставить вставляемую подстроку в исходную подстроку. Пе- ред этим, естественно, левый край вставляемой подстроки находится у лево- го края регистра еах (строка 19); О восстановить командой циклического сдвига правую часть исходной строки (строка 20). Наибольшей эффективности при использовании этой программы можно дос- тичь, если оформить используемую в ней последовательность команд в виде макрокоманды. Понятие макрокоманды будет рассматриваться нами на уро- ке 13, но сейчас важно отметить, что в данном случае она позволит нам не за- думываться о настройке строк 17-20 на конкретную вставку. При изучении материала урока 13 вы можете поэкспериментировать с данной программой, разработав на ее основе макрокоманду. Извлечение битовых строк Рассмотрим пример извлечения 16 битов из строки в памяти bit_str, начиная с бита 8, в регистр еах (листинг 9.4). Результат следует выровнять по правому краю регистра еах; строка bit_st г не изменяется. Этот пример можно рассмат- ривать как обратный тому, который мы только что привели в листинге 9.3. Методика извлечения битовой подстроки, если вы разобрались с программой вставки битовой строки, не должна вызвать у вас трудностей. Листинг 9.4. Извлечение битовой строки ; prg_9_4.asm masm model small stack 256 .data bit_str dd ; строка для извлечения .code main: ;точка входа в программу mov ах,©data mov ds, ах xor ax,ax .386 ; это обязательно продолжение &
204 Урок 9. Логические команды ; левый край места извлечения циклически ; переместить к левому краю ; строки bit_str (сохранение левого контекста) rol bit_str,8 mov ebx,bit_str ; подготовленную строку в ebx shld еах,ebx,16 ; вставить извлекаемые 16 бит ; в регистр еах гог bit—str,8 восстановить старшие 8 бит exit: ;eax=0000ffff mov ax,4c00h int 21h end main Пересылка битов По сути эта программа будет являться комбинацией двух предыдущих. Поэтому попробуйте самостоятельно разработать программу пересылки блока битов из одной битовой строки в другую, взяв за основу только что рассмотренные при- меры (см. листинги 9.3 и 9.4). Подведем некоторые итоги: 0 Минимально адресуемая единица данных в микропроцессоре — байт. Логи- ческие команды позволяют манипулировать отдельными битами. Это един- ственные команды в системе команд микропроцессора, которые позволяют работать на битовом уровне. Этим, в частности, объясняется их важность. 0 Возможность работы на битовом уровне позволяет в отдельных случаях су- щественно сэкономить память, особенно при моделировании различных массивов, содержащих одноразрядные флаги или переключатели. 0 Команды сдвига позволяют выполнять быстрое умножение и деление опе- рандов на степени двойки, а также эффективное преобразование данных. 0 Применение команд циклического сдвига и сдвига двойной точности позво- ляет реализовать максимально быстрые операции по рассогласованию, пере- мещению, вставке и извлечению битовых подстрок.
I УРОК Команды передачи управления □ ПрограммироЕ -ние нелинейных алгоритмов □ Классификация команд передачи управления □ Команды безусловной передачи управления □ Понятие процедуры в языке ассемблера □ Команды условной передачи управления □ Средства организации циклов в языке ассемблера
На предыдущем уроке мы познакомились с некоторыми командами, из кото- рых формируются линейные участки программы. Каждая из них, в общем слу- чае, выполняет некоторые действия по преобразованию или пересылке данных, после чего микропроцессор передает управление следующей команде. Но очень мало программ работают таким последовательным образом. Обычно в про- грамме есть точки, в которых нужно принять решение о том, какая команда будет выполняться следующей. Это решение может быть: О безусловным — в данной точке необходимо передать управление не той ко- манде, которая идет следующей, а другой, которая находится на некотором удалении от текущей команды; О условным — решение о том, какая команда будет выполняться следующей, принимается на основе анализа некоторых условий или данных. Как вы помните, программа представляет собой последовательность команд и данных, занимающих определенное пространство оперативной памяти. Это пространство памяти может быть либо непрерывным, либо состоять из нес- кольких фрагментов. На уроке 5 нами были рассмотрены средства организации фрагментации кода программы и ее данных на сегменты. То, какая команда про- граммы должна выполняться следующей, микропроцессор узнает по содержи- мому пары регистров cs: (e)ip1, в которой: О cs — сегментный регистр кода, в котором находится физический (базовый) адрес текущего сегмента кода; О eip/ip — регистр указателя команды, в котором находится значение, пред- ставляющее собой смещение в памяти следующей команды, подлежащей выполнению, относительно начала текущего сегмента кода. Напомню, поче- му мы записываем регистры eip/ip через косую черту. Какой конкретно ре- гистр будет использоваться, зависит от установленного режима адресации use16 или use32. Если указано use16, то используется ip, если use32, то ис- пользуется eip. Таким образом, команды передачи управления изменяют содержимое регистров cs и eip, в результате чего микропроцессор выбирает для выполнения не следую- 1 На самом деле дело нужно помнить о следующем обстоятельстве. При обсуждении архитектуры микропроцессора мы говорили, что команды извлекаются из памяти заранее в так называемый кон- вейер, поэтому адрес подлежащей выборке команды из памяти и содержимое пары cs e(ip) не одно и то же. Эта пара регистров содержит адрес команды в программе, которая будет выполняться сле- дующей, а не той команды, которая будет выбираться на конвейер.
207 щую по порядку команду программы, а команду в некотором другом участке программы. Конвейер внутри микропроцессора при этом сбрасывается. По принципу действия команды микропроцессора, обеспечивающие организа- цию переходов в программе, можно разделить на три группы: 1. Команды безусловной передачи управления: О команда безусловного перехода; О вызов процедуры и возврата из процедуры; О вызов программных прерываний и возврат из программных прерываний. 2. Команды условной передачи управления: О команды перехода по результату команды сравнения; О команды перехода по состоянию определенного флага; О команды перехода по содержимому регистра есх/сх. 3. Команды управления циклом: О команда организации цикла со счетчиком есх/сх; О команда организации цикла со счетчиком есх/сх с возможностью досроч- ного выхода из цикла по дополнительному условию. Возникает вопрос о том, каким образом обозначается то место, куда необходи- мо передать управление. В языке ассемблера это делается с помощью меток. Метка — это символическое имя, обозначающее определенную ячейку памяти, предназначенное для использования в качестве операнда в командах передачи управления. Подобно переменной, транслятор ассемблера присваивает любой метке три ат- рибута: О имя сегмента кода, где эта метка описана; О смещение — расстояние в байтах от начала сегмента кода, в котором описа- на метка; О тип метки, или атрибут расстояния. Последний атрибут может принимать два значения: О near — переход на эту метку возможен только в пределах сегмента кода, где эта метка описана. Физически это означает, что для перехода на метку дос- таточно изменить только содержимое регистра eip/ip; О far — переход на эту метку возможен только в результате межсегментной передачи управления, для осуществления которой требуется изменение как содержимого регистра eip/ip, так и регистра cs. Метку можно определить двумя способами: О оператором : (двоеточие); О директивой label.
208 Урок 10. Команды передачи управления Синтаксис первого способа показан на рис. 10.1. имволическое имя Команда ассемблера Рис. 10.1. Синтаксис описания метки оператором : С помощью этого способа можно определить метку только ближнего типа — near. Символическое имя в программе может быть определено только один раз. Определенную таким образом метку можно использовать в качестве операнда в командах условного перехода jcc и безусловного перехода jmp, call. Эти ко- манды, естественно, должны быть в сегменте кода, где определена метка. Ко- манда ассемблера может находиться как на одной строке с меткой, так и на следующей. Второй способ определения меток в программе использует директиву label (рис. 10.2). Символическое имя label |—|Тип метки]— Рис. 10.2. Синтаксис директивы label На рис. 10.2 тип метки принимает значения near или far. Обычно директиву label используют для определения идентификатора заданного типа. Например, следующие описания меток ближнего типа эквивалентны: ml: mov ах, polel и ml label near mov ax, polel Понятно, что метка может быть только одного типа — либо near, либо far. Если возникает необходимость использовать для одной и той же команды метку и дальнего, и ближнего типов, то в этом случае необходимо определить две метки, причем метку дальнего типа нужно описать, используя директиву label, как показано в следующем фрагменте: public m_far ; сделать метку m_far видимой ;для внешних программ m_far label far определение метки дальнего типа m_far m_near: определение метки ближнего типа n_far mov ах, pole_1
209 Определив для команды mov ax,pole_1 две метки, можно организовывать переход на эту команду как из данного сегмента команд, так и из других сегментов ко- манд, в том числе принадлежащих другим модулям. Для того чтобы сделать ви- димым извне имя метки m_far, применяется директива public. К более подроб- ному описанию этой директивы мы еще вернемся. Другой часто встречающийся случай применения директивы label — это орга- низация доступа к одной области памяти, как к области, содержащей данные разных типов, например: mas_b label byte mas_w dw 15 dup (?) ; в этом фрагменте оба идентификатора относятся к одной области ; памяти и дают возможность работать с ней, используя разные ; имена, либо как с байтовым массиве*, либо как с массивом слов mov mas_b+10, al ; запись из al в массив ; байтов (в 11-й байт) mov mas_w, ах ; запись из ах в первое ; слово области mas_w Введем еще одно очень важное понятие ассемблера, имеющее прямое отноше- ние к меткам, — счетчик адреса команд. Мы уже упоминали о нем на первых уроках и говорили, что транслятор ассемблера обрабатывает исходную про- грамму, написанную пользователем, последовательно — команду за командой. При этом он ведет счетчик адреса команд, который для первой исполняемой команды равен нулю, а далее, по ходу обработки очередной команды трансля- тором, он увеличивается на длину этой команды. По сути, счетчик адреса ко- манд — это смещение конкретной команды относительно начала сегмента кода. Таким образом, каждая команда во время трансляции имеет адрес, равный зна- чению счетчика адреса команд. Обратитесь к уроку 4 и еще раз посмотрите на приведенный в нем листинг 4.1. Первая колонка в листинге — номер строки листинга. Вторая колонка (или третья, если присутствует колонка с уровнем вложенности) — смещение команды относительно начала сегмента кода или, как мы сейчас определили, счетчик адреса. Значение, на которое он увеличива- ется по мере обработки ассемблером очередной строки исходной программы, равно значению длины машинной команды в этой строке. Исходя из этого, ясно, почему счетчик адреса растет только после тех строк исходной програм- мы, которые генерируют некоторое машинное представление (в том числе пос- ле директив резервирования и инициализации данных в сегменте данных).
210 Урок 10. Команды передачи управления Транслятор ассемблера обеспечивает нам две возможности работы с этим счет- чиком: О Использование меток, атрибуту смещения которых транслятор присваивает значение счетчика адреса той команды, перед которой они появились. О Применение специального символа $ для обозначения счетчика адреса ко- манд. Этот символ позволяет в любом месте программы использовать чис- ленное значение счетчика адреса. Классический пример: .data ; вычисление длины строки в сегменте данных Str.Mes db “Работаешь на ПК - изучи ассемблер” Len_Msg=$-Str_Mes После ассемблирования значение Len_Msg будет равно длине строки, так как значение символа $ в месте его появления отличается от Str_Mes ровно на дли- ну строки. Кроме возможности получения значения счетчика адреса, компилятор TASM позволяет, при необходимости, установить счетчик адреса в нужное абсолют- ное значение. Это делается с помощью директивы ORG: ORG выражение - задает значение счетчика адреса. Выражение должно быть таким, чтобы ассемблер мог преобразовать его к абсолютному числу при первом проходе трансляции. К примеру, эту директиву всегда используют при создании исполняемого файла с типом . сот. В контексте нашего обсуждения поясним, в чем здесь суть. Мы обсуж- дали сегментацию и разделение программы на сегменты. Программа в СОМ-фор- мате состоит из одного сегмента величиной не более 64 Кбайт. Сегментные регис- тры cs, ds содержат одно и то же значение физического адреса, a ss указывает на конец этого единственного сегмента. Программа-загрузчик операционной системы, считывая с диска исполняемые файлы типов . ехе и . сот, производит определен- ные действия. В частности, настраивает перемещаемые адреса программы на их конкретные физические значения. Кроме того, к началу каждой исполняемой про- граммы в памяти добавляется специальная область величиной 256 байт (100h) — префикс программного сегмента (PSP). Он предназначен для хранения различной информации о загруженном исполняемом модуле. Для программ формата .сот блок PSP находится в начале сегмента размером в 64 Кбайт. В исходной програм- ме, для которой планируется формат исполняемого файла . сот, мы должны пре- дусмотреть место для блока PSP, что и делается директивой org 100h. Чтобы закончить разговор о файлах этого типа, разберемся с тем, как получить исполняемый модуль формата .сот. Трансляция программы выполняется как обычно. Далее возможны два варианта действий: О использование утилиты Tlink с опцией /t: tlink /t имя_объектного_файла
Безусловные переходы 211 Этот вариант подходит только в том случае, если вы правильно оформили исходный текст программы для формата . сот. Кроме использования директи- вы org 100h это предполагает: • отсутствие разделения сегментов данных и стека, то есть данные описа- ны в сегменте кода и для их обхода необходимо использовать команду безусловного перехода jmp; • использование директивы assume для указания транслятору на необходи- мость связать содержимое регистров ds и ss с сегментом кода; codeseg segment para “code” assume cs: codeseg,ds:codeseg,ss:codeseg org lOOh jmp ml ; здесь описываем данные ml: ; далее идут команды программы О использование специальной утилиты ехе2Ып. Эта утилита позволяет преобра- зовать уже полученный ранее исполняемый модуль в формате . ехе в формат .сот: exe2bin имя_файла_ехе имя_файла_сом.сот Этот вариант не требует специального оформления исходного текста програм- мы. Единственным требованием является то, чтобы исходный текст был дос- таточно мал по объему. Безусловные переходы Предыдущее обсуждение выявило некоторые детали механизма перехода. Ко- манды перехода модифицируют регистр указателя команды eip/ip и, возмож- но, сегментный регистр кода cs. Что именно должно подвергнуться модифика- ции, зависит: О от типа операнда в команде безусловного перехода (ближний или дальний); О от указания перед адресом перехода (в команде перехода) модификатора; при этом сам адрес перехода может находиться либо непосредственно в команде (прямой переход), либо в регистре или ячейке памяти (косвенный переход). Модификатор может принимать следующие значения: near ptr — прямой переход на метку внутри текущего сегмента кода. Модифицируется только регистр eip/ip (в зависимости от заданного типа сегмента кода use16 или use32) на основе указанных в команде адреса (метки) или выражения, использующего символ извлечения зна- чения счетчика адреса команд — $.
212 Урок 10. Команды передачи управления far ptr — прямой переход на метку в другом сегменте кода. Адрес перехо- да задается в виде непосредственного операнда или адреса (метки) и со- стоит из 16-битного селектора и 16/32-битного смещения, которые загру- жаются, соответственно, в регистры cs и ip/eip. word ptr — косвенный переход на метку внутри текущего сегмента кода. Модифицируется (значением смещения из памяти, по указанному в команде адресу или из регистра) только eip/ip. Размер смещения 16 или 32 бит. dword ptr — косвенный переход на метку в другом сегменте кода. Модифи- цируются (значением из памяти — и только из памяти, из регистра нельзя) оба регистра, cs и eip/ip. Первое слово/двойное слог^ этого адре- са представляет смещение и загружается в ip/eip; второе/третье слово загружается в cs. Команда безусловного перехода jmp Синтаксис команды безусловного перехода jmp [модификатор] адрес_перехода — безусловный переход без сохранения ин- формации о точке возврата. Адрес__перехода представляет собой адрес в виде метки либо адрес области памяти, в которой находится указатель перехода. Всего в системе команд микропроцессора есть несколько кодов машинных ко- манд безусловного перехода j mp. Их различия определяются дальностью пере- хода и способом задания целевого адреса. Дальность перехода определяется местоположением операнда адрес_перехода. Этот адрес может находиться в те- кущем сегменте кода или в некотором другом сегменте. В первом случае пере- ход называется внутрисегментным, или близким, во втором — межсегментным, или дальним. Внутрисегментный переход предполагает, что изменяется только содержимое регистра eip/ip. Можно выделить три варианта внутрисегментного использова- ния команды jmp: О прямой короткий; О прямой; О косвенный. Прямой короткий внутрисегментный переход применяется, когда расстояние от команды jmp до адреса_перехода не более чем -128 или +127 байт. В этом слу- чае транслятор ассемблера формирует машинную команду безусловного перехо- да длиной всего 2 байта (размер обычной команды внутрисегментного безуслов- ного перехода составляет 3 байта). Первый байт в этой команде — код операции значение которого говорит о том, что микропроцессор должен осо- бым образом трактовать второй байт команды. Значение второго байта вычис-
Безусловные переходы 213 ляется транслятором как разность между значением смещения команды, следую- щей за j mp, и значением адреса перехода. При осуществлении прямого короткого перехода нужно иметь в виду следующий тонкий момент, связанный с местопо- ложением адреса перехода и самой команды j mp. Если адрес перехода располо- жен до команды jmp то ассемблер формирует короткую команду безусловного перехода без дополнительных указаний. В случае расположения адреса перехода после команды jmp транслятор не может сам определить, что переход короткий, так как у него еще нет информации об адресе перехода. Для оказания помощи компилятору в формировании команды короткого безусловного перехода в допол- нение к вышерассмотренным используют модификатор short pt г: jmp short ptr ml ... ;не более 35-40 команд (127 байт) ml: или ml: ...;не более 35-40 команд (-128 байт) jmp ml Прямой внутрисегментный переход отличается от прямого короткого внутри- сегментного перехода тем, что длина машинной команды jmp в этом случае со- ставляет 3 байт. Увеличение длины связано с тем, что поле адреса перехода в машинной команде jmp расширяется до двух байт, а это, в свою очередь, позво- ляет производить переходы в пределах 64 Кбайт относительно следующей за jmp команды. ш1: ...; расстояние более 128 байт и менее 64 Кбайт jmp ml Косвенный внутрисегментный переход подразумевает «косвенность» задания ад- реса перехода. Это означает, что в команде указывается не сам адрес перехода, а место, где он «лежит». Приведем несколько примеров, в которых двухбайтовый адрес перехода выбирается либо из регистра, либо из области памяти. lea bx.ml jmp bx ; адрес перехода в регистре bx ml: .data addrjnl dw ml
214 Урок 10. Команды передачи управления .code jmp addr_m1 ; адрес перехода в ячейке памяти addrjnl ml: Приведем еще несколько вариантов косвенного внутрисегментного перехода. <1> <2> .data <з> addr dw ml <4> dw m2 <5> <б> .code <7> < 8> cycl: < 9> mov si, О < ю> jmp addr[si] <и> < 12> mov si, 2 < 13> jmp cycl <14> ml: <15> <1б> m2: <17> <18> ; адрес перехода в слове памяти addr+(si) В этом примере одна команда jmp (строка 10) может производить переходы на разные метки. Выбор конкретной метки перехода определяется содержимым si. Операнд команды jmp определяет адрес перехода косвенно, после вычисле- ния выражения addr+(si). < 2> .data < з> addr dw ml <4> < 5> .code <б> < 7> lea si,addr < 8> jmp near ptr[si] ;адрес перехода в ячейке памяти addr <9> < ю> ml:
Безусловные переходы 215 В данном случае указание модификатора near ptr обязательно, так как, в отли- чие от предыдущего способа, адрес ячейки памяти add г с адресом перехода транслятору передается неявно (строки 3, 7 и 8) и, не имея информации о мет- ке, он не может определить, какой именно вид перехода осуществляется — внутрисегментный или межсегментный. Межсегментный переход предполагает другой формат машинной команды jmp. При осуществлении межсегментного перехода кроме регистра eip/ip модифицируется также и регистр cs. Анало- гично внутрисегментному переходу, межсегментный переход поддерживают два варианта команд безусловного перехода: прямой и косвенный. Команда прямого межсегментного перехода имеет длину пять байт, из которых два байта составляют значение смещения и два байта — значение сегментной составляющей адреса. seg_1 segment jmp far ptr m2 ;здесь far обязательно ml label far seg_1 ends seg_2 segment m2 label far jmp ml ;здесь far необязательно Рассматривая этот пример, обратите внимание на использование модификато- ров far ptr в команде jmp. Обязательность их задания определяется все той же логикой работы однопроходного транслятора. Если описание метки встречает- ся в исходном тексте программы раньше, чем соответствующая ей команда пе- рехода (метка ml), то задание модификатора необязательно, так как транслятор все знает о данной метке и сформирует нужную пятибайтовую форму команды безусловного перехода. В случае, когда команда перехода встречается до описа- ния соответствующей метки, транслятор не имеет еще никакой информации о метке и модификатор far ptr в команде jmp опускать нельзя, так как трансля- тор не знает, какую форму команды формировать — трехбайтовую или пяти- байтовую. Без специального указания модификатора транслятор будет форми- ровать трехбайтовую команду внутрисегментного перехода. Команда косвенного межсегментного перехода в качестве операнда имеет адрес области памяти, в которой содержатся смещение и сегментная часть целевого адреса перехода. data segment addrjnl dd ml ;в поле addr_m1 значения смещения- ;и адреса сегмента метки ш1
216 Урок 10. Команды передачи управления data ends code_1 segment jmp ml code_1 ends code_2 segment ml label far mov ax,bx code_2 ends Как вариант косвенного межсегментного перехода необходимо отметить косвен- ный регистровый межсегментный переход. В этом виде перехода адрес перехода указывается косвенно; он содержится в регистре. Это очень удобно для програм- мирования динамических переходов, в которых адрес перехода может изменять- ся на стадии выполнения программы, data segment addrjnl dd ml ;в поле addr_m1 значения смещения ; и адреса сегмента метки ш1 data ends code_1 segment lea bx,addr_m1 jmp dword ptr[bx] • • • • code_1 ends code_2 segment I • • • ml label far mov ax, bx I • • • code_2 ends Таким образом, модификаторы short pt г, near pt г и word pt г применяются для организации внутрисегментных переходов, a far pt г и dword pt г — межсегмент- ных. Для полной ясности нужно еще раз подчеркнуть, что если тип сегмента use32, то в тех местах, где речь шла о регистре ip, можно использовать eip и, соответ- ственно, размеры полей смещения увеличить до 32 бит.
Безусловные переходы 217 Процедуры До сих пор мы рассматривали примеры программ, предназначенные для одно- кратного выполнения. Но приступив к программированию достаточно серьез- ной задачи, вы столкнетесь с тем, что у вас, наверняка, появятся повторяющиеся участки кода. Они могут быть небольшими, а могут занимать и достаточно много места. В последнем случае эти фрагменты будут существенно затруднять чтение текста программы, снижать ее наглядность, усложнять отладку и слу- жить неисчерпаемым источником ошибок. В языке ассемблера есть несколько средств, решающих проблему дублирования участков программного кода. К ним относятся: О механизм процедур; О макроассемблер; О механизм прерываний. На данном уроке нами будут рассмотрены основы механизма процедур. Важ- ность этого вопроса требует рассмотрения его в полном объеме на отдельном уроке (что мы и сделаем на уроке 14). Макроассемблер и прерывания также будут рассмотрены как отдельные вопросы (на уроках 13, 15 и 17). Процедура, часто называемая также подпрограммой, — это основная функцио- нальная единица декомпозиции (разделения на несколько частей) некоторой за- дачи. Процедура представляет собой группу команд для решения конкретной подзадачи и обладает средствами получения управления из точки вызова задачи более высокого уровня и возврата управления в эту точку. В простейшем случае программа может состоять из одной процедуры. Другими словами, процедуру можно определить как правильным образом оформленную совокупность команд, которая, будучи однократно описана, при необходимости может быть вызвана в любом месте программы. Для описания последовательности команд в виде процедуры в языке ассембле- ра используются две директивы: PROC и ENDP. Синтаксис описания процедуры таков (рис. 10.3). имя_процедуры PROC [[модификатор_языка ] язык] [расстояние ] [ARG список—аргументов—] [RETURN список_элементов] команды, директивы ассемблера [имя_процедуры ] ENDP Рис, 10.3. Синтаксис описания процедуры в программе Заголовок процедуры Тело процедуры Конец процедуры
218 Урок 10. Команды передачи управления Из рис. 10.3 видно, что в заголовке процедуры (директиве PROC) обязательным является только задание имени процедуры. Среди большого количества опе- рандов директивы PROC следует особо выделить [расстояние]. Этот атрибут может принимать значения near или far и характеризует возможность обращения к процедуре из другого сегмента кода. По умолчанию атрибут [расстояние] прини- мает значение near. Процедура может размещаться в любом месте программы, но так, чтобы на нее случайным образом не попало управление. Если процедуру просто вставить в общий поток команд, то микропроцессор будет воспринимать команды проце- дуры как часть этого потока и, соответственно, будет осуществлять выполнение команд процедуры. Учитывая это обстоятельство, есть следующие варианты раз- мещения процедуры в программе: О в начале программы (до первой исполняемой команды); О в конце (после команды, возвращающей управление операционной сис- теме); О промежуточный вариант — тело процедуры располагается внутри другой про- цедуры или основной программы. В этом случае необходимо предусмотреть обход процедуры с помощью команды безусловного перехода jmp; О в другом модуле. Размещение процедуры в начале сегмента кода предполагает, что последователь- ность команд, ограниченная парой директив PROC и ENDP, будет размещена до мет- ки, обозначающей первую команду, с которой начинается выполнение програм- мы. Эта метка должна быть указана как параметр директивы END, обозначающей конец программы: model small .stack 100h .data .code my_proc proc near ret my_proc endp start: end start Объявление имени процедуры в программе равнозначно объявлению метки, поэтому директиву PROC в частном случае можно рассматривать как завуали- рованную форму определения метки в программе. Поэтому сама исполняемая
Безусловные переходы 219 программа также может быть оформлена в виде процедуры, что довольно часто и делается с целью пометить первую команду программы, с которой должно на- чаться выполнение. При этом не забывайте, что имя этой процедуры нужно обя- зательно указывать в заключительной директиве END. Такой синтаксис мы уже неоднократно использовали в своих программах. Так, последний рассмотренный фрагмент будет абсолютно эквивалентен следующему: model small .stack 100h .data .code my_proc proc near ret my_proc endp start proc start endp end start В этом фрагменте после загрузки программы в память управление будет переда- но первой команде процедуры с именем main. Размещение процедуры в конце программы предполагает, что последовательность команд, ограниченная директивами PROC и ENDP, будет размещена после команды, возвращающей управление операционной системе. model small .stack 100h .data .code start: mov ax,4c00h int 21 h ; возврат управления операционной системе my_proc proc near ret my_proc endp end start Промежуточный вариант расположения тела процедуры предполагает ее разме- щение внутри другой процедуры или основной программы. В этом случае не-
220 Урок 10. Команды передачи управления обходимо предусмотреть обход тела процедуры, ограниченного директивами PROC и ENDP, с помощью команды безусловного перехода jmp: model small .stack lOOh .data .code start: jmp ml my_proc proc near ret my_proc endp ml: mov ax,4c00h int 21 h ; возврат управления операционной системе end start Последний вариант расположения описаний процедур — в отдельном сегменте кода — предполагает, что часто используемые процедуры выносятся в отдель- ный файл. Файл с процедурами должен быть оформлен как обычный исход- ный файл и подвергнут трансляции для получения объектного кода. Впослед- ствии этот объектный файл утилитой TLINK можно объединить с файлом, где процедуры используются. Использование утилиты TLINK описано на уроке 4. Этот способ предполагает наличие в исходном тексте программы еще кото- рых элементов, связанных с особенностями реализации концепции модульного программирования в языке ассемблера. Поэтому в полном объеме этот способ будет рассмотрен на уроке 14. Как обратиться к процедуре? Так как имя процедуры обладает теми же атри- бутами, что и обычная метка в команде перехода, то обратиться к процедуре, в принципе, можно с помощью любой команды перехода. Но есть одно важное свойство, которое можно использовать благодаря специальному механизму вы- зова процедур. Суть состоит в возможности сохранения информации о контек- сте программы в точке вызова процедуры. Под контекстом понимается инфор- мация о состоянии программы в точке вызова процедуры. В системе команд микропроцессора есть две команды, осуществляющие работу с контекстом. Это команды call и ret: call [модификатор] имя_процедуры — вызов процедуры(подпрограммы). Команда call, подобно jmp, передает управление по адресу с символичес- ким именем имя_процедуры, но при этом в стеке сохраняется адрес возврата. Адрес возврата — это адрес команды, следующей после команды call.
Безусловные переходы 221 ret [число] - возвратить управление вызывающей программе. Команда ret считывает адрес возврата из стека и загружает его в регистры cs и ip/eip, тем самым возвращая управление на команду, следующую в про- грамме за командой call, [число] — необязательный параметр, обозначаю- щий количество элементов, удаляемых из стека при возврате из процеду- ры. Размер элемента определяется хорошо знакомыми нам параметрами директивы segment - use16 или use32 (или соответствующим параметром упрощенных директив сегментации). Если указано use16, то [число] — это значение в байтах; если use32 - в словах. Для команды call, как и для jmp, актуальна проблема организации ближних и дальних переходов. Это видно из формата команды, где присутствует [моди- фикатор]. Как и в случае команды jmp, вызов процедуры командой call может быть: О внутрисегментным — процедура находится в текущем сегменте кода (имеет тип near), и в качестве адреса возврата команда call сохраняет только со- держимое регистра ip/eip, что вполне достаточно для осуществления воз- врата (рис. 10.4); Процедура ближнего типа my_proc: Оперативная память Оперативная память Стек до команды call ту_ргос Стек после команды call ту_ргос Рис. 10.4. Содержимое стека до и после команды вызова процедуры ближнего типа О межсегментным — процедура находится в другом сегменте кода (имеет тип far), и для осуществления возврата команда call должна запомнить содер- жимое обоих регистров cs, и ip/eip. Очередность размещения их в стеке та- кова: сначала cs, затем ip/eip (рис. 10.5). Важно отметить, что одна и та же процедура не может быть процедурой и ближнего, и дальнего типа. Таким образом, если процедура используется в те- кущем сегменте кода, но может вызываться и из другого сегмента программы, то она должна быть объявлена процедурой типа far. Подобно команде jmp, су- ществуют четыре разновидности команды call. Какая именно команда будет
222 Урок 10. Команды передачи управления сформирована, зависит от значения [модификатор] в команде вызова процедуры call и атрибута дальности в описании процедуры. Если процедура описана в начале сегмента данных с указанием дальности в ее заголовке, то при ее вызове [модификатор] можно не указывать: транслятор сам разберется, какую команду call ему нужно формировать. Если же процедура описана после ее вызова, например, в конце текущего сегмента или в другом сегменте, то при ее вызове нужно указать ассемблеру тип вызова, чтобы он мог за один проход правильно сформировать команду call. Значения [модификатор] такие же, как и у команды jmp, за исключени- ем short pt г. Процедура дальнего типа ту_ргос: Оперативная память Стек до команды call my_proc Оперативная память Стек после команды call my_proc Рис. 10.5. Содержимое стека до и после команды вызова процедуры дальнего типа Последний и, наверное, самый важный вопрос, возникающий при работе с про- цедурами, — как правильно передать параметры процедуре и вернуть резуль- тат? Этот вопрос тесно связан с концепцией модульного программирования и подробно будет рассматриваться на уроке 14. Примеры использования процедур вы можете посмотреть в приложении 7. Условные переходы До сих пор мы рассматривали команды перехода с «безусловным» принципом действия, но в системе команд микропроцессора есть большая группа команд, умеющих принимать решение о том, какая команда должна выполняться сле- дующей. Решение принимается в зависимости от определенных условий. Усло- вие определяется выбором конкретной команды перехода. Микропроцессор имеет 18 команд условного перехода (см. «Справочник команд»). Эти команды позволяют проверить: О отношение между операндами со знаком («больше-меньше»);
Условные переходы 223 О отношение между операндами без знака («выше-ниже»)1; О состояниями арифметических флагов zf, sf, cf, of, pf (но не af). Команды условного перехода имеют одинаковый синтаксис: jcc метка_перехода Как видно, мнемокод всех команд начинается с «]» — от слова jump (прыжок), сс — определяет конкретное условие, анализируемое командой. Что касается операнда метка_перехода, то эта метка может находиться только в пределах текущего сегмента кода; межсегментной передачи управления в условных пере- ходах не допускается. В связи с этим отпадает вопрос о модификаторе, который присутствовал в синтаксисе команд безусловного перехода. В ранних моделях микропроцессора (8086, 80186 и 80286) команды условного перехода могли осу- ществлять только короткие переходы — на расстояние от -128 до +127 байт от команды, следующей за командой условного перехода. Начиная с модели микро- процессора 80386, это ограничение снято, но, как видите, только в пределах теку- щего сегмента кода. Для того чтобы принять решение о том, куда будет передано управление ко- мандой условного перехода, предварительно должно быть сформировано усло- вие, на основании которого и будет приниматься решение о передаче управле- ния. Источниками такого условия могут быть: О любая команда, изменяющая состояние арифметических флагов; О команда сравнения стр, сравнивающая значения двух операндов; О состояние регистра есх/сх. Обсудим эти варианты, чтобы разобраться с тем, как работают команды услов- ного перехода. Команда сравнения стр Команда сравнения стр имеет интересный принцип работы. Он абсолютно та- кой же, как и у команды вычитания sub операнду!, операнд_2. Команда стр так же, как и команда sub, выполняет вычитание операндов и устанавливает флаги. Единственное, чего она не делает — это запись результата вычитания на место первого операнда. Синтаксис команды стр: стр операнд_1,операнд_2 (compare) — сравнивает два операнда и по ре- зультатам сравнения устанавливает флаги. Флаги, устанавливаемые командой стр, можно анализировать специальными командами условного перехода. Прежде чем мы их рассмотрим, уделим немно- 2 Термины «больше-меньше* и «выше-ниже* происходят от соответствующих английских терминов «greater-less* и «above-below*. Первые буквы этих терминов входят в состав мнемонических обо- значений соответствующих команд условного перехода. Несмотря на кажущуюся синонимичность этих терминов, на самом деле они отражают тот факт, что соответствующие им команды условного перехода анализируют разные флаги. Смотрите также далее пояснения в тексте.
224 Урок 10. Команды передачи управления го внимания мнемонике этих команд условного перехода (табл. 10.1). Понима- ние обозначений при формировании названия команд условного перехода (эле- мент в названии команды jcc, обозначенный нами сс) облегчит их запоминание и дальнейшее практическое использование. Таблица 10.1. Значение аббревиатур в названии команды jcc Мнемоническое обозначение Английский Русский Тип операндов Ее equal Равно Любые Nn not He Любые Gg greater Больше Числа со знаком LI less Меньше Числа со знаком А а above Выше, в смысле «больше» Числа без знака Bb below Ниже, в смысле «меньше» Числа без знака Таблица 10.2. Перечень команд условного перехода для команды стр операнд_1,операнд_2 Типы операндов Мнемокод команды условного перехода Критерий условного перехода Значения флагов для осществления перехода Любые je операнд_1 = операнд_2 zf = 1 Любые jne операнд_1 <>операнд_2 zf = 0 Со знаком jl/jnge операнд_1 < операнд_2 sf <> of Со знаком jle/jng операнд_1<= операнд_2 sf <> of or zf = 1 Со знаком jg/jnle операнд_1 > операнд_2 sf = of and zf = 0 Со знаком jge/jnl операнд_1=> операнд_2 sf = of Без знака jb/jnae операнд_1 < операнд_2 cf = 1 Без знака jbe/jna операнд_1<= операнд_2 cf = 1 or zf=l Без знака ja/jnbe операнд_1 > операнд_2 cf = 0 and zf = 0 Без знака jae/jnb операнд_1=> операнд_2 cf = 0 Не удивляйтесь тому обстоятельству, что одинаковым значениям флагов соот- ветствуют несколько разных мнемокодов команд условного перехода (они отде- лены друг от друга косой чертой в табл. 10.2). Разница в названии обусловлена желанием разработчиков микропроцессора облегчить использование команд условного перехода в сочетании с определенными группами команд. Поэтому разные названия отражают скорее различную функциональную направлен- ность. Тем не менее, то, что эти команды реагируют на одни и те же флаги, де- лает их абсолютно эквивалентными и равноправными в программе. Поэтому в табл. 10.2 они сгруппированы не по названиям, а по значениям флагов (усло- виям), на которые они реагируют.
Условные переходы 225 В качестве примера применения команды стр рассмотрим фрагмент программы, который обнуляет поле pole_m длиной п байт: model small .stack 100h .data n equ 50 polejn db n dup (?) .code start: xor bx,bx ;bx=0 m1:mov mem[bx],0 dec bx cmp bx, n jne ml exit: mov ax,4c00h int 21 h ; возврат управления операционной системе end start Так как команды условного перехода не изменяют флагов, то после одной ко- манды стр вполне могут следовать несколько команд условного перехода. Это может быть сделано для того, например, чтобы исследовать каждую из альтер- нативных ветвей: «больше», «меньше» или «равно»: .data masdb dup (?) .code cmp mas[si],5 ;сравнить очередной элемент ;массива с 5 je eql ;переход, если элемент mas равен 5 jl low ;переход, если элемент mas меньше 5 jg grt ;переход, если элемент mas больше 5 eql: low: grt:
226 Урок 10. Команды передачи управления Команды условного перехода и флаги Мнемоническое обозначение некоторых команд условного перехода отражает название флага, с которым они работают, и имеет следующую структуру: пер- вой буквой идет символ «j» (jump, переход), вторым — либо обозначение флага, либо символ отрицания «п», после которого стоит название флага. Такая струк- тура команды отражает ее назначение. Если символа «п» нет, то проверяется состояние флага, и, если он равен 1, производится переход на метку перехода. Если символ «п» присутствует, то проверяется состояние флага на равенство 0, и в случае успеха производится переход на метку перехода. Мнемокоды ко- манд, названия флагов и условия переходов приведены в табл. 10.3. Эти ко- манды можно использовать после любых команд, изменяющих указанные флаги. Таблица 10.3. Команды условного перехода и флаги Название флага Номер бита в eflags/flags Команда условного перехода Значение флага для осуществления перехода Флаг переноса cf 1 jc cf = 1 Флаг четности pf 2 J’P pf=l Флаг нуля zf 6 jz zf = 1 Флаг знака sf 7 js sf = 1 Флаг переполнения of И jo of= 1 Флаг переноса cf 1 jnc cf = 0 Флаг четности pf 2 jnp pf = 0 Флаг нуля zf 6 jnz zf = 0 Флаг знака sf 7 jns sf = 0 Флаг переполнения of И jno of = 0 Если внимательно посмотреть на табл. 10.2 и 10.3, видно, что многие команды условного перехода в них являются эквивалентными, так как в основе и тех, и других лежит анализ одинаковых флагов. В листинге 10.1 приведен пример программы, производящей замену в строке символов длиной п байт малых (строчных) букв английского алфавита на большие (прописные). Для осмысленного рассмотрения этого примера вспом- ним коды ASCII, соответствующие этим буквам. Строчные и прописные буквы в таблице ASCII упорядочены по алфавиту. Строчным буквам в этой таблице соответствует диапазон кодов 61h-7ah, а прописным — 41h~5ah. Для того что- бы понять идею, лежащую в основе алгоритма преобразования, достаточно сравнить представления соответствующий прописных и строчных букв в дво- ичном виде: а - ОНО 0001 ... z - 0111 1010 А - 0100 0001... Z - 0101 1010
Условные переходы 227 Как видно из приведенного двоичного представления, для выполнения преобра- зования между строчными и прописными буквами достаточно всего лишь инвер- тировать 5-й бит. Листинг 10.1. Преобразование регистра символов <1> ;prg_10_1.asm <2> model small <3> .stacklOOh <4> .data <5> n equ 10 •.количество символов в stroka <б> strokadb "acvfgrndup" <7> .code <8> start: <9> mov ах,©data <10> mov ds, ax <11> xor ax, ax <12> mov ex, n <13> lea bx.stroka ; адрес stroka в bx <14> ml: mov al,[bx] ; очередной символ из stroka в al <15> cmp al, 61h ; проверить, что код символа не меньше 61h <1б> j’o 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 •.уменьшить значение счетчика в сх <2б> jnz ml ;если сх не 0, то переход на ml <27> exit: <28> mov ax,4c00h <29> int 21 h ;возврат управления операционной системе <30> end start Обратите внимание на строку 25 листинга 10.1. Команда dec уменьшает значе- ние регистра сх на 1. Когда это значение станет равным 0, микропроцессор по результату операции декремента установит флаг zf. Команда в строке 26 ана-
228 Урок 10. Команды передачи управления лизирует состояние этого флага, и пока он не равен 1 (см. табл. 10.3), передает управление на метку ml. Заметьте, что на место этой команды можно было бы поставить команду jne (см. табл. 10.2). Но для анализа регистра сх в системе команд микропроцессора есть отдельная команда, которую мы сейчас и рас- смотрим. Команды условного перехода и регистр есх/сх Архитектура микропроцессора предполагает специфическое использование многих регистров. К примеру, регистр еах/ах/а! используется как аккумулятор, а регистры bp, sp — для работы со стеком. Регистр есх/сх тоже имеет определенное функцио- нальное назначение — он выполняет роль счетчика в командах управления циклами и при работе с цепочками символов. Возможно, что функ- ционально команду условного перехода, связанную с регистром есх/сх, правиль- нее было бы отнести к этой группе команд. Синтаксис этой команды условного перехода таков: jcxz метка_перехода Gump if сх is Zero) — переход, если сх ноль. jecxz метка_перехода (Jump Equal есх Zero) — переход, если есх ноль. Эти команды очень удобно использовать при организации цикла и при работе с цепочками символов. На этом уроке мы разберемся со средствами организации циклов в программах на языке ассемблера и покажем работу команды jcxz/jecxz. Следующий урок будет посвящен цепочечным командам. Нужно отметить огра- ничение, свойственное команде jcxz/jecxz. В отличие от других команд услов- ной передачи управления, команда jcxz/jecxz может адресовать только короткие переходы — на -128 байт или на +127 байт от следующей за ней командой. Организация циклов Цикл, как известно, представляет собой важную алгоритмическую структуру, без использования которой не обходится, наверное, ни одна программа. Организо- вать циклическое выполнение некоторого участка программы можно, к примеру, используя команды условной передачи управления или команду безусловного перехода jmp. Например, подсчитаем количество нулевых байтов в области mas (листинг 10.2). Листинг 10.2. Подсчет числа нулевых элементов <1> ;prg_10_2.asm <2> model small <3> .stack lOOh <4> .data <5> len equ 10 ;количество элементов в mas
Организация циклов 229 <б> 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> mov ex,len ; длину поля mas в ex <12> xor ax, ax <13> xor si, si <14> cycl: <15> jcxz exit проверка ex на 0, если 0, то выход <1б> cmp mas[si],0 ; сравнить очередной элемент mas с 0 <17> jne ml ;если не равно 0, то на ml <18> inc al ; в al - счетчик нулевых элементов <19> ml: <20> inc si ; перейти к следующему элементу <21> dec ex ; уменьшить сх на 1 <22> jmp cycl <23> exit: <24> mov ax,4c00h <25> int 21 h ; возврат управления операционной системе <2б> end start Цикл в листинге 10.2 организован тремя командами, jcxz, dec и jmp (строки 15 и 22). Команда jcxz выполняет здесь две функции: предотвращает выполнение «пу- стого» цикла (когда счетчик цикла в сх равен нулю) и отслеживает окончание цикла после обработки всех элементов поля mas. Команда dec после каждой итерации цик- ла уменьшает значение счетчика в регистре сх на 1. Заметьте, что при такой органи- зации цикла все операции по его организации выполняются «вручную». Но, учиты- вая важность такого алгоритмического элемента, как цикл, разработчики микропроцессора ввели в систему команд группу из трех команд, облегчающую про- граммирование циклов. Эти команды также используют регистр есх/сх как счет- чик цикла. Дадим краткую характеристику этим командам: loop метка_перехода (Loop) — повторить цикл. Команда позволяет орга- низовать циклы, подобные циклам for в языках высокого уровня с авто- матическим уменьшением счетчика цикла. Работа команды заключается в выполнении следующих действий: О декремента регистра есх/сх; О сравнения регистра есх/сх с нулем; если (есх/сх) > 0, то управление передается на метку перехода;
230 Урок 10. Команды передачи управления если (есх/сх) = 0, то управление передается на следующую после loop ко- манду. loope/loopz метка_перехода (Loop till сх <> 0 or Zero Flag = 0) — повторить цикл пока сх <> 0 или zf = 0. Команды loope и loopz — абсолютные синонимы, поэтому используйте ту команду, которая вам больше нравится. Работа команд заключается в выполнении следующих действий: О декремента регистра есх/сх; О сравнения регистра есх/сх с нулем; О анализа состояния флага нуля zf; если (есх/сх) > 0 и zf = 1, управление передается на метку перехода; если (есх/сх) = 0 или zf = 0, управление передается на следующую после loop команду. loopne/loopnz метка_перехода (Loop till сх <> 0 or nonzero flag=O) — повторить цикл, пока сх о 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.
Организация циклов 231 Листинг 10.3. Подсчет нулевых байтов с использованием команд управления циклом <1> ;prg_10_3.asm <2> model small <3> .stacklOOh <4> .data <5> len equ 10 ; количество элементов в mas <б> 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> mov ex, len; длину поля mas в ex <12> xor ax,ax <13> xor si, si <14> jcxz exit проверка сх на 0, если 0, то выход <15> cycl: <1б> cmp mas[si],0 ; сравнить очередной элемент mas c 0 <17> jne ml ;если не равно 0, то на ml <18> inc al ;в al - счетчик нулевых элементов <19> ml: <20> inc si ; перейти к следующему элементу <21> loop cycl <22> exit: <23> mov ax,4c00h <24> int 21 h ;возврат управления операционной системе <25> end start Заметьте, что у команды jcxz в строке 14 осталась только одна функция — не допустить выполнения «пустого» цикла, поэтому несколько изменилось ее мес- то в тексте программы: теперь она стоит перед меткой начала цикла cycl. Из- менение и контроль содержимого регистра сх в процессе выполнения каждой итерации цикла выполняет команда loop (строка 21). Рассмотрим пример, в котором продемонстрируем типичный метод использо- вания команды loopnz. Программа из листинга 10.4 ищет первый нулевой эле- мент в поле mas.
232 Урок 10. Команды передачи управления Листинг 10.4. Программа prg_10_4.asm <1> ; prg_10_4.asm <2> model small <3> .stacklOOh <4> .data <5> len equ 10 ; количество элементов в mas <б> mas db 1,0,9,8,0,7,8,0,2,0 <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> jcxz exit [проверка сх на 0, если 0, то выход <16> mov si,-1 ; готовим si к адресации элементов поля mas <17> cycl: <18> inc si <19> emp 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 <2б> exit: <27> mov ax,4c00h <28> int 21 h ; возврат управления операционной системе <29> end start Программа несложная; интерес в ней представляют строки 20 и 21. Команда loopnz на основании содержимого регистра сх и флага zf принимает решение о продолжении выполнения цикла. Выход из цикла происходит в одном из двух случаев: сх = 0 (просмотрены все элементы поля mas) или zf = 1 (командой стр обнаружен нулевой элемент). Назначение следующей команды jz (строка 21) в том, чтобы распознать конкретную причину выхода из цикла. Если выход из цикла произошел после просмотра строки, в которой нет нулевых элементов, то jz не сработает и будет выдано сообщение об отсутствии нулевых элементов в строке (строки 7, 23-25). Если выход из цикла произошел в результате обнару- жения нулевого элемента, то в регистре si будет номер позиции этого элемента в
Организация циклов 233 поле mas и при необходимости можно продолжить обработку. В нашем случае мы просто завершаем программу — переходим на метку exit. Читатели, имеющие даже небольшой опыт программирования на языках высоко- го уровня, знают, что очень часто возникает необходимость использовать вло- женные циклы. Самый простой пример — обработка двухмерного массива. На уроке 12 мы рассмотрим организацию работы с массивами, в том числе и двух- мерными. Пока же разберемся с основными принципами организации вложен- ных циклов. Основная проблема, которая при этом возникает, — как сохранить значения счетчиков в регистре есх/сх для каждого из циклов. Для временного сохранения счетчика внешнего цикла на время выполнения внутреннего цикла можно использовать несколько способов: задействовать регистры, ячейки памяти или стек. Следующий фрагмент программы содержит три цикла, вложенных один в другой. Этот фрагмент можно рассматривать как шаблон для построения программы с вложенными циклами. <1> <2> mov ex,100 ; количество повторений цикла cycl_1 <3> cycl_1: <4> push ex ; счетчик цикла cycl_1 в стек <5> ; команды цикла cycl_1 <б> mov ex, 50 ; количество повторений цикла сус!_2 <7> cycl_2: <8> push ex ; счетчик цикла сус!_2 в стек <9> ; команды цикла сус!_2 <10> mov ex, 25 ; количество повторений цикла сус!_3 <11> cycl_3: <12> ; команды цикла сус!_3 <13> loop cycl_3 <14> ; команды цикла сус!_2 <15> pop ex ; восстановить счетчик цикла сус!_2 <1б> loop cycl_2 <17> ; команды цикла сус!_1 <18> pop ex ; восстановить счетчик цикла сус!_2 <19> loop cycl_1 <20> В качестве примера рассмотрим фрагмент программы, которая обрабатывает спе- циальным образом некоторую область памяти. Область памяти рассматривается как совокупность пяти полей, содержащих 10 однобайтовых элементов. Требует- ся заменить все нулевые байты в этой области на значение Offh.
234 Урок 10. Команды передачи управления Листинг 10.5. Пример использования вложенных циклов <1> ;prg_10_5.asm <2> model small <3> .stacklOOh <4> .data <5> mas db 1,0,9,8,0,7,8,0,2,0 <б> db 1,0,9,8,0,7,8,0,2,0 <7> db 1,0,9,8,0,7,8,0,2,0 <8> db 1,0,9,8,0,7,8,0,2,0 <9> db 1,0,9,8,0,7,8,0,2,0 <10> .code <11> start: <12> mov ax,@data <13> mov ds, ax <14> xor ax, ax <15> lea bx.mas <1б> mov ex, 5 <17> cycl_1: <18> push ex <19> xor si, si <20> mov ex, 10 <21> cycl_2: <22> cmp byte ptr [bx+si],0 <23> jne no_zero <24> mov byte ptr [bx+si],Offh <25> no_zero: <2б> inc si <27> loop cycl_2 <28> pop ex <29> add bx,10 <30> loop cycl_1 <31> exit: <32> mov ax,4c00h <33> int 21h ; возврат управления операционной системе <34> end start Комментировать листинг 10.5 нет необходимости — он очень прост для пони- мания.
Организация циклов 235 Подведем некоторые итоги: 0 Язык ассемблера (система команд микропроцессора) имеет достаточно пол- ный и гибкий набор средств организации всевозможных переходов как в пределах текущего сегмента кода, так и во внешние сегменты. 0 При организации безусловных переходов возможны переходы как с потерей (jmp), так и с запоминанием (call) информации о точке передачи управле- ния. 0 Принцип работы команд условного перехода основан на том, что микропро- цессор по результатам выполнения некоторых команд устанавливает опре- деленные флаги в регистре eflags/flags. Команды условного перехода ана- лизируют состояние этих флагов и инициируют передачу управления, исходя из результатов анализа. 0 Система команд микропроцессора допускает программирование циклов со счетчиком. Для этого используется регистр есх/сх, в который до входа в цикл должно быть загружено значение счетчика цикла.
: УРОК Цепочечные команды □ Характеристика средств микропроцессора для обработки цепочек элементов в памяти □ Операции пересылки и сравнения цепочек □ Операции для работы с отдельными элементами цепочек
На данном уроке будет рассмотрена чрезвычайно интересная группа команд. Понимание принципов работы этих команд и умение грамотно их использо- вать способны значительно облегчить жизнь программисту, пишущему про- граммы на языке ассемблера. Речь идет о так называемых цепочечных коман- дах. Эти команды также называют командами обработки строк символов. Названия почти синонимичны. Отличие в том, что под строкой символов здесь понимается последовательность байт, а цепочка — это более общее название для случаев, когда элементы последовательности имеют размер больше бай- та — слово или двойное слово. Таким образом, цепочечные команды позволя- ют проводить действия над блоками памяти, представляющими собой последо- вательности элементов следующего размера: О 8 бит, то есть байт; О 16 бит, то есть слово; О 32 бита, то есть двойное слово. Содержимое этих блоков для микропроцессора не имеет никакого значения. Это могут быть символы, числа и все что угодно. Главное, чтобы размерность элементов совпадала с одной из вышеперечисленных и эти элементы находи- лись в соседних ячейках памяти. Всего в системе команд микропроцессора имеется семь операций-примитивов обработки цепочек. Каждая из них реализуется в микропроцессоре тремя ко- мандами, в свою очередь, каждая из этих команд работает с соответствующим размером элемента — байтом, словом или двойным словом. Особенность всех цепочечных команд в том, что они, кроме обработки текущего элемента цепоч- ки, осуществляют еще и автоматическое продвижение к следующему элементу данной цепочки. Перечислим операции-примитивы и команды, с помощью которых они реали- зуются, а затем подробно их рассмотрим: О пересылка цепочки: movs адрес_приемника,адрес_источника movsb movsw movsd
238 Урок 11. Цепочечные команды О сравнение цепочек: crops адрес_приемника,адрес_источника cmpsb cmpsw cmpsd О сканирование цепочки: seas адрес_приемника scasb scasw scasd О загрузка элемента из цепочки: lods адрес_источника lodsb lodsw lodsd О сохранение элемента в цепочке: stos адрес_приемника stosb stosw stosd О получение элементов цепочки из порта ввода-вывода: ins адрес_приемника,номер_порта insb insw insd О вывод элементов цепочки в порт ввода-вывода: outs номер_порта,адрес_источника outbs outws outds Логически к этим командам нужно отнести и так называемые префиксы повто- рения. Вспомните формат машинной команды и его первые необязательные байты префиксов. Один из возможных типов префиксов — это префиксы пов- торения. Они предназначены для использования цепочечными командами. Пре- фиксы повторения имеют свои мнемонические обозначения: гер гере или repz repne или repnz
239 Эти префиксы повторения указываются перед нужной цепочечной командой в поле метки. Цепочечная команда без префикса выполняется один раз. Разме- щение префикса перед цепочечной командой заставляет ее выполняться в цик- ле. Отличия приведенных префиксов в том, на каком основании принимается решение о циклическом выполнении цепочечной команды: по состоянию реги- стра есх/сх или по флагу нуля zf: префикс повторения rep (REPeat). Этот префикс используется с команда- ми, реализующими операции-примитивы пересылки и сохранения элемен- тов цепочек — соответственно, movs и stos. Префикс гер заставляет данные команды выполняться, пока содержимое в есх/сх не станет равным 0. При этом цепочечная команда, перед которой стоит префикс, автоматически уменьшает содержимое есх/сх на единицу. Та же команда, но без префик- са, этого не делает; префиксы повторения гере или repz (REPeat while Equal or Zero). Эти префиксы являются абсолютными синонимами. Они заставляют цепо- чечную команду выполняться до тех пор, пока содержимое есх/сх не равно нулю или флаг zf равен 1. Как только одно из этих условий нарушается, управление передается следующей команде программы. Бла- годаря возможности анализа флага zf, наиболее эффективно эти префик- сы можно использовать с командами crops и seas для поиска отличающихся элементов цепочек; префиксы повторения герпе или repnz (REPeat while Not Equal or Zero). Эти префиксы также являются абсолютными синонимами. Их действие на цепочечную команду несколько отличается от действий префиксов гере/repz. Префиксы герпе/repnz заставляют цепочечную команду цик- лически выполняться до тех пор, пока содержимое есх/сх не равно нулю или флаг zf равен нулю. При невыполнении одного из этих условий работа команды прекращается. Данные префиксы также можно использо- вать с командами crops и seas, но для поиска совпадающих элементов цепочек. Следующий важный момент, связанный с цепочечными командами, заключается в особенностях формирования физического адреса операндов адрес_источника и адрес_приемника. Цепочка-источник, адресуемая операндом адрес_источника, может находиться в текущем сегменте данных, определяемом регистром ds. Цепочка-приемник, адресуемая операндом адрес_приемника, должна быть в до- полнительном сегменте данных, адресуемом сегментным регистром es. Важно отметить, что допускается замена (с помощью префикса замены сегмента) только регистра ds, регистр es подменять нельзя. Вторые части адресов — сме- щения цепочек — также находятся в строго определенных местах. Для цепочки-источника это регистр esi/si (Source Index register — индексный регистр источника). Для цепочки-получателя это регистр edi/di (Destination Index
240 Урок 11. Цепочечные команды register — индексный регистр приемника). Таким образом, полные физические адреса для операндов цепочечных команд следующие: О адрес_источника — пара ds:esi/si; О адрес_приемника — пара es:edi/di. Кстати, вспомните команды Ids и les, которые мы рассматривали на уроке 7. Эти команды позволяют получить полный указатель (сегмент:смещение) на ячейку памяти. Применение их в данном случае очень удобно в силу жесткой регламентации использования регистров для адресации операндов источника и приемника в цепочечных командах. Вы, наверное, обратили внимание на то, что все семь групп команд, реализую- щих цепочечные операции-примитивы, имеют похожий по структуре набор команд. В каждом из этих наборов присутствуют одна команда с явным указа- нием операндов и три команды, не имеющие операндов. На самом деле набор команд микропроцессора имеет соответствующие машинные команды только для цепочечных команд ассемблера без операндов. Команды с операндами транслятор ассемблера использует только для определения типов операндов. После того как выяснен тип элементов цепочек по их описанию в памяти, гене- рируется одна из трех машинных команд для каждой из цепочечных операций. По этой причине все регистры, содержащие адреса цепочек, должны быть ини- циализированы заранее, в том числе и для команд, допускающих явное указа- ние операндов. В силу того, что цепочки адресуются однозначно, нет особого смысла применять команды с операндами. Главное, что вы должны запом- нить, — правильная загрузка регистров указателями обязательно требуется до выдачи любой цепочечной команды. Последний важный момент, касающийся всех цепочечных команд, — это направ- ление обработки цепочки. Есть две возможности: О от начала цепочки к ее концу, то есть в направлении возрастания адресов; О от конца цепочки к началу, то есть в направлении убывания адресов. Как мы увидим ниже, цепочечные команды сами выполняют модификацию реги- стров, адресующих операнды, обеспечивая тем самым автоматическое продвиже- ние по цепочке. Количество байт, на которые эта модификация осуществляется, определяется кодом команды. А вот знак этой модификации определяется значе- нием флага направления df (Direction Flag) в регистре ef lags/f lags: О если df = 0, то значение индексных регистров esi/si и edi/di будет автомати- чески увеличиваться (операция инкремента) цепочечными командами, то есть обработка будет осуществляться в направлении возрастания адресов; О если df = 1, то значение индексных регистров esi/si и edi/di будет автомати- чески уменьшаться (операция декремента) цепочечными командами, то есть обработка будет идти в направлении убывания адресов. Состоянием флага df можно управлять с помощью двух команд, не имеющих операндов:
Пересылка цепочек 241 cld (Clear Direction Flag) — очистить флаг направления. Команда сбрасы- вает флаг направления df в 0. std (Set Direction Flag) — установить флаг направления. Команда устанав- ливает флаг направления df в 1. Это вся информация, касающаяся общих свойств цепочечных команд. Далее мы более подробно рассмотрим каждую операцию-примитив и команды, которые ее реализуют. При этом более подробно мы будем рассматривать одну команду в каждой группе цепочечных команд — команду с операндами. Это — более об- щая команда, в смысле ограничений, накладываемых на типы операндов. Пересылка цепочек Команды, реализующие эту операцию-примитив, производят копирование эле- ментов из одной области памяти (цепочки) в другую. Размер элемента опреде- ляется применяемой командой. Система команд TASM предоставляет програм- мисту четыре команды, работающие с разными размерами элементов цепочки: movs адрес_приемника,адрес_источника (MOVe String) — переслать цепочку; movsb (MOVe String Byte) — переслать цепочку байт; movsw (MOVe String Word) — переслать цепочку слов; movsd (MOVe String Double word) — переслать цепочку двойных слов. Команда movs movs адрес_приемника,адрес_источника Команда копирует байт, слово или двойное слово из цепочки, адресуемой опе- рандом адрес_источника, в цепочку, адресуемую операндом адрес_приемника. Размер пересылаемых элементов ассемблер определяет, исходя из атрибутов идентификаторов, указывающих на области памяти приемника и источника. К примеру, если эти идентификаторы были определены директивой db, то пе- ресылаться будут байты, если идентификаторы были определены с помощью директивы dd, то пересылке подлежат 32-битовые элементы, то есть двойные слова. Выше уже было отмечено, что для цепочечных команд с операндами, к ко- торым относится и команда пересылки movs адрес_приемника, адрес_источника, не существует машинного аналога. При трансляции в зависимости от типа опе- рандов транслятор преобразует ее в одну из трех машинных команд: movsb, movsw или movsd. Сама по себе команда movs пересылает только один элемент, исходя из его типа, и модифицирует значения регистров esi/si и edi/di. Если перед коман- дой написать префикс гер, то одной командой можно переслать до 64 Кбайт данных (если размер адреса в сегменте 16 бит — use16) или до 4 Гбайт данных
242 Урок 11. Цепочечные команды (если размер адреса в сегменте 32 бит — use32). Число пересылаемых элементов должно быть загружено в счетчик — регистр сх (usel6) или есх (use32). Перечис- лим набор действий, которые нужно выполнить в программе для того, чтобы вы- полнить пересылку последовательности элементов из одной области памяти в дру- гую с помощью команды movs. В общем случае этот набор действий можно рассматривать как типовой для выполнения любой цепочечной команды: 1. Установить значение флага df в зависимости от того, в каком направлении будут обрабатываться элементы цепочки — в направлении возрастания или убывания адресов. 2. Загрузить указатели на адреса цепочек в памяти в пары регистров ds: (e)si и es: (e)di. 3. Загрузить в регистр есх/сх количество элементов, подлежащих обработке. 4. Выдать команду movs с префиксом гер. На примере листинга 11.1 рассмотрим, как эти действия реализуются про- граммно. В этой программе производится пересылка символов из одной строки в другую. Строки находятся в одном сегменте памяти. Для пересылки исполь- зуется команда-примитив movs с префиксом повторения гер. Листинг 11.1. Пересылка строк командой movs ;prg_11_1.asm MASM MODEL small STACK 256 .data source db "Тестируемая строка", ;строка-источник dest db 19 DUP (" ") ; строка-приёмник .code assume ds:@data,es:@data main: ;точка входа в программу mov ax,©data ;загрузка сегментных регистров mov ds, ax ;настройка регистров DS и ES ;на адрес сегмента данных mov es, ax old ;сброс флага DF - обработка •.строки от начала к концу lea si,source ;загрузка в si смещения ;строки-источника lea di,dest ;загрузка в DS смещения строки-приёмника mov ex, 20 ;для префикса гер - счетчик повторений (длина строки) rep movs dest,source ;пересылка строки lea dx,dest mov ah,09h •.вывод на экран строки-приёмника
Пересылка цепочек 243 int 21h exit: mov ax,4c00h int 21h end main Команды пересылки байтов, слов и двойных слов Пересылка байтов, слов и двойных слов производится командами movsb, movsw и movsd. Единственной отличительной особенностью этих команд от команды movs является то, что последняя может работать с элементами цепочек любого размера — 8, 16 или 32 бит. При трансляции команда movs преобразуется в одну из трех команд: movsb, movsw или movsd. Выше мы обсуждали, что решение о том, в какую конкретно команду будет произведено преобразование, прини- мается транслятором, исходя из размеров элементов цепочек, адреса которых указаны в качестве операндов команды movs. Что касается адресов цепочек, то для любой из четырех команд они должны формироваться заранее в регистрах esi/si и edi/di. Так не проще ли сразу использовать команды пересылки без операндов?! К примеру, посмотрим, как изменится программа из листинга 11.1 при исполь- зовании команды movsb: .data source db "Пересылаемая строка$"; строка-источник dest db 20 DUP (?) ;строка-приёмник .code ASSUME ds :©data,es:©data main: cld ; сброс флага DF - просмотр строки ; от начала к концу lea si,source ;загрузка в ES строки-источника lea di,dest ;загрузка в DS строки-приёмника mov сх,20 ;для префикса гер - длина строки repmovsb ; пересылка строки Как видим, изменилась только строка с командой пересылки. Отличие в том, что программа из листинга 11.1 может работать с цепочками элементов любой из трех размерностей: 8, 16 или 32 бит, — а последний фрагмент — только с це- почками байт. Далее, как мы и договорились выше, чтобы не загромождать описания, мы будем рассматривать группы команд для операций-примитивов только на примере более общей команды, а вы будете понимать, что на самом деле можно использовать любую из трех команд в соответствующем контексте.
244 Урок 11. Цепочечные команды Сравнение цепочек Команды, реализующие эту операцию-примитив, производят сравнение элемен- тов цепочки-источника с элементами цепочки-приемника. Здесь ситуация с на- бором команд и методами работы с ними аналогична операции-примитиву пере- сылки цепочек. TASM предоставляет программисту четыре команды сравнения цепочек, работающие с разными размерами элементов цепочки: crops адрес_приемника,адрес_источника (CoMPare String) — сравнить строки; cmpsb (CoMPare String Byte) — сравнить строку байт; cmpsw (CoMPare String Word) — сравнить строку слов; cmpsd (CoMPare String Double word) — сравнить строку двойных слов. Команда cmps Синтаксис команды crops: cmps адрес_приемника,адрес_источника Здесь: адрес_источника определяет цепочку-источник в сегменте данных. Адрес цепочки должен быть заранее загружен в пару ds:esi/si; адрес_приемника определяет цепочку-приемник. Цепочка должна нахо- диться в дополнительном сегменте, и ее адрес должен быть заранее загру- жен в пару es:edi/di. Алгоритм работы команды crops заключается в последовательном выполнении вычитания (элемент цепочки-источника - элемент цепочки-получателя) над очередными элементами обеих цепочек. Принцип выполнения вычитания ко- мандой crops аналогичен команде сравнения crop. Она так же, как и crop, произво- дит вычитание элементов, не записывая при этом результата, и устанавливает флаги zf, sf и of. После выполнения вычитания очередных элементов цепочек командой crops индексные регистры esi/si и edi/di автоматически изменяются в соответствии со значением флага df на значение, равное размеру элемента сравниваемых цепочек. Чтобы заставить команду crops выполняться несколько раз, то есть производить последовательное сравнение элементов цепочек, необ- ходимо перед командой crops определить префикс повторения. С командой crops можно использовать префикс повторения repe/repz или repne/repnz: О гере или repz - если необходимо организовать сравнение до тех пор, пока не будет выполнено одно из двух условий: • достигнут конец цепочки (содержимое есх/сх равно нулю); • в цепочках встретились разные элементы (флаг zf стал равен нулю).
Сравнение цепочек 245 О герпе или repnz — если нужно проводить сравнение до тех пор, пока: • не будет достигнут конец цепочки (содержимое есх/сх равно нулю); • в цепочках встретились одинаковые элементы (флаг zf стал равен единице). Таким образом, выбрав подходящий префикс, удобно использовать команду crops для поиска одинаковых или различающихся элементов цепочек. Выбор префикса определяется причиной, которая приводит к выходу из цикла. Таких причин может быть две для каждого из префиксов. Для определения конкрет- ной причины наиболее подходящим является способ, использующий команду условного перехода j cxz. Ее работа заключается в анализе содержимого регист- ра есх/сх, и если оно равно нулю, то управление передается на метку, указан- ную в качестве операнда jcxz. Так как в регистре есх/сх содержится счетчик повторений для цепочечной команды, имеющей любой из префиксов повторе- ния, то, анализируя есх/сх, можно определить причину выхода из зациклива- ния цепочечной команды. Если значение в есх/сх не равно нулю, то это означа- ет, что выход произошел по причине совпадения либо несовпадения очередных элементов цепочек. Существует возможность еще больше конкретизировать информацию о причине, приведшей к окончанию операции сравнения. Сделать это можно с помощью команд условной передачи управления (табл. 11.1 и 11.2). Таблица 11.1. Сочетание команд условной передачи управления с результатами команды cmps (для чисел со знаком) Причина прекращения операции сравнения Команда условного перехода, реализующая переход по этой причине операнД—ИСточник > операнд_приемник jg операнд_источник = операнд_приемник je операнд_источник <> операнд_приемник jne операнд_источник < операнд_приемник Л операнд_источник <= операнд_приемник jle операнд_источник >= операнд_приемник jge Таблица 11.2. Сочетание команд условной передачи управления с результатами команды cmps (для чисел без знака) Причина прекращения операции сравнения Команда условного перехода, реализующая переход по этой причине операнд_источник > операнд_приемник ja операнд_источник = операнд_приемник je операнд_источник <>операнд_приемник jne операнд_источник < операнд_приемник jb операнд_источник <= операнд_приемник jbe операнд_источник >= операнд_приемник jae
246 Урок 11. Цепочечные команды Как определить местоположение очередных совпавших или не совпавших эле- ментов в цепочках? Вспомните, что после каждой итерации цепочечная коман- да автоматически осуществляет инкремент/декремент значения адреса в соот- ветствующих индексных регистрах. Поэтому после выхода из цикла в этих регистрах будут находиться адреса элементов, находящихся в цепочке после (!) элементов, которые послужили причиной выхода из цикла. Для получения ис- тинного адреса этих элементов необходимо скорректировать содержимое индек- сных регистров, увеличив либо уменьшив значение в них на длину элемента цепочки. В качестве примера рассмотрим программу из листинга 11.2, которая сравнивает две строки, находящиеся в одном сегменте. Используется команда crops. Префикс повторения — гере. Листинг 11.2. Сравнение двух строк командой cmps <1> ;prg_11_2.asm <2> MODEL small <3> STACK 256 <4> .data <5> match db Oah.Odh,’Строки совпадают.’, <б> faileddb Oah.Odh, 'Строки не совпадают’, <7> stringl db "0123456789",Oah,Odh,исследуемые строки <8> string2 db "0123406789",’$’ <9> .code <10> ASSUME ds: ©data, es: ©data привязка DS и ES к сегменту данных <11> main: <12> mov ax,©data ; загрузка сегментных регистров <13> mov ds,ax <14> mov es,ax ; настройка ES на DS <15> ; вывод на экран исходных строк stringl и string2 <1б> mov ah,09h <17> lea dx,stringl <18> int 21 h <19> lea dx,string2 <20> int 21 h <21> ; сброс флага DF - сравнение в направлении возрастания адресов <22> cld <23> lea si,stringl ; загрузка в si смещения stringl <24> lea di,string2 ; загрузка в di смещения string2 <25> mov ex,10 ; длина строки для префикса гере <2б> сравнение строк (пока сравниваемые элементы строк равны) <27> ; выход при обнаружении не совпавшего элемента <28> cycl: <29> гере cmps stringl,string2
Сравнение цепочек 247 <30> jcxz equal ;cx=0, то есть строки совпадают <31> jne notjnatch ;если не равны - переход на notjnatch <32> equal: ;иначе, если совпадают, то <33> mov ah, 09h ; вывод сообщения <34> lea dx,match <35> int 21 h <3б> jmp exit ;выход <37> notjnatch: ;не совпали <38> mov ah,09h <39> lea dx,failed <40> int 21 h ; вывод сообщения <4i> ;теперь, чтобы обработать не совпавший элемент в строке, необходимо уменьшить значения регистров si и di <42> dec si <43> dec di <44> ;сейчас в ds:si и es:di адреса несовпавших элементов <45> ;здесь вставить код по обработке несовпавшего элемента <4б> после этого продолжить поиск в строке: <47> inc si <48> inc di <49> jmp cycl <50> exit: ;выход <51> mov ax,4c00h <52> int 21h <53> end main ;конец программы Программа достаточно прозрачна, только два момента, на мой взгляд, требуют пояснения. Это, во-первых, строки 42 и 43, в которых мы скорректировали ад- реса очередных элементов для получения адресов несовпавших элементов. Вы должны понимать, что если сравниваются цепочки с элементами слов или двойных слов, то корректировать содержимое esi/si и edi/di нужно на 2 и 4 байта соответственно. И, во-вторых, строки 47-49. Смысл их в том, что для просмотра оставшейся части строк необходимо установить указатели на следу- ющие элементы строк за последними несовпавшими. После этого можно повто- рить весь процесс просмотра и обработки несовпавших элементов в оставших- ся частях строк. Команды сравнения байтов, слов и двойных слов Аналогично ситуации с набором команд для пересылки цепочки в группе ко- манд сравнения есть отдельные команды сравнения цепочек байт, слов, двой- ных слов — cmpsb, cmpsw и cmpsd соответственно. Для этих команд все рассужде- ния аналогичны тем, что были приведены при обсуждении команд пересылки.
248 Урок 11. Цепочечные команды Ассемблер преобразует команду cmps в одну из машинных команд cmpsb, cmpsw или cmpsd, в зависимости от размера элементов сравниваемых цепочек. Сканирование цепочек Команды, реализующие эту операцию-примитив, производят поиск некоторого значения в области памяти. Логически эта область памяти рассматривается как последовательность (цепочка) элементов фиксированной длины размером 8, 16 или 32 бит. Искомое значение предварительно должно быть помещено в ре- гистр al/ax/eax. Выбор конкретного регистра из этих трех должен быть согла- сован с размером элементов цепочки, в которой осуществляется поиск. Систе- ма команд микропроцессора предоставляет программисту четыре команды сканирования цепочки. Выбор конкретной команды определяется размером элемента: seas адрес_приемника (SCAning String) — сканировать цепочку; scasb (SCAning String Byte) — сканировать цепочку байт; scasw (SCAning String Word) — сканировать цепочку слов; scasd (SCAning String Double Word) — сканировать цепочку двойных слов. Команда seas seas адрес_приемника Команда имеет один операнд, обозначающий местонахождение цепочки в до- полнительном сегменте (адрес цепочки должен быть заранее сформирован в es: edi/di). Транслятор анализирует тип идентификатора адрес_приемника, кото- рый обозначает цепочку в сегменте данных, и формирует одну из трех машинных команд, scasb, scasw или scasd. Условие поиска для каждой из этих трех команд находится в строго определенном месте. Так, если цепочка описана с помощью директивы db, то искомый элемент должен быть байтом и находиться в al, а ска- нирование цепочки осуществляется командой scasb; если цепочка описана с по- мощью директивы dw, то это — слово в ах и поиск ведется командой scasw; если цепочка описана с помощью директивы dd, то это двойное слово в еах, и поиск ведется командой scasd. Принцип поиска тот же, что и в команде сравнения cmps, то есть последовательное выполнение вычитания (содержимое_регистра_аккумулято- ра - содержимое__очередного_элемента_ цепочки). В зависимости от результатов вы- читания производится установка флагов, при этом сами операнды не изменяют- ся. Так же как и в случае команды cmps, с командой seas удобно использовать префиксы repe/repz или repne/repnz:
Сканирование цепочек 249 О гере или repz — если нужно организовать поиск до тех пор, пока не будет выполнено одно из двух условий: • достигнут конец цепочки (содержимое есх/сх равно 0); • в цепочке встретился элемент, отличный от элемента в регистре al/ax/eax. О герпе или repnz — если нужно организовать поиск до тех пор, пока не будет выполнено одно из двух условий: • достигнут конец цепочки (содержимое есх/сх равно 0); • в цепочке встретился элемент, совпадающий с элементом в регистре al/ax/eax. Таким образом, команда seas с префиксом гере/repz позволяет найти элемент цепочки, отличающийся по значению от заданного в аккумуляторе. Команда seas с префиксом герое/repnz позволяет найти элемент цепочки, совпадающий по значению с элементом в аккумуляторе. В качестве примера рассмотрим лис- тинг 11.3, который производит поиск символа в строке. В программе использу- ется команда-примитив seas. Символ задается явно (строка 20). Префикс по- вторения — герпе. Листинг 113. Поиск символа в строке командой seas < i> ; prg_11_3. asm < 2> MASM < з> MODEL small < 4> STACK 256 < 5> .data < б> ;тексты сообщений < 7> fnd db Oah.Odh,’Символ найден! < 8> nochardb Oah.Odh,’Символ не найден < 9> ; строка для поиска < ю> stringdb "Поиск символа в этой строке.",Oah.Odh, <и> .code < 12> ASSUME ds:©data, es:©data < 13> main: <14> mov ax,©data <15> mov ds, ax <16> mov es.ax ; настройка ES на DS <17> mov ah,09h <18> lea dx, string <19> int 21h ; вывод сообщения string <20> mov al, ’a’ ;символ для поиска - "а"(кириллица) <21> cld ; сброс флага df <22> lea di, string ; загрузка в es:di смещения строки <23> mov сх,29 ;для префикса герпе - длина строки продолжение &
250 Урок 11. Цепочечные команды <24> ; поиск в строке (пока искомый символ и символ в строке не совпадут) <25> ; выход при первом совпадении <2б> repne seas string <27> je found ;если равны - переход на обработку, <28> failed: ;иначе выполняем некоторые действия <29> ; вывод сообщения о том, что символ не найден <30> mov ah.09h <31> lea dx,nochar <32> int 21 h ;вывод сообщения nochar <33> jmp exit ;на выход <34> found: ;совпали <35> mov ah,09h <3б> lea dx,fnd <37> int 21 h ;вывод сообщения fnd <38> ;теперь, чтобы узнать место, где совпал элемент в строке, <39> •.необходимо уменьшить значение в регистре di и вставить нужный обработчик <40> ; dec di <41> ... вставьте обработчик <42> exit: ;выход <43> mov ax,4c00h <44> int 21 h <45> end main Сканирование строки байтов, слов, двойных слов Система команд микропроцессора, так же, как и в случае операций-примитивов пересылки и сравнения, предоставляет вам команды сканирования, явно указы- вающие размер элемента цепочки — scasb, scasw или scasd. Помните, что даже если вы этого не делаете, то ассемблер все равно преобразует команду seas в одну из этих трех машинных команд. Загрузка элемента цепочки в аккумулятор Эта операция-примитив позволяет извлечь элемент цепочки и поместить его в регистр-аккумулятор al, ах или еах. Эту операцию удобно использовать вместе с поиском (сканированием) с тем, чтобы, найдя нужный элемент, извлечь его (например, для изменения). Возможный размер извлекаемого элемента опреде- ляется применяемой командой. Программист может использовать четыре ко- манды загрузки элемента цепочки в аккумулятор, работающие с элементами разного размера: lods адрес_источника (LOaD String) — загрузить элемент из цепочки в регистр-аккумулятор al/ax/eax.
Загрузка элемента цепочки в аккумулятор 251 lodsb (LOaD String Byte) — загрузить байт из цепочки в регистр al. lodsw (LOaD String Word) — загрузить слово из цепочки в регистр ах. lodsd (LOaD String Double Word) — загрузить двойное слово из цепочки в регистр еах. Рассмотрим работу этих команд на примере lods. Команда lods lods адрес_источника (LOaD String) — загрузить элемент из цепочки в аккумулятор al/ax/eax. Команда имеет один операнд, обозначающий строку в основном сегменте дан- ных. Работа команды заключается в том, чтобы извлечь элемент из цепочки по адресу, соответствующему содержимому пары регистров ds:esi/si, и поместить его в регистр eax/ax/al. При этом содержимое esi/si подвергается инкременту или декременту (в зависимости от состояния флага df) на значение, равное раз- меру элемента. Эту команду удобно использовать после команды seas, локализу- ющей местоположение искомого элемента в цепочке. Префикс повторения в этой команде может и не понадобиться — все зависит от логики программы. В качестве примера рассмотрим листинг 11.4. Программа сравнивает командой cmps две цепочки байт в памяти stringl и string2 и помещает первый несовпав- ший байт из string2 в регистр al. Для загрузки этого байта в регистр-аккуму- лятор al используется команда lods. Префикса повторения в команде lods нет, так как он попросту не нужен. <1> Листинг 11.4. Использование lods для загрузки байта в регистр al ;prg_11_4.asm <2> <3> <4> <6> <7> <8> <9> <10> <11> <12> <13> <14> <15> <16> <17> MASM MODEL small STACK 256 .data ;строки для сравнения stringl db “Поиск символа в этой строке.",Oah,Odh, string2 db “Поиск символа не в этой строке.”,Oah,Odh,'$’ mes_eqdb “Строки совпадают.",Oah,Odh, '$’ fnd db “Несовпавший элемент в регистре al",Oah,Odh, ’$' .code ; привязка ds и es к сегменту данных assume ds:©data,es:©data main: mov ax,©data ; загрузка сегментных регистров mov ds,ax mov es.ax ; настройка es на ds продолжение &
252 Урок 11. Цепочечные команды <18> mov ah,09h <19> lea dx,stringl <20> int 21h ;вывод stringl <21> lea dx,string2 <22> int 21h ;вывод string2 <23> cld ;сброс флага df <24> lea di,stringl -.загрузка в es:di смещения <25> ;строки stringl <26> lea si,string2 ;загрузка в ds:si смещения <27> ;строки string2 <28> mov ex, 29 ;для префикса гере - длина строки <29> ;поиск в строке (пока нужный символ и символ ;в строке не равны) <30> ;выход - при первом несовпавшем <31> гере cmps stringl,string2 <32> jcxz eql ;если равны - переход на eql <33> jmp no_eq ;если не равны - переход на no_eq <34> eql: ;выводим сообщение о совпадении строк <35> mov ah,09h <3б> lea dx,mes_eq <37> int 21h ;вывод сообщения mes.eq <38> jmp exit ; на выход <39> no_eq: ;обработка несовпадения элементов <40> mov ah,09h <41> lea dx,fnd <42> int 21h ;вывод сообщения fnd <43> ;теперь, чтобы извлечь несовпавший элемент из строки <44> ;в регистр- -аккумулятор, <45> ;уменьшаем значение регистра si и тем самым перемещаемся <4б> ;к действительной позиции элемента в строке <47> dec si •.команда lods использует ds:si-адресацию <48> ;теперь ds: si указывает на позицию в string2 <49> lods string2 •.загрузим элемент из строки в AL <50> •.нетрудно догадаться, что в нашем примере это символ - «н» <51> exit: ;выход <52> mov ax,4c00h <53> int 21h <54> end main Загрузка в регистр al/ax/eax байтов, слов, двойных слов Команды загрузки байта в регистр al (lodsb), слова в регистр ах (lodsw), двой- ного слова в регистр еах (lodsd) аналогичны другим цепочечным командам.
Перенос элемента из аккумулятора в цепочку 253 Они являются вариантами команды lods. Каждая из этих команд работает с це- почками из элементов определенного размера. Предварительно вы должны загрузить длину цепочки и ее адрес в регистры есх/сх и ds:esi/si. Перенос элемента из аккумулятора в цепочку Эта операция-примитив позволяет произвести действие, обратное команде lods, то есть сохранить значение из регистра-аккумулятора в элементе цепочки. Эту операцию удобно использовать вместе с операциями поиска (сканирования) scans и загрузки lods с тем, чтобы, найдя нужный элемент, извлечь его в ре- гистр и записать на его место новое значение. Команды, поддерживающие эту операцию-примитив, могут работать с элементами размером 8, 16 или 32 бит. TASM предоставляет программисту четыре команды сохранения элемента це- почки из регистра-аккумулятора, работающие с элементами разного размера: stos адрес_приемника (STOre String) — сохранить элемент из регистра- аккумулятора al/ax/eax в цепочке; stosb (STOre String Byte) — сохранить байт из регистра al в цепочке; stosw (STOre String Word) — сохранить слово из регистра ах в цепочке; stosd (STOre String Double Word) — сохранить двойное слово из регистра еах в цепочке. Команда stos stos адрес_приемника (STOrage String) — сохранить элемент из регистра- аккумулятора al/ax/eax в цепочке. Команда имеет один операнд адрес_приемника, адресующий цепочку в дополни- тельном сегменте данных. Работа команды заключается в том, что она пересылает элемент из аккумулятора (регистра eax/ax/al) в элемент цепочки по адресу, со- ответствующему содержимому пары регистров es:edi/di. При этом содержимое edi/di подвергаются инкременту или декременту (в зависимости от состояния флага df) на значение, равное размеру элемента цепочки. Префикс повторения в этой команде может и не понадобиться — все зависит от логики программы. Например, если использовать префикс повторения гер, то можно применить команду для инициализации области памяти некоторым фик- сированным значением. В качестве примера рассмотрим листинг 11.5. Программа производит замену в строке всех символов «а» на другой символ. Символ для замены вводится с клавиатуры.
254 Урок 11. Цепочечные команды Листинг 11.5. Замена командой stos символа в строке на вводимый с клавиатуры ;prg_n. _5.asm MASM MODEL small STACK 256 .data ;сообщения fnddb Oah,Odh,'Символ найден’,'$' nochar db Oah,Odh,'Символ не найден. mes1 db Oah, Odh,'Исходная строка string db "Поиск символа в этой строке.",Oah,Odh, '$' ;строка для поиска mes2 db Oah,Odh, 'Введите символ, на который следует заменить найденный' db Oah,Odh,'$' mes3 db Oah,Odh,'Новая строка: .code assume ds:©data,es:©data привязка ds и es ; к сегменту данных main: ; точка входа в программу mov ах,©data ; загрузка сегментных регистров mov ds, ах mov es, ах ; настройка es на ds mov ah,09h lea dx,mes1 int 21 h ;вывод сообщения mes1 lea dx,string int 21h ;вывод string mov al, ’a* ;символ для поиска- "а"(кириллица) cld ; сброс флага df lea di, string ; загрузка в di смещения string mov сх,29 ;для префикса герпе - длина строки ;поиск i в строке string до тех пор, пока ;символ в al и очередной символ в строке ;не равны: выход- при первом совпадении cycl: герпе seas string je found ;если элемент найден то переход на found failed: ; иначе, если не найден, то вывод сообщения nochar mov ah,09h lea dx,nochar int 21h jmp exit ; переход на выход found: mov ah,09h lea dx.fnd
Перенос элемента из аккумулятора в цепочку 255 int 21 h ; вывод сообщения об обнаружении символа ; корректируем di для получения значения действительной позиции совпавшего элемента ; в строке и регистре al dec di new_char: ;блок замены символа mov ah,09h lea dx,mes2 int 21h ; вывод сообщения mes2 ; ввод символа с клавиатуры mov ah.Olh int 21h ; в al - введённый символ stos string ; сохраним введённый символ ;(из al) в строке ; string в позиции старого символа mov ah,09h lea dx,mes3 int 21h ; вывод сообщения mes3 lea dx,string int 21 h ; вывод сообщения string ’.переход на поиск следующего символа "а” в строке inc di ;указатель в строке string на символ, следующий после совпавшего, jmp cycl ;на продолжение просмотра string exit: ; выход mov ax,4c00h int 21h endmain ;конец программы Сохранение в цепочке байта, слова, двойного слова из регистра al/ax/eax Команды stosb, stosw и stosd, аналогично другим цепочечным операциям, явля- ются вариантами команды stos. Каждая из этих команд работает с цепочками из элементов определенного размера. Предварительно вы должны загрузить длину цепочки и ее адрес в регистры есх/сх и es: edi/di. Следующие две команды появились впервые в системе команд микропроцессора 1386. Они позволяют организовать эффективную передачу данных между порта- ми ввода-вывода и цепочками в памяти. Следует отметить, что эти две команды позволяют достичь более высокой скорости передачи данных со скоростью по сравнению с той, которую может обеспечить контроллер DMA (Direct Memory Access — прямой доступ к памяти). Контроллер DMA — это специальная мик- росхема, предназначенная для того, чтобы освободить микропроцессор от управ- ления вводом-выводом больших массивов данных между внешним устройством (диском) и памятью.
256 Урок 11. Цепочечные команды Ввод элемента цепочки из порта ввода-вывода Данная операция позволяет произвести ввод цепочки элементов из порта ввода- вывода и реализуется командой ins, имеющей следующий формат: ins адрес_приемника,номер_порта (Input String) — ввести элементы из порта ввода-вывода в цепочку. Эта команда вводит элемент из порта, номер которого находится в регистре dx, в элемент цепочки, адрес которого определяется операндом адрес_приемника. Несмотря на то, что цепочка, в которую вводится элемент, адресуется указанием этого операнда, ее адрес должен быть явно сформирован в паре регистров es: edi/ di. Размер элементов цепочки должен быть согласован с размерностью порта; он определяется директивой резервирования памяти, с помощью которой выделяет- ся память для размещения элементов цепочки. После пересылки команда ins производит коррекцию содержимого edi/di на величину, равную размеру эле- мента, участвовавшего в операции пересылки. Как обычно, при работе цепочеч- ных команд учитывается состояние флага df. Подобно командам, реализующим рассмотренные выше цепочечные операции- примитивы, транслятор преобразует команду ins в одну из трех машинных команд без операндов, работающих с цепочками элементов определенного раз- мера: insb (INput String Byte) — ввести из порта цепочку байт; insw (INput String Word) — ввести из порта цепочку слов; insd (INput String Double Word) — ввести из порта цепочку двойных слов. К примеру, выведем 10 байт из области памяти pole в порт 5000h. .data pole db 10 dup (” ”) .code push ds pop es ; настройка es на ds mov dx,5000h lea di,pole mov ex, 10 repinsb
Вывод элемента цепочки в порт ввода-вывода 257 Вывод элемента цепочки в порт ввода-вывода Данная операция позволяет произвести вывод элементов цепочки в порт ввода- вывода. Она реализуется командой outs, имеющей следующий формат: outs номер_порта,адрес_источника (Output String) — вывести элементы из цепочки в порт ввода-вывода. Эта команда выводит элемент цепочки в порт, номер которого находится в ре- гистре dx. Адрес элемента цепочки определяется операндом адрес_источника. Несмотря на то, что цепочка, из которой выводится элемент, адресуется указани- ем этого операнда, значение адреса должно быть явно сформировано в паре реги- стров ds: esi/si. Размер структурных элементов цепочки должен быть согласован с размерностью порта. Он определяется директивой резервирования памяти, с помощью которой выделяется память для размещения элементов цепочки. После пересылки команда outs производит коррекцию содержимого esi/si на величи- ну, равную размеру элемента цепочки, участвовавшего в операции пересылки. При этом, как обычно, учитывается состояние флага df. Подобно команде ins транслятор преобразует команду outs в одну из трех ма- шинных команд без операндов, работающих с цепочками элементов определен- ного размера: outsb (OUTput String Byte) — вывести цепочку байт в порт ввода-вывода; outsw (OUTtput String Word) — вывести цепочку слов в порт ввода- вывода; outsd (OUTput String Double Word) — вывести цепочку двойных слов в порт ввода-вывода. В качестве примера рассмотрим фрагмент программы, которая выводит после- довательность символов в порт ввода-вывода, соответствующего принтеру (но- мер 378 (Iptl)). .data str_pech db "Текст для печати" .code mov dx,378h lea di,str_pech mov ex, 16 repoutsb В заключение напомню, что для организации работы с портами недостаточно знать их номера и назначение. Не менее важно знать и понимать алгоритм их
258 Урок 11. Цепочечные команды работы. Эти сведения можно найти в документации на устройство (но, к сожале- нию, далеко не всегда). Подведем некоторые итоги: 0 Система команд микропроцессора имеет очень интересную группу команд, позволяющих производить действия над блоками элементов до 64 Кбайт или 4 Гбайт, в зависимости от установленной разрядности адреса use16 или use32. 0 Эти блоки логически могут представлять собой последовательности элемен- тов с любыми значениями, хранящимися в памяти в виде двоичных кодов. Единственное ограничение состоит в том, что размеры элементов этих бло- ков памяти имеют фиксированный размер 8, 16 или 32 бит. 0 Команды обработки строк предоставляют возможность выполнения семи операций-примитивов, обрабатывающих цепочки поэлементно. 0 Каждая операция-примитив представлена тремя разными машинными ко- мандами и одной псевдокомандой, которая преобразуется транслятором в одну из трех вышеупомянутых машинных команд. Это преобразование про- исходит в зависимости от типа указанных в ней операндов. 0 Микропроцессор всегда предполагает, что строка-приемник находится в до- полнительном сегменте (адресуемом посредством сегментного регистра es), а строка-источник — в сегменте данных (адресуемом посредством сегментного регистра ds). 0 Микропроцессор адресует строку-приемник через регистр edi/di, а строку- источник — через регистр esi/si. 0 Допускается переопределять сегмент для строки-источника, для строки-при- емника этого делать нельзя. 0 Особенность работы цепочечных команд состоит в том, что они автомати- чески выполняют приращение или уменьшение содержимого регистров edi/ di и esi/si в зависимости от используемой цепочечной команды. Что имен- но происходит с этими регистрами, определяется состоянием флага df, кото- рым управляют команды cld и std. Значение, на которое изменяется содер- жимое индексных регистров, определяется типом элементов строки или кодом операции цепочечной команды.
: УРОК Сложные структуры данных □ Понятие сложного типа данных в ассемблере □ Средства ассемблера для создания и обработки сложных структур данных □ Массивы □ Структуры □ Объединения □ Записи
На предыдущих уроках при разработке программ мы использовали данные двух типов: О Непосредственные данные, представляющие собой числовые или символь- ные значения, являющиеся частью команды. Непосредственные данные фор- мируются программистом в процессе написания программы для конкретной команды ассемблера. О Данные, описываемые с помощью ограниченного набора директив резерви- рования памяти, позволяющих выполнить самые элементарные операции по размещению и инициализации числовой и символьной информации. При обработке этих директив ассемблер сохраняет в своей таблице символов информацию о местоположении данных (значения сегментной составляю- щей адреса и смещения) и типе данных, то есть единицах памяти, выделяе- мых для размещения данных в соответствии с директивой резервирования и инициализации данных. Эти два типа данных являются элементарными, или базовыми; работа с ними поддерживается на уровне системы команд микропроцессора. Используя дан- ные этих типов, можно формализовать и запрограммировать практически лю- бую задачу. Но насколько это будет удобно — вот вопрос. Обработка информации, в общем случае, процесс очень сложный. Это косвен- но подтверждает популярность языков высокого уровня. Одно из несомненных достоинств языков высокого уровня — поддержка развитых структур данных. При их использовании программист освобождается от решения конкретных проблем, связанных с представлением числовых или символьных данных, и получает возможность оперировать информацией, структура которой в боль- шей степени отражает особенности предметной области решаемой задачи. В тоже самое время, чем выше уровень такой абстракции данных от конкретного их представления в компьютере, тем большая нагрузка ложится на компилятор с целью создания действительно эффективного кода. Ведь нам уже известно, что в конечном итоге все, написанное на языке высокого уровня, в компьютере будет представлено на уровне машинных команд, работающих только с базовы- ми типами данных. Таким образом, самая эффективная программа — програм- ма, написанная в машинных кодах, но писать сегодня большую программу в машинных кодах — занятие, не имеющее слишком большого смысла.
Массивы 261 С целью облегчения разработки программ в язык ассемблера была введена воз- можность использования нескольких сложных типов данных. Они строятся на основе базовых типов данных, которые являются как бы кирпичиками для их построения. Введение сложных типов данных позволяет несколько сгладить различия между языками высокого уровня и ассемблером. У программиста по- является возможность сочетания преимуществ языка ассемблера и языков вы- сокого уровня (в направлении абстракции данных), что, в конечном итоге, повы- шает эффективность конечной программы. TASM поддерживает следующие сложные типы данных: О массивы; О структуры; О объединения; О записи. Разберемся более подробно с тем, как определить данные этих типов в програм- ме и организовать работу с ними. Массивы Дадим формальное определение: массив — структурированный тип данных, со- стоящий из некоторого числа элементов одного типа. Для того чтобы разобраться в возможностях и особенностях обработки масси- вов в программах на ассемблере, нужно ответить на следующие вопросы: О Как описать массив в программе? О Как инициализировать массив, то есть как задать начальные значения его элементов? О Как организовать доступ к элементам массива? О Как организовать выполнение типовых операций с массивами? Описание и инициализация массива в программе Специальных средств описания массивов в программах ассемблера, конечно, нет. При необходимости использовать массив в программе его нужно модели- ровать одним из следующих способов: О перечислением элементов массива в поле операндов одной из директив опи- сания данных. При перечислении элементы разделяются запятыми. К при- меру: ;массив из 5 элементов. Размер каждого элемента 4 байта: mas dd 1,2,3,4,5
262 Урок 12. Сложные структуры данных О используя оператор повторения dup. К примеру: •«массив из 5 нулевых элементов. Размер каждого элемента 2 байта: mas dw 5 dup (0) Такой способ определения используется для резервирования памяти с целью размещения и инициализации элементов массива; О используя директивы label и rept. Пара этих директив может облегчить опи- сание больших массивов в памяти и повысить наглядность такого описания. Директива rept относится к макросредствам языка ассемблера и вызывает повторение указанное число раз строк, заключенных между директивой и строкой endm. К примеру, определим массив байт в области памяти, обозна- ченной идентификатором mas_b. В данном случае директива label определяет символическое имя mas_b аналогично тому, как это делают директивы резер- вирования и инициализации памяти. Достоинство директивы label в том, что она не резервирует память, а лишь определяет характеристики объекта. В данном случае объект — это ячейка памяти. Используя несколько директив label, записанных одна за другой, можно присвоить одной и той же области памяти разные имена и разный тип, что и сделано в следующем фрагменте: п=0 mas_b label byte mas_w label word rept 4 dw OflfOh endm В результате в памяти будет создана последовательность из четырех слов flfO. Эту последовательность можно трактовать как массив байт или слов в зави- симости от того, какое имя области мы будем использовать в программе — mas_b или mas_w; О использованием цикла для инициализации значениями области памяти, кото- рую можно будет впоследствии трактовать как массив. Посмотрим на приме- ре листинга 12.1, каким образом это делается. Листинг 12.1. Инициализация массива в цикле ; prgJ2J.asm MASM MODEL smal STACK 256 small 256
Массивы 263 .data mesdb Oah.Odh,’Массив- ' masdb i db .code main: mov mov 10 dup (?) 0 ax,©data ds, ax ; исходный массив xor ax, ax ; обнуление ax mov ex, 10 ; значение счетчика цикла в сх mov si,0 ; индекс начального элемента в сх go: ; цикл инициализации mov bh, 1 ; i в bh mov mas[si],bh ; запись в массив i inc i ;инкремент i inc si ; продвижение к следующему ;элементу массива loop go ; повторить цикл ; вывод на экран получившегося массива mov ex, 10 mov si,0 mov ah,09h lea dx.mes int 21 h show: mov ah,02h ; функция вывода значения из al на экран mov dl,mas[si] add dl,30h ; преобразование числа в символ int 21 h inc si loop show exit: mov ax,4c00h •.стандартный выход int 21h end main ;конец программы
264 Урок 12. Сложные структуры данных Доступ к элементам массива При работе с массивами необходимо четко представлять себе, что все элементы массива располагаются в памяти компьютера последовательно. Само по себе такое расположение ничего не говорит о назначении и порядке использования этих элементов. И только лишь программист с помощью составленного им ал- горитма обработки определяет то, как нужно трактовать эту последователь- ность байт, составляющих массив. Так, одну и ту же область памяти можно трактовать как одномерный массив, и одновременно те же самые данные могут трактоваться как двухмерный массив. Все зависит только от алгоритма обра- ботки этих данных в конкретной программе. Сами по себе данные не несут ни- какой информации о своем «смысловом» или логическом типе. Помните об этом принципиальном моменте. Эти же соображения можно распространить и на индексы элементов массива. Ассемблер не подозревает об их существовании и ему абсолютно все равно, каковы их численные смысловые значения. Для того чтобы локализовать опре- деленный элемент массива, к его имени нужно добавить индекс. Так как мы моделируем массив, то должны позаботиться и о моделировании индекса. В языке ассемблера индексы массивов — это обычные адреса, но с ними работа- ют особым образом. Другими словами, когда при программировании на ассемб- лере мы говорим об индексе, то скорее подразумеваем под этим не номер эле- мента в массиве, а некоторый адрес. Давайте еще раз обратимся к описанию массива. К примеру, в программе статически определена последовательность данных: mas dw 0,1,2,3,4,5 Пусть эта последовательность чисел трактуется как одномерный массив. Раз- мерность каждого элемента определяется директивой dw, то есть она равна 2 байт. Чтобы получить доступ к третьему элементу, нужно к адресу массива прибавить 6. Нумерация элементов массива в ассемблере начинается с нуля. То есть, в нашем случае речь, фактически, идет о 4-м элементе массива — 3, но об этом знает только программист; микропроцессору в данном случае все равно — ему нужен только адрес. В общем случае для получения адреса элемента в мас- сиве необходимо начальный (базовый) адрес массива сложить с произведением индекса (номер элемента минус единица) этого элемента на размер элемента массива: база + (индекс*размер элемента) Архитектура микропроцессора предоставляет достаточно удобные программно- аппаратные средства для работы с массивами. К ним относятся базовые и ин- дексные регистры, позволяющие реализовать несколько режимов адресации данных. Используя данные режимы адресации, можно организовать эффектив- ную работу с массивами в памяти. Вспомним эти режимы:
Массивы 265 О индексная адресация со смещением — режим адресации, при котором эффек- тивный адрес формируется из двух компонентов: • постоянного (базового) — указанием прямого адреса массива в виде име- ни идентификатора, обозначающего начало массива; • переменного (индексного) — указанием имени индексного регистра. К примеру: mas dw 0,1,2,3,4.5 mov si, 4 поместить 3-й элемент массива mas в регистр ах: mov ax,mas[si] О базовая индексная адресация со смещением — режим адресации, при кото- ром эффективный адрес формируется максимум из трех компонентов: • постоянного (необязательного компонента), в качестве которого может выступать прямой адрес массива в виде имени идентификатора, обозна- чающего начало массива, или непосредственное значение; • переменного (базового) — указанием имени базового регистра; • переменного (индексного) — указанием имени индексного регистра. Этот вид адресации удобно использовать при обработке двухмерных массивов. Пример использования этой адресации мы рассмотрим ниже при изучении особенностей работы с двухмерными массивами. Напомним, что в качестве базового регистра может использоваться любой из восьми регистров общего назначения. В качестве индексного регистра также можно использовать любой регистр общего назначения, за исключением esp/sp. Микропроцессор позволяет масштабировать индекс. Это означает, что если указать после имени индексного регистра знак умножения «*» с последующей цифрой 2, 4 или 8, то содержимое индексного регистра будет умножаться на 2, 4 или 8, то есть масштабироваться. Применение масштабирования облегчает работу с массивами, которые имеют размер элементов, равный 2, 4 или 8 байт, так как микропроцессор сам производит коррекцию индекса для получения адреса очередного элемента массива. Нам нужно лишь загрузить в индексный регистр значение требуемого индекса (считая от 0). Кстати сказать, возмож- ность масштабирования появилась в микропроцессорах Intel начиная с модели i486. По этой причине в рассматриваемом ниже примере программы стоит ди- ректива . 486. Ее назначение, как и ранее использовавшейся директивы . 386, в том, чтобы указать ассемблеру учитывать дополнительные возможности новых микропроцессоров при формировании машинных команд. В качестве примера использования масштабирования рассмотрим лис- тинг 12.2, в котором просматривается массив, состоящий из слов, и производит- ся сравнение этих элементов с нулем. Выводится соответствующее сообщение.
266 Урок 12. Сложные структуры данных Листинг 12.2. Просмотр массива слов с использованием масштабирования ; prg_.12_2.asm MASM MODEL small STACK 256 . data ; начало сегмента данных ;тексты сообщений: mes1 db "не равен 0!Oah,Odh mes2 db "оавен O!$",Oah,Odh mes3 db Oah,Odh,'Элемент $ ’ masdw 2,7,0,0,1,9,3,6,0,8 ;исходный массив .code .486 main: ;это обязательно ax,©data ds, ax ax, ax ; связка ds с сегментом данных ;обнуление ax prepare: mov ex, 10 esi,0 •.значение счетчика цикла в сх ; индекс в esi compare: mov стр je dx,mas[esi*2] dx,0 equal not_equal: ;первый элемент массива в dx сравнение dx с 0 ;переход, если равно ;не равно mov lea int ah,09h dx,mes3 21 h ; вывод сообщения на экран ah,02h ; вывод номера элемента массива на экран mov dx.si add dl,30h int 21h mov ah,09h lea dx.mesl int 21h inc esi ; на следующий элемент dec сх ; условие для выхода из цикла jcxz exit ;сх=0? Если да - на выход
Массивы 267 jmp compare ;нет - повторить цикл equal: ; равно О mov ah, 09h ; вывод сообщения mes3 на экран lea dx,mes3 int 21h mov ah,02h mov dx.sl add dl,30h int 21h mov ah,09h ; вывод сообщения mes2 на экран lea dx,mes2 int 21h inc esi ; на следующий элемент dec сх ;все элементы обработаны? jcxz exit jmp compare exit: mov ax,4c00h стандартный выход int 21h end main ;конец программы Еще несколько слов о соглашениях: О Если для описания адреса используется только один регистр, то речь идет о базовой адресации и этот регистр рассматривается как базовый: ;переслать байт из области данных, ;адрес которой находится в регистре ebx: mov al,[ebx] О Если для задания адреса в команде используется прямая адресация (в виде идентификатора) в сочетании с одним регистром, то речь идет об индекс- ной адресации. Регистр считается индексным, и поэтому можно использо- вать масштабирование для получения адреса нужного элемента массива: add еах,mas[ebx*4] ;сложить содержимое еах с двойным словом ;в памяти по адресу mas + (ebx)*4 О Если для описания адреса используются два регистра, то речь идет о базово- индексной адресации. Левый регистр рассматривается как базовый, а пра- вый — как индексный. В общем случае это не принципиально, но если мы используем масштабирование с одним из регистров, то он всегда является индексным. Но лучше придерживаться определенных соглашений. Помните,
268 Урок 12. Сложные структуры данных что применение регистров ebp/bp и esp/sp по умолчанию подразумевает, что сегментная составляющая адреса находится в регистре ss. Заметим, что базово-индексную адресацию не возбраняется сочетать с прямой адресацией или указанием непосредственного значения. Адрес тогда будет формироваться как сумма всех компонентов. К примеру: mov ах,mas[ebx][ecx*2] ;адрес операнда равен [mas+(ebx)+(ecx)*2] sub dx,[ebx+8][ecx*4] ’.адрес операнда равен [(ebx)+8+(ecx)*4] Но имейте в виду, что масштабирование эффективно лишь тогда, когда раз- мерность элементов массива равна 2, 4 или 8 байт. Если же размерность эле- ментов другая, то организовывать обращение к элементам массива нужно обычным способом, как описано выше. Рассмотрим пример работы с массивом из пяти трехбайтовых элементов (лис- тинг 11.3). Младший байт в каждом из этих элементов представляет собой не- кий счетчик, а старшие два байта — что-то еще, для нас не имеющее никакого значения. Необходимо последовательно обработать элементы данного массива, увеличив значения счетчиков на единицу. Листинг 12.3. Обработка массива элементов с нечетной длиной ;prg_12_3.asm MASM MODEL small ;модель памяти STACK 256 ;размер стека .data ;начало сегмента данных N=5 ;количество элементов массива mas db 5 dup (3 dup (0)) .code ;сегмент кода main: ;точка входа в программу mov ах,©data mov ds,ах хог ах,ах ;обнуление ах mov si,0 ;0 в si mov сх,N ;N в сх go: mov dl,mas[si] ;первый байт поля в dl inc dl [увеличение dl на 1 (по условию)
Массивы 269 mov mas[si],dl ; заслать обратно в массив add si,3 ; сдвиг на следующий элемент массива loop go ; повтор цикла mov si,0 ; подготовка к выводу на экран mov cx.N show: ; вывод на экран содержимого ; первых байт полей mov dl,mas[si] add dl,30h mov ah,02h int 21h loop show exit: mov ax,4c00h стандартный выход int 21 h end main ;конец программы Двухмерные массивы С представлением одномерных массивов в программе на ассемблере и организа- цией их обработки все достаточно просто. А как быть, если программа должна обрабатывать двухмерный массив? Все проблемы возникают по-прежнему из-за того, что специальных средств для описания такого типа данных в ассемблере нет. Двухмерный массив нужно моделировать. На описании самих данных это почти никак не отражается — память под массив выделяется с помощью дирек- тив резервирования и инициализации памяти. Непосредственно моделирование обработки массива производится в сегменте кода, где программист, описывая алгоритм обработки ассемблеру, определяет, что некоторую область памяти не- обходимо трактовать как двухмерный массив. При этом вы вольны в выборе того, как понимать расположение элементов двумерного массива в памяти: по строкам или по столбцам. Если последовательность однотипных элементов в па- мяти трактуется как двухмерный массив, расположенный по строкам, то адрес элемента (i,j) вычисляется по формуле (база + количество_элементов_в_строке * размер_элемента * i+j) Здесь i s О...п-l указывает номер строки, a j = О...т-l указывает номер столбца. Например, пусть имеется массив чисел (размером в 1 байт) mas(i,j) с размер- ностью 4 х 4 (i = 0...3, j = 0...3): 23 04 05 67 05 06 07 99 67 08 09 23 87 09 00 08
270 Урок 12. Сложные структуры данных В памяти элементы этого массива будут расположены в следующей последова- тельности: 23 04 05 67 05 06 07 99 67 08 09 23 87 09 00 08 Если мы хотим трактовать эту последовательность как двухмерный массив, приведенный выше, и извлечь, например, элемент mas(2, 3) = 23, то, проведя нехитрый подсчет, убедимся в правильности наших рассуждений: Эффективный адрес mas(2, 3) = mas + 4 * 1 * 2 + 3 = mas + 11 Посмотрите на представление массива в памяти и убедитесь, что по этому сме- щению действительно находится нужный элемент массива. Организовать адресацию двухмерного массива логично, используя рассмотрен- ную нами ранее базово-индексную адресацию. При этом возможны два основ- ных варианта выбора компонентов для формирования эффективного адреса: О сочетание прямого адреса, как базового компонента адреса, и двух индекс- ных регистров для хранения индексов: mov ах,mas[ebx][esi] О сочетание двух индексных регистров, один из которых является и базовым, и индексным одновременно, а другой — только индексным: mov ах,[ebx][esi] В программе это будет выглядеть примерно так: •.Фрагмент программы выборки элемента ;массива mas(2,3) и его обнуления .data mas db 23,4,5,67,5,6,7,99,67,8,9,23,87,9,0,8 1=2 j=3 .code mov si,4*1*1 mov di, j mov al,mas[si][di] ;b al элемент mas(2,3) В качестве законченного примера рассмотрим программу поиска элемента в дву- мерном массиве чисел (листинг 12.4). Элементы массива заданы статически. Листинг 12.4. Поиск элемента в двухмерном массиве ;prg_12_4.asm MASM MODEL small
Массивы 271 STACK 256 .data ;матрица размером 2x5 - если ее не инициализировать, то для наглядности она может быть описана так: ;аггау dw 2 DUP (5 DUP (?)) ;но мы ее инициализируем: array dw 1,2,3,4,5,6,7,3,9,О логически это будет выглядеть так: ;аггау= {1 2} {3 4} ; {5 6} ; {7 3} ; {9 0} elem dw 3 ;элемент для поиска failed db Oah.Odh, ’Нет такого элемента в массиве! success db Oah.Odh, 'Такой элемент в массиве присутствует foundtime db ? ; количество найденных элементов fnddb “ раз(а)”,Oah.Odh,’$’ .code main: mov ax,@data mov ds, ax хог ax, ax mov si,0 ; з!=столбцы в матрице mov bx, 0 ; Ьх=строки в матрице mov сх,5 ; число для внешнего цикла (по строкам) external: ; внешний цикл по строкам mov ах,array[bx][si] ;в ах первый элемент матрицы push сх ; сохранение в стеке счётчика внешнего цикла mov сх,2 ; число для внутреннего цикла (по столбцам) mov si, 0 iternal: •.внутренний цикл по строкам inc si ; передвижение на следующий элемент в строке сравниваем содержимое текущего элемента в ах с искомым элементом: cmp ах,elem ;если текущий совпал с искомым, то переход на here для обработки, ; иначе цикл продолжения поиска продолжение &
272 Урок 12. Сложные структуры данных je here ; иначе - цикл по строке сх=2 раз loop iternal here: jcxz move_next ; просмотрели строку? inc foundtime ; иначе увеличиваем счётчик совпавших move.next: продвижение в матрице pop сх восстанавливаем СХ из стека (5) add bx, 1 ; передвигаемся на следующую строку loop external ; цикл (внешний) cmp foundtime, Oh сравнение числа совпавших с О ja eql ;если больше 0, то переход not_equal: ;нет элементов, совпавших с искомым mov ah, 09h ; вывод сообщения на экран mov dx,offset failed int 21h jmp exit ; на выход eql: ;есть элементы, совпавшие с искомым mov ah,09h ; вывод сообщений на экран mov dx,offset success int 21h mov ah,02h mov dl,foundtime add dl,30h int 21h mov ah,09h mov dx,offset fnd int 21h exit: ;выход mov ax,4c00h стандартное завершение программы int 21h end main ;конец программы При анализе работы программы не забывайте, что в языке ассемблера принято элементы массива нумеровать с 0. При поиске определенного элемента массив просматривается от начала и до конца. Программа сохраняет в поле foundtime количество вхождений искомого элемента в массив. В качестве индексных ре- гистров используются si и Ьх.
Массивы 273 Типовые операции с массивами Для демонстрации основных приемов работы с массивами лучше всего подхо- дят программы поиска или сортировки. Рассмотрим одну такую программу, выполняющую сортировку массива по возрастанию (листинг 12.5). Листинг 12.5. Сортировка массива < 1> ; prg_12_5.asm < 2> MASM < 3> MODEL small < 4> STACK 256 < 5> .data <6> mes1 db Oah,Odh, ’Исходный массив - $’,Oah,Odh <7> ; некоторые сообщения <8> mes2 db Oah,Odh,’Отсортированный массив - $’,Oah,Odh <9> п equ 9 количество элементов в массиве, считая с 0 <10> mas dw 2,7,4,0,1,9,3,6,5,8 ;исходный массив <11> tmp dw 0 ; переменные для работы с массивом <12> i dw 0 <13> j dw 0 <14> .code <15> main: <1б> mov ах,©data <17> mov ds, ах <18> xor ах, ах <19> ; вывод на экран исходного массива <20> mov ah,09h <21> lea dx,mes1 <22> int 21h ; вывод сообщения mes1 <23> mov сх, 10 <24> mov si, 0 <25> show_primary: ;вывод значения элементов <2б> ; исходного массива на экран <27> mov dx,mas[si] <28> add dl,30h <29> mov ah,02h <30> int 21 h продолжение
274 Урок 12. Сложные структуры данных <31> add si, 2 <32> loop show_primary <33> <34> ; строки 40-85 программы эквивалентны следующему коду на языке С: <35> ;for (i=0; i<9;1++) <3б> ; for (j <37> if (mas[i]>mas[j]) <38> > {tmp=mas[i]; <39> > mas[i]=mas[j]; <40> mas[j]=tmp;} <41> mov i,0 ; инициализация i <42> ;внутренний ЦИКЛ no j <43> internal: <44> mov j, 9 инициализация j <45> jmp cycl.j; переход на тело цикла <4б> exchange: <47> mov bx, i ;bx=i <48> shl bx, 1 <49> mov ax,mas[bx] ;ax=mas[i] <50> mov bx, j ;bx=j <51> shl bx, 1 <52> cmp ax,mas[bx] ; mas[i] ? mas[j] - сравнение элементов <5^> jle lesser;если mas[i] меньше, то обмен не нужен и ; переход на продвижение далее по массиву <54> ;иначе tmp=mas[i], mas[i]=mas[j], mas[j]=tmp: <55> ;tmp=mas[i] <5б> mov bx,i ; bx=i <57> shl bx, 1 ; умножаем на 2, так как элементы - слова <58> mov tmp.ax;tmp=mas[i] <59> <60> ;mas[i]=mas[j] <б1> mov bx, j ;bx=j <б2> shl bx, 1 ; умножаем на 2, так как элементы - слова <63> mov ax,mas[bx] ;ax=mas[j] <64> mov bx,i ;bx=i <б5> shl bx, 1 ; умножаем на 2, так как элементы - слова <бб> mov mas[bx],ax ;mas[i]=mas[j]
Массивы 275 <67> <68> ; mas[j]=tmp <69> mov bx, j ;bx=j <70> shl bx,1 ; умножаем на 2, так как элементы - слова <71> mov ax,tmp ;ax=tmp <72> mov mas[bx],ax ;mas[j]=tmp <73> lesser: ;продвижение далее по массиву во ;цикле внутреннем <74> dec j < ; j- <75> ;тело цикла no j <76> cycl J: <77> mov ax, j ; ax=j <78> cmp ax, i : сравнить j ? i <79> jg exchange ;если j>i, то переход на обмен <80> ;иначе на внешний цикл по i <81> inc i ; i++ <82> cmp i, n ;сравнить i ? n - прошли до конца массива <83> jl internal ;если i<n продолжение обработки <84> <85> ;вывод отсортированного массива <86> mov ah,09h <87> lea dx,mes2 <88> int 21h <89> prepare: <90> mov ex, 10 <91> mov si, 0 <92> show: ;вывод значения элемента на экран <93> mov dx,mas[si] <94> add dl,30h <95> mov ah,02h <96> int 21h <97> add si, 2 <98> loop show <99> exit: <100> mov ax,4c00h стандартный выход <101> int 21h <102> end main ;конец программы
276 Урок 12. Сложные структуры данных В основе программы лежит алгоритм, похожий на метод пузырьковой сортиров- ки. Эта программа не претендует на безусловную оптимальность, так как суще- ствует цедая теория, касающаяся подобного типа сортировок. Перед нами стоит другая цель — показать использование средств ассемблера для решения подобно- го рода задач. В программе два цикла. Внешний цикл определяет позицию в мас- сиве очередного элемента, с которым производится попарное сравнение элементов правой части массива (относительно этого элемента). За каждую итерацию внеш- него цикла на месте этого очередного элемента оказывается меньший элемент из правой части массива (если он есть). В остальном программа достаточно проста и на языке высокого уровня заняла бы около десятка строк. Структуры Рассмотренные нами выше массивы представляют собой совокупность однотип- ных элементов. Но часто в приложениях возникает необходимость рассматри- вать некоторую совокупность данных разного типа как некоторый единый тип. Это очень актуально, например, для программ баз данных, где необходимо связы- вать совокупность данных разного типа с одним объектом. К примеру, выше мы рассмотрели листинг 12.3, в котором работа производилась с массивом трехбай- товых элементов. Каждый элемент, в свою очередь, представлял собой два эле- мента разных типов: однобайтовое поле счетчика и двухбайтовое поле, которое могло нести еще какую-то нужную для хранения и обработки информацию. Если читатель знаком с одним из языков высокого уровня, то он знает, что такой объект обычно описывается с помощью специального типа данных — структуры. С целью повысить удобство использования языка ассемблера в него также был введен такой тип данных. По определению структура — это тип данных, состоящий из фиксированного числа элементов разного типа. Для использования структур в программе необходимо выполнить три действия: 1. Задать шаблон структуры. По смыслу это означает определение нового типа данных, который впоследствии можно использовать для определения пере- менных этого типа. 2. Определить экземпляр структуры. Этот этап подразумевает инициализацию конкретной переменной с заранее определенной (с помощью шаблона) струк- турой. 3. Организовать обращение к элементам структуры. Очень важно, чтобы вы с самого начала уяснили, в чем разница между описани- ем структуры в программе и ее определением. Описать структуру в программе означает лишь указать ее схему или шаблон; память при этом не выделяется.
Структуры 277 Этот шаблон можно рассматривать лишь как информацию для транслятора о расположении полей и их значении по умолчанию. Определить структуру — значит дать указание транслятору выделить память и присвоить этой области памяти символическое имя. Описать структуру в программе можно только один раз, а определить — любое количество раз. Описание шаблона структуры Описание шаблона структуры имеет следующий синтаксис: имя_структуры STRUC <описание полей> имя_структуры ENDS Здесь <описание полей> представляет собой последовательность директив описа- ния данных db, dw, dd, dq и dt. Их операнды определяют размер полей и, при необ- ходимости, начальные значения. Этими значениями будут, возможно, ини- циализироваться соответствующие поля при определении структуры. Как мы уже отметили, при описании шаблона память не выделяется, так как это всего лишь информация для транслятора. Местоположение шаблона в про- грамме может быть произвольным, но, следуя логике работы однопроходного транслятора, он должен быть расположен до того места, где определяется пере- менная с типом данной структуры. То есть, при описании в сегменте данных переменной с типом некоторой структуры ее шаблон необходимо поместить в начале сегмента данных либо перед ним. Рассмотрим работу со структурами на примере моделирования базы данных о сотрудниках некоторого отдела. Для простоты, чтобы уйти от проблем преоб- разования информации при вводе, условимся, что все поля символьные. Опре- делим структуру записи этой базы данных следующим шаблоном: worker struc информация о сотруднике namdb 30 dup (” ”) ;фамилия, имя, отчество sexdb " " ; пол position db 30 dup (" ’’) должность agedb 2 dup (" ") ;возраст standing db 2 dup (” ”) ;стаж salary db 4 dup (" ”) ; оклад в рублях birthdate db 8 dup (" ”) ;дата рождения worker ends
278 Урок 12. Сложные структуры данных Определение данных с типом структуры Для использования описанной с помощью шаблона структуры в программе необ- ходимо определить переменную с типом данной структуры. Для этого использу- ется следующая синтаксическая конструкция: [имя переменной] имя_структуры <[список значений]> Здесь: имя переменной - идентификатор переменной данного структурного типа. Задание имени переменной необязательно. Если его не указать, будет просто выделена область памяти размером в сумму длин всех элементов структуры. список значений - заключенный в угловые скобки список начальных значений элементов структуры, разделенных запятыми. Его задание так- же необязательно. Если список указан не полностью, то все поля структу- ры для данной переменной инициализируются значениями из шаблона, если таковые заданы. Допускается инициализация отдельных полей, но в этом случае пропущенные поля должны отделяться запятыми. Пропущен- ные поля будут инициализированы значениями из шаблона структуры. Если при определении новой переменной с типом данной структуры мы согласны со всеми значениями полей в ее шаблоне (то есть заданными по умолчанию), то нужно просто написать угловые скобки. К примеру: victor worker о. Для примера определим несколько переменных с типом описанной выше структуры. data segment sotrl worker <"Гурко Андрей Вячеславович”,, ‘художник’, ’33’,'15','1800’,’26.01.64’> sotr2 worker<”Михайлова Наталья Геннадьевна”, ’ж’, ’программист’, ’30’,’10’,’1680’,’27.10.58’> sotr3 worker<”Степанов Юрий Лонгинович”,, 'художник', ’38’,’20','1750',’01.01.58’> sotr4 worker <"Юрова Елена Александровна”, 'ж’, ’связист’, ’32’, ’2',, ’09.01.66’> sotr5 worker о ; здесь все значения по умолчанию data ends Методы работы со структурой Идея введения структурного типа в любой язык программирования состоит в объединении разнотипных переменных в один объект. В языке должны быть средства доступа к этим переменным внутри конкретного экземпляра структуры.
Структуры 279 Для того чтобы сослаться в команде на поле некоторой структуры, используется специальный оператор — символ «.» (точка). Он используется в следующей син- таксической конструкции: адресное_выражение.имя_поля_структуры Здесь: адресное_выражение — идентификатор переменной некоторого структурного типа или выражение в скобках в соответствии с указанными ниже синтаксическими правилами (рис. 12.1); имя_поля_структуры — имя поля из шаблона структуры. Это, на самом деле, тоже адрес, а точнее, смещение поля от начала структуры. Таким образом оператор . вычисляет выражение: (адресное_выражение) + (имя_поля_структуры) Рис. 12.1. Синтаксис адресного выражения в операторе обращения к полю структуры Продемонстрируем на примере определенной нами структуры worker некото- рые приемы работы со структурами. К примеру, требуется извлечь в ах значе- ния поля с возрастом. Так как вряд ли возраст трудоспособного человека будет больше величины 99 лет, то после помещения содержимого этого символьного поля в регистр ах его будет удобно преобразовать в двоичное представление командой aad (см. урок 8). Будьте внимательны, так как из-за принципа хране- ния данных «младший байт по младшему адресу» старшая цифра возраста бу- дет помещена в al, а младшая — в ah. Для корректировки достаточно использо- вать команду xchg al,ah. mov ax,word ptr sotrl.age ;b al возраст sotrl xchg ah,al ;a можно и так: lea bx,sotrl mov ax,word ptr [bx].age xchg ah,al Давайте представим, что сотрудников не четверо, а намного больше и к тому же их число и информация о них постоянно меняются. В этом случае теряется смысл явного определения переменных с типом worker для конкретных личнос- тей. Язык ассемблера разрешает определять не только отдельную переменную с типом структуры, но и массив структур. К примеру, определим массив из 10
280 Урок 12. Сложные структуры данных структур типа worker: mas_sotr worker 10 dup (о) Дальнейшая работа с массивом структур производится так же, как и с одномер- ным массивом. Здесь возникает несколько вопросов. Как быть с размером и как организовать индексацию элементов массива? Аналогично другим идентификаторам, определенным в программе, транслятор назначает имени типа структуры и имени переменной с типом структуры атри- бут типа. Значением этого атрибута является размер в байтах, занимаемый по- лями этой структуры. Извлечь это значение можно с помощью оператора type. После того как стал известен размер экземпляра структуры, организовать ин- дексацию в массиве структур не представляет особой сложности. К примеру: worker struc worker ends mas.sotr worker 10 dup (<>) mov bx.type worker ;bx=77 lea di,mas_sotr ;извлечь и вывести на экран пол всех сотрудников: mov сх,10 cycl: mov al,[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
Объединения 281 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 repmovsb В приложении 8 приведена программа, которая осуществляет работу с базой данных о сотрудниках. На ее примере вы можете глубже познакомиться с тем, как организовать работу со структурами в своей программе. Есть две причины, по которым эта программа была вынесена в отдельное приложение. Возможно, для читателя имеет смысл в полном объеме исследовать работу этой програм- мы после знакомства с макрокомандами на следующем уроке. Объединения Мне кажется, что программистское ремесло рано или поздно делает человека похожим на хорошую домохозяйку. Он, подобно ей, постоянно находится в поиске: где бы чего-нибудь сэкономить, урезать, из минимума продуктов сде- лать прекрасный обед. И если это удается, то и моральное удовлетворение по- лучается ничуть не меньше, а может и больше, чем от прекрасного обеда у до- мохозяйки. Степень этого удовлетворения, как мне кажется, зависит от степени любви к своей профессии. С другой стороны, успехи в разработке программно- го и аппаратного обеспечения несколько расслабляют программиста, и доволь- но часто наблюдается ситуация, похожая на известную пословицу про муху и слона, — для решения некоторой мелкой задачи привлекаются тяжеловесные средства, эффективность которых, в общем случае, значима только при реали- зации сравнительно больших проектов. Наличие в языке следующих двух типов данных, наверное, объясняется стрем- лением хозяйки максимально эффективно использовать рабочую площадь сто- ла (оперативной памяти) при приготовлении еды или для размещения продук- тов (данных программы). Представим ситуацию, когда мы используем некоторую область памяти для размещения некоторого объекта программы (переменной, массива или структу- ры). Вдруг, после некоторого этапа работы у нас отпала надобность в использо- вании этих данных. Обычно память остается занятой до конца работы про- граммы. Конечно, в принципе, ее можно было бы использовать для хранения других переменных, но при этом без принятия специальных мер нельзя изме- нить тип и имя. Неплохо было бы иметь возможность переопределить эту об- ласть памяти для объекта с другими типом и именем. Язык ассемблера предос- тавляет такую возможность в виде специального типа данных, называемого объединением. Объединение — тип данных, позволяющий трактовать одну и ту
282 Урок 12. Сложные структуры данных же область памяти как имеющую разные типы и имена. Описание объединений в программе напоминает описание структур, то есть сна- чала описывается шаблон, в котором с помощью директив описания данных пе- речисляются имена и типы полей: имя_объединения UNION <описание полей> имя_объединения ENDS Отличие объединений от структур состоит, в частности, в том, что при определе- нии переменной типа объединения память выделяется в соответствии с разме- ром максимального элемента. Обращение к элементам объединения происходит по их именам, но при этом нужно, конечно, помнить о том, что все поля в объе- динении накладываются друг на друга. Одновременная работа с элементами объединения исключена. В качестве элементов объединения можно использовать и структуры. Листинг 12.6, который мы сейчас рассмотрим, примечателен тем, что, кроме де- монстрации использования собственно типа данных «объединение», в нем пока- зывается возможность взаимного вложения структур и объединений. Постарай- тесь внимательно отнестись к анализу этой программы. Основная идея здесь в том, что указатель на память, формируемый программой, может быть представ- лен в виде: О 16-битного смещения; О 32-битного смещения; О пары из 16-битного смещения и 16-битной сегментной составляющей ад- реса; О пары из 32-битного смещения и 16-битного селектора. Какие из этих указателей можно применять в конкретной ситуации, зависит от режима адресации (use16 или use32) и режима работы микропроцессора. Так вот, описанный в листинге 12.6 шаблон объединения позволяет нам облегчить формирование и использование указателей различных типов. Листинг 12.6. Пример использования объединения masm model small stack 256 . 586P pnt st rue структура pnt, содержащая вложенное объединение union ;описание вложенного в структуру объединения offs_16 dw ? offs_32 dd ?
Объединения 283 ends ; конец описания объединения segm dw ? ends ; конец описания структуры .data point union определение объединения, ; содержащего вложенную структуру of Мб dw ? off_32 dd ? point_16 pnt <> point_32 pnt <> point ends tstdb «Строка для тестирования» adr_data point <> ; определение экземпляра объединения .code main: mov ах,©data mov ds, ах mov ax,seg tst •«записать адрес сегмента строки tst в поле структуры adr_data mov adr.data.point_16.segm,ax •«когда понадобится, можно извлечь значение из этого поля обратно, ; к примеру, в регистр Ьх: mov bx,adr_data.point_16.segm Нормируем смещение в поле структуры adr_data mov ах,offset tst ; смещение строки в ах mov adr_data.point_16.offs_16, ax аналогично, когда понадобится, можно извлечь ;значение из этого поля: mov bx,adr.data.point_16.offs_16 exit: mov ax,4c00h int 21h end main Когда вы будете работать в защищенном режиме микропроцессора и использо- вать 32-разрядные адреса, то аналогичным способом можете заполнить и ис- пользовать описанное выше объединение.
284 Урок 12. Сложные структуры данных Записи Наша «хозяйка-программист» становится все более экономной. Она уже хочет работать с продуктами на молекулярном уровне, без любых отходов и напрас- ных трат. Подумаем, зачем тратить под некоторый программный индикатор со значением «включено-выключено» целых восемь разрядов, если вполне хватает одного? А если таких индикаторов несколько, то расход оперативной памяти может стать весьма ощутимым. Когда мы знакомились с логическими командами, то говорили, что их можно применять для решения подобной проблемы. Но это не совсем эффективно, так как велика вероятность ошибок, особенно при составлении битовых масок. TASM предоставляет нам специальный тип данных, использование которого помогает решить проблему работы с битами более эффективно. Речь идет о специальном типе данных — записях. Запись — структурный тип данных, со- стоящий из фиксированного числа элементов длиной от одного до нескольких бит. При описании записи для каждого элемента указывается его длина в битах и, что необязательно, некоторое значение. Суммарный размер записи определяет- ся суммой размеров ее полей и не может быть более 8, 16 или 32 бит. Если суммарный размер записи меньше указанных значений, то все поля записи «прижимаются» к младшим разрядам. Использование записей в программе, так же как и структур, организуется в три этапа: 1. Задание шаблона записи, то есть определение набора битовых полей, их длин и, при необходимости, инициализация полей. 2. Определение экземпляра записи. Так же как и для структур, этот этап под- разумевает инициализацию конкретной переменной типом заранее опреде- ленной с помощью шаблона записи. 3. Организация обращения к элементам записи. Описание записи Описание шаблона записи имеет следующий синтаксис: имя_зэписи RECORD <описание элементов> Здесь: <описание элементов> представляет собой последовательность описаний отдельных элементов записи согласно синтаксической диаграмме (рис. 12.2). При описании шаблона память не выделяется, так как это всего лишь инфор- мация для транслятора ассемблера о структуре записи. Так же как и для струк-
Записи 285 тур, местоположение шаблона в программе может быть любым, но при этом не- обходимо учитывать логику работы однопроходного транслятора. —| Имя записи]—| RECORDhd Имя поля|-(?)- Размер Рис. 12.2. Синтаксис описания шаблона записи Определение экземпляра записи Для использования шаблона записи в программе необходимо определить пере- менную с типом данной записи, для чего применяется следующая синтаксичес- кая конструкция (рис. 12.3). Рис. 12.3. Синтаксис описания экземпляра записи Анализируя эту синтаксическую диаграмму, можно сделать вывод, что инициа- лизация элементов записи осуществляется достаточно гибко. Рассмотрим не- сколько вариантов инициализации. Если инициализировать поля не требуется, то достаточно указать ? при опре- делении экземпляра записи: iotest record ±1:1,12:2=11,13:1,14:2=11,15:2=00 flag iotest ? Если вы составите и исследуете в отладчике тестовый пример с данным опре- делением записи, то увидите, что все поля переменной типа запись flag обну- ляются. Это происходит несмотря на то, что в определении записи заданы на- чальные значения полей. Если требуется частичная инициализация элементов, то они заключаются в угловые (< и >) или фигурные ({ и }) скобки. Различие здесь в том, что в угло-
286 Урок 12. Сложные структуры данных вых скобках элементы должны быть заданы в том же порядке, что и в определе- нии записи. Если значение некоторого элемента совпадает с начальным, то его можно не указывать, но обязательно обозначить его запятой. Для последних эле- ментов идущие подряд запятые можно опустить. К примеру, согласиться со значениями по умолчанию можно так: iotest reco rd 11:1,12:2=11,13:1,14:2=11,15:2=00 flag iotest о ; согласились со значением по умолчанию Изменить значение поля 12 можно так: iotest record 11:1,12:2=11,13:1,14:2=11,15:2=00 flag iotest<,10,> ; переопределили 12 Применяя фигурные скобки, также можно указать выборочную инициализацию полей, но при этом необязательно обозначать запятыми поля, со значениями по умолчанию которых мы согласны: iotest reco rd 11:1,12: 2=11,13:1,14:2=11,15:2=00 flag iotest {12=10} ; переопределили 12, не обращая ; внимания на порядок ; следования других компонентов записи Работа с записями Как организовать работу с отдельными элементами записи? Обычные механиз- мы адресации здесь бессильны, так как они работают на уровне ячеек памяти, то есть байтов, а не отдельных битов. Здесь программисту нужно приложить некоторые усилия. Прежде всего для понимания проблемы нужно усвоить не- сколько моментов: О Каждому имени элемента записи ассемблер присваивает числовое значение, равное количеству сдвигов вправо, которые нужно произвести для того, что- бы этот элемент оказался «прижатым» к началу ячейки. Это дает нам воз- можность локализовать его и работать с ним. Но для этого нужно знать длину элемента в битах. О Сдвиг вправо производится с помощью команды сдвига shr. О Ассемблер содержит оператор width, который позволяет узнать размер эле- мента записи в битах или полностью размер записи. Варианты применения оператора width: • width имя_элемента_записи — значением оператора будет размер элемента в битах.
Записи 287 о width имя_экземпляра_записи или width имя_типа_записи — значением оператора будет размер всей записи в битах. mov al,width i2 mov ax,width iotest О Ассемблер содержит оператор mask, который позволяет локализовать биты нужного элемента записи. Эта локализация производится путем создания маски, размер которой совпадает с размером записи. В этой маске обнулены биты во всех позициях, за исключением тех, которые занимает элемент в записи. О Сами действия по преобразованию элементов записи производятся с помо- щью логических команд. Теперь у вас есть вся информация о средствах ассемблера для работы с запися- ми. Вы также поняли, что непосредственно обратиться к элементу записи не- возможно. Чтобы произвести обработку интересующего нас элемента, нужно сначала выделить его, сдвинуть при необходимости к младшим разрядам, вы- полнить требуемые действия и поместить обратно на свое место в записи. По- этому, чтобы вам не изобретать каждый раз велосипед, далее мы опишем типо- вые алгоритмы осуществления этих операций над элементами записи. Ваша задача — закодировать эти алгоритмы тем или иным способом в соответствии с требованиями задачи. Для выделения элемента записи: О Поместить запись во временную память — регистр (8, 16 или 32-битный в зависимости от размера записи). О Получить битовую маску, соответствующую элементу записи, с помощью оператора mask. О Локализовать биты в регистре с помощью маски и команды and. О Сдвинуть биты элемента к младшим разрядам регистра командой shr. Чис- ло разрядов для сдвига получить с использованием имени элемента записи. В результате этих действий элемент записи будет локализован в начале рабоче- го регистра и далее с ним можно производить любые действия (как, см. ниже). Как мы уже выяснили, с элементами записи производятся любые действия, как над обычной двоичной информацией. Единственное, что нужно отслеживать — это размер битового поля. Если, к примеру, размер поля увеличится, то впос- ледствии может произойти случайное изменение соседних полей битов. Поэто- му желательно исключить изменение размера поля. Чтобы поместить измененный элемент на его место в запись: О Используя имя элемента записи в качестве счетчика сдвигов, сдвинуть вле- во биты элемента записи.
288 Урок 12. Сложные структуры данных О Если вы не уверены в том, что разрядность результата преобразований не превысила исходную, можно выполнить «обрезание» лишних битов, ис- пользуя команду and и маску элемента. О Подготовить исходную запись к вставке измененного элемента путем обну- ления битов в записи на месте этого элемента. Это можно сделать путем наложения командой and инвертированной маски элемента записи на исход- ную запись. О С помощью команды о г наложить значение в регистре на исходную запись. В качестве примера рассмотрим листинг 12.7, который обнуляет поле 12 в за- писи iotest. Листинг 12.7. Работа с полем записи ; prg_12_7.asm masm model small 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 12 shr al,12 ;биты 12 в начале ax and al.Ofch ;обнулили 12 ; помещаем 12 на место shl al, 12 mov bl,[flag] xor bl,mask 12 ;сбросили 12 or bl,al ; наложили exit: mov ax,4c00h стандартный выход int 21h end main ;конец программы В заключение еще раз проанализируйте тип записи и особенности работы с ним. При этом обратите внимание на то обстоятельство, что мы нигде явно не просчитываем расположение битов. Поэтому, если понадобится изменить раз-
Записи 289 мер элемента или его начальное значение, достаточно внести изменения в экзем- пляр записи или в описание ее типа; функциональную часть программы, работа- ющую с этой записью, трогать не нужно. Дополнительные возможности обработки Понимая важность для эффективного программирования такого типа данных, как запись, разработчики транслятора TASM, начиная с версии 3.0, включили в систему его команд две дополнительные команды на правах директив. Послед- нее означает, что эти команды внешне имеют формат обычных команд ассемб- лера, но после трансляции они приводятся к одной или нескольким машинным командам. Введение этих команд в язык TASM повышает наглядность работы с записями, оптимизирует код и уменьшает размер программы. Эти команды позволяют скрыть от программиста действия по выделению и установке от- дельных полей записи (мы их обсуждали выше). Для установки значения некоторого поля записи используется команда setfield с синтаксисом setfield имя_элемента_записи назначение,регистр_источник Для выборки значения некоторого поля записи используется команда getf ield с синтаксисом getfield имя_элемента_записи регистр_назначение, источник Работа команды setfield заключается в следующем. Местоположение записи определяется операндом назначение, который может представлять собой имя регистра или адрес памяти. Операнд имя_элемента_записи определяет элемент записи, с которым ведется работа (по сути, если вы были внимательны, он оп- ределяет смещение элемента в записи относительно младшего разряда). Новое значение, в которое необходимо установить указанный элемент записи, должно содержаться в операнде регистр_источник. Обрабатывая данную команду, транс- лятор генерирует последовательность команд, которые выполняют следующие действия: О сдвиг содержимого регистр_источник влево на количество разрядов, соответ- ствующее расположению элемента в записи; О логическую операцию о г над операндами назначение и регистр_источник. Ре- зультат операции помещается в операнд назначение. Важно отметить, что setfield не производит предварительной очистки элемен- та, в результате после логического сложения командой о г возможно наложение старого содержимого элемента и нового устанавливаемого значения. Поэтому требуется предварительно подготовить поле в записи путем его обнуления. Действие команды getfield обратно setfield. В качестве операнда источник может быть указан либо регистр, либо адрес памяти. В регистр, указанный опе- рандом регистр_назначение, помещается результат работы команды — значение
290 Урок 12. Сложные структуры данных элемента записи. Интересная особенность связана с регистр_назначение. Команда get field всегда использует 16-битный регистр, даже если вы укажете в этой ко- манде имя 8-битного регистра. В качестве примера применения команд setfield и getfield рассмотрим лис- тинг 12.8. Листинг 12.8. Работа с полями записи ;prg_12_8.asm masm model small stack 256 lotest 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 xor bl,bl getfield 15 bl,al mov bl,1 setfield i4 al,bl setfield i5 al,bl exit: mov ax,4c00h стандартный выход int 21h end main ;конец программы В листинге 12.8 демонстрируется порядок извлечения и установки некоторых полей записи. Результат работы команд setfield и getfield удобнее всего изу- чать в отладчике. При установке значений полей не производится их предвари- тельная очистка. Это сделано специально. Для такого рода операций лучше ис- пользовать некоторые универсальные механизмы, иначе велик риск внесения ошибок, которые трудно обнаружить и исправить. В качестве такого механизма можно предложить макрокоманды, к рассмотрению которых мы и приступим на следующем уроке.
Записи 291 В заключение хотелось бы привести еще один пример использования записей. Это описание регистра eflags. Для удобства это описание мы разбили на три части: eflags_1_7 — младший байт eflags/flags, eflags_8_15 — второй байт eflags/flags, eflags.h — старшая половина eflags. eflags_l_7 record sf7:1=0, zf6:1=0,c5:1=0,af4:1=0,c3:1=0,pf2:1=0,c1:=1,cfO:1=0 eflags_l_15 record c15:1=0,nt14:1=0,iopl:2=0,of11:1=0,df10:1=0,if9:1=1,tf8:1=0 eflagsji record c:13=0, ac18:1=0,vm17:1=0,rf16:1=0 Запомните это описание. Когда вы освоите работу с макрокомандами и столк- нетесь с необходимостью задействовать регистр флагов, то вы сразу же захоти- те написать соответствующую макрокоманду. Эта макрокоманда, если вы не забудете хорошо ее протестировать, избавит вас от многих трудно обнаружива- емых ошибок. Подведем некоторые итоги: 0 TASM поддерживает несколько дополнительных типов данных, значительно расширяющих возможности базовых директив резервирования и инициали- зации данных. По сути, эти типы заимствованы из языков высокого уровня и призваны облегчить разработку прикладных программ на ассемблере. 0 Практическое использование дополнительных типов данных требует повы- шенной внимательности и отражает специфику программирования на языке ассемблера. 0 Понятия массива и индексации массива весьма условны, и логическая ин- терпретация области памяти, отведенной под массив, определяется алгорит- мом обработки. 0 Тип структуры в языке ассемблера позволяет создать совокупность логичес- ки взаимосвязанных разнотипных данных и рассматривать их как отдель- ный объект. Это очень удобно в случаях, когда в программе необходимо иметь несколько таких объектов. В этом случае обычно организуют массив структур. 0 Основное достоинство объединений — в возможности «плюрализма сужде- ний» о типе одной и той же области памяти. 0 Записи в языке ассемблера расширяют возможности логических команд для работы на уровне бит, что подчеркивает значение ассемблера как языка сис- темного программирования.
I УРОК Макросредства языка ассемблера □ Понятие о макросредствах языка ассемблера □ Псевдооператоры equ и = □ Макрокоманды и макродирективы □ Директивы условной компиляции
Любопытный читатель к данному уроку, вероятно, попытался самостоятельно написать хотя бы несколько программ на ассемблере. Скорее всего, эти про- граммы были предназначены для решения небольших, чисто исследовательских задач, но даже на примере этих маленьких по объему программ вам, наверное, стали очевидны некоторые из нижеперечисленных проблем: О плохое понимание исходного текста программы, особенно по прошествии некоторого времени после ее написания; О ограниченность набора команд; О повторяемость некоторых идентичных или незначительно отличающихся участков программы; О необходимость включения в каждую программу участков кода, которые уже были использованы в других программах; О и т. д. Если бы мы писали программу на машинном языке, то данные проблемы были бы принципиально нерешаемыми. Но язык ассемблера, являясь символичес- ким аналогом машинного языка, предоставляет для их решения ряд средств. Основной целью, которая при этом преследуется, является повышение удоб- ства написания программ. В общем случае эта цель достигается по нескольким направлениям за счет: О расширения набора директив; О введения некоторых дополнительных команд, не имеющих аналогов в систе- ме команд микропроцессора. За примером далеко ходить не нужно — ко- манды setfield и getfield, рассмотренные на уроке 12. Они скрывают от программиста рутинные действия и генерируют наиболее эффективный код; О введения сложных типов данных. Но это все глобальные направления, по которым развивается сам транслятор от версии к версии. Что же делать программисту для решения его локальной задачи, для облегчения работы в определенной проблемной области? Для этого разработчики компиляторов ассемблера включают в язык и постоянно совер- шенствуют аппарат макросредств. Этот аппарат является очень мощным и
294 Урок 13. Макросредства языка ассемблера важным. В общем случае есть смысл говорить о том, что транслятор ассемблера состоит из двух частей — непосредственно транслятора, формирующего объект- ный модуль, и макроассемблера (рис. 13.1). Если вы знакомы с языком С или C++, то, конечно, помните широко применяемый в них механизм препроцес- сорной обработки. Он является некоторым аналогом механизма, заложенного в работу макроассемблера. Для тех, кто ничего раньше не слышал об этих механизмах, поясню их суть. Основная идея — использование подстановок, ко- торые замещают определенным образом организованную символьную после- довательность другой символьной последовательностью. Создаваемая таким образом последовательность может быть как последовательностью, описываю- щей данные, так и последовательностью программных кодов. Главное здесь то, что на входе макроассемблера может быть текст программы, весьма далекий по виду от программы на языке ассемблера, а на выходе обязательно будет текст на чистом ассемблере, содержащем символические аналоги команд системы машинных команд микропроцессора. Таким образом, обработка программы на ассемблере с использованием макросредств неявно осуществляется транслято- ром в две фазы (рис. 13.1). На первой фазе работает часть компилятора, назы- ваемая макроассемблером, основные функции которого мы описали выше. На второй фазе трансляции участвует непосредственно ассемблер, задачей которо- го является формирование объектного кода, содержащего текст исходной про- граммы в машинном виде. Транслятор TASM.EXE Рис. 13.1. Макроассемблер в общей схеме трансляции программы на TASM Далее мы обсудим основной набор макросредств, доступных при использовании компилятора TASM. Отметим, что большинство этих средств доступно и в ком- пиляторе с языка ассемблера фирмы Microsoft. Обсуждение начнем с простейших средств и закончим более сложными.
Псевдооператоры equ и = 295 Псевдооператоры equ и = К простейшим макросредствам языка ассемблера можно отнести псевдооперато- ры equ и «=» (равно). Их мы уже неоднократно использовали при написании программ. Эти псевдооператоры предназначены для присвоения некоторому выражению символического имени или идентификатора. Впоследствии, когда в ходе трансляции этот идентификатор встретится в теле программы, макро- ассемблер подставит вместо него соответствующее выражение. В качестве вы- ражения могут быть использованы константы, имена меток, символические имена и строки в апострофах. После присвоения этим конструкциям символи- ческого имени его можно использовать везде, где требуется размещение данной конструкции. Синтаксис псевдооператора equ: имя_идентификатора equ строка или числовое_выражение Синтаксис псевдооператора =: имя_идентификатора = числовое_выражение Несмотря на внешнее и функциональное сходство, псевдооператоры equ и «=» отличаются следующим: О из синтаксического описания видно, что с помощью equ идентификатору можно ставить в соответствие как числовые выражения, так и текстовые строки, а псевдооператор «=» может использоваться только с числовыми выражениями; О идентификаторы, определенные с помощью «=», можно переопределять в исходном тексте программы, а определенные с использованием equ — нельзя. Ассемблер всегда пытается вычислить значение строки, воспринимая ее как вы- ражение. Для того чтобы строка воспринималась именно как текстовая, не- обходимо заключить ее в угловые скобки: <строка>. Кстати сказать, угловые скобки являются оператором ассемблера, с помощью которого транслятору сооб- щается, что заключенная в них строка должна трактоваться как текст, даже если в нее входят служебные слова ассемблера или операторы. Хотя в режиме Ideal это не обязательно, так как строка для equ в нем всегда трактуется как текстовая. Псевдооператор equ удобно использовать для настройки программы на кон- кретные условия выполнения, замены сложных в обозначении объектов, много- кратно используемых в программе, более простыми именами и т. п. К примеру: masm model small stack 256 mas_size equ 10 ;размерность массива akk equ ax ;переименовать регистр
296 Урок 13. Макросредства языка ассемблера mas.elem equ mas[bx][si] ; адресовать элемент массива .data ; описание массива из 10 байт: masdb mas_size dup (0) .code mov akk,@data фактически mov ax,@data mov ds.akk фактически mov ds, ax mov al,mas_elem фактически - mov al, mas[bx][si] Псевдооператор «=» удобно использовать для определения простых абсолютных (то есть не зависящих от места загрузки программы в память) математических выражений. Главное условие то, чтобы транслятор мог вычислить эти выраже- ния во время трансляции. К примеру: .data adrl db 5 dup (0) adr2 dw 0 len = 43 len = len+1 ; можно и так, через предыдущее определение len = adr2-adr1 Как видно из примера, в правой части псевдооператора «=» можно использовать метки и ссылки на адреса — главное, чтобы в итоге получилось абсолютное вы- ражение. Компилятор TASM, начиная с версии 3.00, содержит директивы, значительно расширяющие его возможности по работе с текстовыми макросами. Эти дирек- тивы аналогичны некоторым функциям обработки строк в языках высокого уровня. Под строками здесь понимается текст, описанный с помощью псевдо- оператора equ. Набор этих директив следующий: О директива слияния строк catstr: идентификатор catstr строка_ 1 ,строка_2,... - значением этого макроса будет новая строка, состоящая из сцепленной слева направо последовательности строк строка_1,строка_2,... В качестве сцепляемых строк могут быть указаны имена ранее определенных макросов. К примеру: preequ Привет, name equ < Юля> privet catstr pre, name ; privet= «Привет, Юля» О директива выделения подстроки в строке substr: идентификатор substr строка,номер_позиции,размер - значением данного макроса будет часть заданной строки, начинающаяся с позиции с номером номер_позиции и длиной, указанной в размер. Если требуется только
Макрокоманды 297 остаток строки, начиная с некоторой позиции, то достаточно указать только номер позиции без указания размера. К примеру: •«продолжение предыдущего фрагмента: privet catstr pre,name ;privet= «Привет, Юля» name substr privet,7,3 ;пате=«Юля» О директива определения вхождения одной строки в другую instr: идентификатор instr номер_нач_позиции,строка_1,строка_2 - после обработки данного макроса транслятором идентификатору будет присвоено числовое значение, соответствующее номеру (первой) позиции, с которой совпада- ют строка^ 1 и строка_2. Если такого совпадения нет, то идентификатор получит значение 0; О директива определения длины строки в текстовом макросе sizestr: идентификатор sizestr строка — в результате обработки данного макроса значение идентификатор устанавливается равным длине строки: ; как продолжение предыдущего фрагмента: privet catstr pre,name ; privet= «Привет, Юля» len sizestr privet ;len=10 Эти директивы очень удобно использовать при разработке макрокоманд, которые являются следующим макросредством, предоставляемым компилятором ассемб- лера. Макрокоманды Идейно макрокоманда представляет собой дальнейшее развитие механизма заме- ны текста. С помощью макрокоманд в текст программы можно вставлять после- довательности строк (которые логически могут быть данными или коман- дами) и даже более того — привязывать их к контексту места вставки. Представим ситуацию, когда необходимо выполнить некоторые повторяющие- ся действия. Программа из листинга 3.1 является ярким этому примером. Структурно в ней явно прослеживаются повторяющиеся участки кода. Их можно оформить в виде макрокоманд и использовать эти повторяющиеся фрагменты в различных программах. Дальнейшее наше обсуждение будет по- священо тому, как это сделать. Определимся с терминологией. Макрокоманда представляет собой строку, со- держащую некоторое символическое имя — имя макрокоманды, предназначен- ную для того, чтобы быть замещенной одной или несколькими другими стро- ками. Имя макрокоманды может сопровождаться параметрами. Обычно программист сам чувствует момент, когда ему нужно использовать макроко- манды в своей программе. Если такая необходимость возникает и нет готового,
298 Урок 13. Макросредства языка ассемблера ранее разработанного варианта нужной макрокоманды, то вначале необходимо задать ее шаблон-описание, который называют макроопределением. Синтаксис макроопределения следующий: имя_макрокоманды macro список_формальных_аргументов тело макроопределения endm Где должны располагаться макроопределения? Есть три варианта: О в начале исходного текста программы до сегмента кода и данных с тем, что- бы не ухудшать читабельность программы. Этот вариант следует применять в случаях, если определяемые вами макрокоманды актуальны только в пре- делах одной этой программы; О в отдельном файле. Этот вариант подходит при работе над несколькими программами одной проблемной области. Чтобы сделать доступными эти макроопределения в конкретной программе, необходимо в начале исходного текста этой программы записать директиву include имя_файла, к примеру: masm model small include show.inc ; в это место будет вставлен текст файла show, inc О в макробиблиотеке. Если у вас есть универсальные макрокоманды, которые используются практически во всех ваших программах, то их целесообразно записать в так называемую макробиблиотеку. Сделать актуальными макро- команды из этой библиотеки можно с помощью все той же директивы include. Недостаток этого и предыдущего способов в том, что в исходный текст программы включаются абсолютно все макроопределения. Для ис- правления ситуации можно использовать директиву purge, в качестве опе- рандов которой через запятую перечисляются имена макрокоманд, которые не должны включаться в текст программы. К примеру: include iomac.inc purge outstr.exit В данном случае в исходный текст программы перед началом компиляции TASM вместо строки include iomac.inc вставит строки из файла iomac.inc. Но вставлен- ный текст будет отличаться от оригинала тем, что в нем будут отсутствовать макроопределения outstr и exit. А теперь вернемся к программе из листинга 3.1. Проанализируем ее текст, выя- вим повторяющиеся участки и составим для них макроопределения (лис- тинг 13.1).
Макрокоманды 299 <i> <2> <3> <4> <5> <6> <7> <8> <9> <10> <11> <12> <13> <14> <15> <16> <17> <18> <19> <20> <21> <22> <23> <24> <25> <26> <27> <28> <29> <30> <31> <32> <33> <34> <35> <3б> Листинг 13.1. Пример 1 создания и использования макрокоманд ;prg_3_1.asm с макроопределениями init.ds macro •«Макрос настройки ds на сегмент данных mov ах,data mov ds, ах endm out_str macro str ; Макрос вывода строки на экран. ;На входе - выводимая строка. ; На выходе - сообщение на экране, push ах mov ah,09h mov dx,offset str int 21h pop ax endm clear.r macro rg ; очистка регистра rg хог rg, rg endm get_char macro ; ввод символа ; введенный символ в al mov ah,1h int 21h endm conv_16_2 macro ; макрос преобразования символа шестнадцатеричной цифры ;в ее двоичный эквивалент в al sub dl,30h cmp dl,9h jle $+5 sub dl,7h продолжение &
300 Урок 13. Макросредства языка ассемблера <37> <38> <39> <40> <41> <42> <43> <44> <45> <46> <47> <48> <49> <50> <51> <52> <53> <54> <55> <56> <57> <58> <59> <60> <61> <62> <63> <64> <65> <66> <67> <68> <69> <70> <71> endm exit macro ; макрос конца программы mov ax,4c00h int 21h endm data segment para public "data" message db "Введите две шестнадцатеричные цифры (буквы А, В, С, D, Е, F - прописные): $" data ends stk segment stack db 256 dup("?") stk ends code segment para public "code" assume cs: code, ds: data, ss: st k main proc init_ds out_str message clear_r ax get_char mov dl.al conv_16_2 mov cl,4h shl dl.cl get_char conv_16_2 add dl.al xchg dl,al результат в al exit main endp code ends end main
Макрокоманды 301 В листинге 13.1 в строках 2-7, 8-16,18-21, 23-28, 30-37, 39-43 описаны макро- определения. Их назначение приведено сразу после заголовка в теле каждого макроопределения. Все эти макроопределения можно использовать и при на- писании других программ. Посмотрите на модернизированный исходный текст программы из листинга 3.1 в листинге 13.1 (строки 55-69). Если не обращать внимания на некоторые неясные моменты, то сам сегмент кода стал внешне более читабельным и даже можно сказать, что в нем появился какой то смысл. Откомпилируйте листинг 13.1 и получите файл листинга. После этого сравни- те исходный текст программы и то, каким он стал после его обработки ассемб- лером. Вы увидите, что текст программы изменился — после строк программы, в которых были макрокоманды, появились фрагменты текста. Вид этих фраг- ментов зависит от того, есть ли у макрокоманды параметры. Уделите анализу файла листинга немного времени. Функционально макроопределения похожи на процедуры. Сходство их в том, что и те, и другие достаточно один раз где-то описать, а затем вызывать их спе- циальным образом. На этом их сходство заканчивается, и начинаются разли- чия, которые в зависимости от целевой установки можно рассматривать и как достоинства, и как недостатки: О в отличие от процедуры, текст которой неизменен, макроопределение в процессе макрогенерации может меняться в соответствии с набором факти- ческих параметров. При этом коррекции могут подвергаться как операнды команд, так и сами команды. Процедуры в этом отношении менее гибки; О при каждом вызове макрокоманды ее текст в виде макрорасширения встав- ляется в программу. При вызове процедуры микропроцессор осуществляет передачу управления на начало процедуры, находящейся в некоторой облас- ти памяти в одном экземпляре. Код в этом случае получается более компакт- ным, хотя быстродействие несколько снижается за счет необходимости осуществления переходов. Макроопределение обрабатывается компилятором особым образом. Для того чтобы использовать описанное макроопределение в нужном месте программы, оно должно быть активизировано с помощью макрокоманды указанием следую- щей синтаксической конструкции: имя_макрокоманды список_фактических_аргументов Результатом применения данной синтаксической конструкции в исходном текс- те программы будет ее замещение строками из конструкции тело макроопределе- ния. Но это не простая замена. Обычно макрокоманда содержит некоторый список аргументов — список_фактических_аргументов, которыми корректиру- ется макроопределение. Места в теле макроопределения, которые будут заме- щаться фактическими аргументами из макрокоманды, обозначаются с помо- щью так называемых формальных аргументов. Таким образом, в результате применения макрокоманды в программе формальные аргументы в макроопре-
302 Урок 13. Макросредства языка ассемблера делении замещаются соответствующими фактическими аргументами; в этом и заключается учет контекста. Процесс такого замещения называется макрогенера- цией, а результатом этого процесса является макрорасширение. К примеру, рассмотрим самое короткое макроопределение в листинге 13.1 — clear_rg. Как отмечено выше, результаты работы макроассемблера можно уз- нать, просмотрев файл листинга после трансляции. Покажем несколько его фрагментов, которые демонстрируют, как был описан текст макроопределения clear_rg (строки 24-27), как был осуществлен вызов макрокоманды clear.rg с фактическим параметром ах (строка 74) и как выглядит результат работы мак- рогенератора, сформировавшего команду ассемблера хог ах,ах (строка 75): 24 с1еаг_г macro rg 25 ; очистка регистра гд 26 хог гд, гд 27 endm 74 clear.r ах 75 000Е 33 СО хог ах, ах Таким образом, в итоге мы получили то, что и требовалось, — команду очистки заданного регистра, в данном случае ах. В другом месте программы вы можете выдать ту же макрокоманду, но уже с другим именем регистра. Если у вас есть желание, то вы можете провести эксперименты с этой и другими макрокоман- дами. Каждый фактический аргумент представляет собой строку символов, для формирования которой применяются следующие правила: О строка может состоять: • из последовательности символов без пробелов, точек, запятых, точек с за- пятой; • из последовательности любых символов, заключенных в угловые скобки: <...>. В этой последовательности можно указывать как пробелы, так и точ- ки, запятые, точки с запятыми. Не забывайте о том, что угловые скобки < > — это тоже оператор ассемблера. Мы упоминали о них при обсужде- нии директивы equ; О для того чтобы указать, что некоторый символ внутри строки, представляю- щей фактический параметр, является собственно символом, а не чем-то иным, например некоторым разделителем или ограничивающей скобкой, применяется специальный оператор «!». Этот оператор ставится непосред- ственно перед описанным выше символом, и его действие эквивалентно за- ключению данного символа в угловые скобки (см. предыдущий пункт); О если требуется вычисление в строке некоторого константного выражения, то в начале этого выражения нужно поставить знак %:
Макрокоманды 303 % константное_выражение - значение константное_выражение вычисляется и подставляется в текстовом виде в соответствии с текущей системой счисления1. Теперь обсудим вопрос, как транслятор распознает формальные аргументы в теле макроопределения для их последующей замены на фактические аргументы? Прежде всего по их именам в заголовке макроопределения. В процессе генерации макрорасширения компилятор ассемблера ищет в тексте тела макроопределения последовательности символов, совпадающие с теми последовательностями сим- волов, из которых состоят формальные параметры. После обнаружения такого совпадения формальный параметр из тела макроопределения замещается соот- ветствующим фактическим параметром из макрокоманды. Этот процесс называ- ется подстановкой аргументов. Здесь нужно отметить еще раз особо спи- со к_формальных_аргументов в заголовке макроопределения. В общем случае он содержит не только перечисление формальных элементов через запятую, но и не- которую дополнительную информацию. Полный синтаксис формального аргу- мента следующий: имя_формального_аргумента[: тип], где тип может принимать значения: О REQ, которое говорит о том, что требуется обязательное явное задание фак- тического аргумента при вызове макрокоманды; О =<любая_строка> — если аргумент при вызове макрокоманды не задан, то в со- ответствующие места в макрорасширении будет вставлено значение по умолча- нию, соответствующее значению любая_строка. Будьте внимательны: символы, входящие в любая_строка, должны быть заключены в угловые скобки. Но не всегда ассемблер может распознать в теле макроопределения формаль- ный аргумент. Это, например, может произойти в случае, когда он является частью некоторого идентификатора. В этом случае последовательность симво- лов формального аргумента отделяют от остального контекста с помощью спе- циального символа &. Этот прием часто используется для задания модифици- руемых идентификаторов и кодов операций. К примеру, определим макрос, который предназначен для генерации в программе некоторой таблицы, причем параметры этой таблицы можно задавать с помощью аргументов макрокоманды: def .table macro type=b, len=REQ tabl.&type d&typelen dup (0) endm 1 Под текущей системой счисления понимается то, как интерпретируются транслятором числа или строки с фиксированным числовым значением - как двоичные, десятичные или шестнадцатерич- ные числа. По умолчанию транслятор трактует их как десятичные. Ассемблер имеет специаль- ную директиву .radix, которая дает возможность изменить текущую систему счисления. В каче- стве операнда директива .radix имеет значение 2, 10 или 16, что означает выбор, соответственно, двоичной, десятичной или шестнадцатеричной системы счисления.
304 Урок 13. Макросредства языка ассемблера .data def.tabl b,10 def_tabl w,5 После того как вы подвергнете трансляции текст программы, содержащий эти строки, вы получите следующие макрорасширения: tabl.b db 10 dup (0) tabl.w dw 10 dup (0) Символ & можно применять и для распознавания формального аргумента в строке, заключенной в кавычки "". Например: num_char macro message подсчитать количество (num) символов в строке jmp ml elem db "Строка &message содержит" ; число символов в строке message в коде ASCII numdb 2 dup (0) db " символов",10,13,’$’ ;конец строки ;для вывода функцией 09h ml: ; вывести elem на экран endm В связи с рассмотрением последнего фрагмента разберем ситуацию, когда тело макроопределения содержит метку или имя в директиве резервирования и инициализации данных. Если в программе некоторая макрокоманда вызывает- ся несколько раз, то в процессе макрогенерации возникнет ситуация, когда в программе один идентификатор будет определен несколько раз, что, естествен- но, будет распознано транслятором как ошибка. Для выхода из подобной ситу- ации применяют директиву local, которая имеет следующий синтаксис: local список_идентификаторов Эту директиву необходимо задавать непосредственно за заголовком макро- определения. Результатом работы этой директивы будет генерация в каждом экземпляре макрорасширения уникальных имен для всех идентификаторов, пе- речисленных в список_идентификаторов. Эти уникальные имена имеют вид ??хххх, где хххх — шестнадцатеричное число. Для первого идентификатора в первом экземпляре макрорасширения хххх= 0000, для второго — хххх= 0001 и т. д. Контроль за правильностью размещения и использования этих уникальных имен берет на себя ассемблер. Для того чтобы вам окончательно все стало по- нятно, введем и подвергнем трансляции листинг 13.2. В нем, кроме некоторых
Макрокоманды 305 ранее рассмотренных макрокоманд, содержится макрокоманда num_char. Ее на- значение — подсчитывать количество символов в строке, адрес которой переда- ется этой макрокоманде в качестве фактического параметра. Строка должна удовлетворять требованию, предъявляемому к строке, предназначенной для вы- вода на экран функцией 09h прерывания 2 lh, то есть заканчиваться символом $. Другой момент, который нашел отражение в этой программе, — использование символа & для распознавания формального аргумента в строке, заключенной в кавычки ” ” (см. последний фрагмент). Листинг 13.2. Пример 2 создания и использования макрокоманд ;prg_13_2.asm init_ds macro ; макрос настройки ds на сегмент данных mov ах,data mov ds, ах хог ах,ах endm out_str macro str ; макрос вывода строки на экран. ;На входе - выводимая строка. ;На выходе - сообщение на экране. push ах mov ah,09h mov dx,offset str int 21h pop ax endm exit macro ; макрос конца программы mov ax,4c00h int 21h endm num_char macro message local ml,elem,num,errjnes,find,num_exit ;макрос подсчета количества символов в строке. ;Длина строки - не более 99 символов. ;Вход: message - адрес строки символов, ограниченной ; Выход: в al —количество символов в строке message продолжение &
306 Урок 13. Макросредства языка ассемблера ;и вывод сообщения jmp ml elem db "Строка &message содержит " numdb 2 dup (0) ; число символов в строке ; message в коде ASCII db ” символов",10,13,’S’ ;конец строки ;для вывода функцией 09h errjnes db "Строка &message не содержит символа конца строки", 10,13, ml: сохраняем используемые в макросе регистры push es push ex push ax push di push ds pop es ; настройка es на ds mov al, ;символ для поиска - "$" old ; сброс флага df lea di,message ;загрузка в es:di смещения ; строки message push di ; запомним di - адрес начала строки mov сх,99 ; для префикса герпе - максимальная ; длина строки ; поиск в строке (пока нужный символ ;и символ в строке не равны) ; выход - при первом совпавшем герпе scasb je find ; если символ найден - переход на обработку ; вывод сообщения о том, что символ не найден push ds ; подставляем cs вместо ds для функции 09h (int21h) push cs pop ds out_str errjnes pop ds jmp num_exit ;выход из макроса find: ;совпали
Макрокоманды 307 ;считаем количество символов в строке: pop ax ; восстановим адрес начала строки sub di, ax ;(di)=(di)-(ax) xchg di, ax ; (di) <-> (ах) sub al,3 ; корректировка на служебные ; символы - 10, 13, aam ; в al две упакованные BCD-цифры ; результата подсчета or ax,3030h ;преобразование результата ; в код ASCII mov cs:num,ah mov cs:num+1,al ;вывести elem на экран push ds ; подставляем cs вместо ds для функции 09h (int21h) push cs pop ds out_str elem pop ds num_exit: push di push ax push ex push es endm data segment para public ’’data” msg_1 db "Строка_1 для испытания”, 10,13, ’$’ msg_2 db ”Строка_2 для второго испытания", 10,13, ’$’ data ends stksegment stack db 256 dup("?") stkends code segment para public "code" assume cs:code,ds:data,ss:stk продолжение&
308 Урок 13. Макросредства языка ассемблера main proc init_ds out_str msg_1 num_char msg_1 out_str msg_2 num_char msg_2 exit main endp code ends end main В теле макроопределения можно размещать комментарии и делать это особым образом. Если применить для обозначения комментария не одну, как обычно, а две подряд идущие точки с запятой, то при генерации макрорасширения этот комментарий будет исключен. Если по какой-то причине необходимо присут- ствие комментария в макрорасширении, то его нужно задавать обычным обра- зом, то есть с помощью одинарной точки с запятой. Например: mes macro messsage ;этот комментарий будет включен в текст листинга ;;этот комментарий не будет включен в текст листинга endm Макродирективы С помощью макросредств ассемблера можно не только частично изменять вхо- дящие в макроопределение строки, но и модифицировать сам набор этих строк и даже порядок их следования. Сделать это можно с помощью набора макроди- ректив (далее — просто директив). Их можно разделить на две группы: О директивы повторения WHILE, REPT, IRP и IRPC. Директивы этой группы пред- назначены для создания макросов, содержащих несколько идущих подряд одинаковых последовательностей строк. При этом возможна частичная мо- дификация этих строк; О директивы управления процессом генерации макрорасширения EXITM и GOTO. Они предназначены для управления процессом формирования макрорасши- рения из набора строк соответствующего макроопределения. С помощью этих директив можно как исключать отдельные строки из макрорасшире- ния, так и вовсе прекращать процесс генерации. Директивы EXITM и GOTO обычно используются вместе с условными директивами компиляции, поэто- му они будут рассмотрены вместе с ними.
Макродирективы 309 Директивы WHILE и REPT Директивы WHILE и REPT применяют для повторения определенное количество раз некоторой последовательности строк. Эти директивы имеют следующий синтак- сис: WHILE константное_выражение последовательность_строк ENDM REPT константное_выражение последовательность строк ENDM Обратите внимание, что последовательность повторяемых строк в обеих дирек- тивах ограничена директивой ENDM. При использовании директивы WHILE макрогенератор транслятора будет повто- рять последовательность_стро1< до тех пор, пока значение константное_выражение не станет равно нулю. Это значение вычисляется каждый раз перед очередной ите- рацией цикла повторения (то есть значение константное_выражение должно подвергаться изменению внутри последовательность_строк в процессе макрогене- рации). Директива REPT, подобно директиве WHILE, повторяет последовательность_строк столько раз, сколько это определено значением константное_выражение. Отличие этой директивы от WHILE состоит в том, что она автоматически уменьшает на единицу значение константное_выражение после каждой итерации. В качестве при- мера рассмотрим листинг 13.3. В нем демонстрируется применение директив WHILE и REPT для резервирования области памяти в сегменте данных. Имя иденти- фикатора и длина области задаются в качестве параметров для соответствующих макросов def_st0—1 и def_sto_2. Листинг 133. Использование директив повторения ; prg_13_3.asm def_sto_1 macro id_table, In: =<5> ;макрос резервирования памяти длиной len. ; Используется WHILE id-table label byte len=ln while len db 0 len=len-1 endm продолжение &
310 Урок 13. Макросредства языка ассемблера endm def_sto_2 macro id_table, len ; макрос резервирования памяти длиной len id_table label byte rept len db 0 endm endm data segment para public “data” def_sto_1 tab—1,10 def_sto_2 tab_2,10 data ends ; сегменты данных и стека в этой программе необязательны end Заметьте, что счетчик повторений в директиве REPT уменьшается автоматически после каждой итерации цикла. Проанализируйте результат трансляции листин- га 13.3. Таким образом, директивы REPT и WHILE удобно применять для «размножения» в тексте программы последовательности одинаковых строк без внесения в эти строки каких-либо изменений. Следующие две директивы, IRP и IRPC, делают этот процесс более гибким, позволяя модифицировать на каждой итерации неко- торые элементы в последовательность_строк. ДирективаIRP Директива IRP имеет следующий синтаксис: IRP формальный_аргумент,<строка_символов_1.строка_символов_М> последовательность_строк ENDM Действие данной директивы заключается в том, что она повторяет последователь- ность_строк N раз, то есть столько раз, сколько строк_символов заключено в угло- вые скобки во втором операнде директивы IRP. Но это еще не все. Повторение последовательности_строк сопровождается заменой в ней формального_аргумента строкой символов из второго операнда. Так, при первой генерации последователь- ности_строк формальный_аргумент в них заменяется на строка_символов_1. Если есть строка_символов__2, то это приводит к генерации второй копии последовательность- _строк, в которой формальный_аргумент заменяется на строка_символов_2. Эти дей- ствия продолжаются до строка^ символов-N включительно.
Директивы условной компиляции 311 К примеру, рассмотрим результат определения в программе следующей конст- рукции: 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, но отличается тем, что она на каждой оче- редной итерации заменяет формальный-аргумент очередным символом из строка_симво- лов. Понятно, что количество повторений последовательность_строк будет определять- ся количеством символов в строке_символов. К примеру: irpc rg,<abcd> push rg&x endm В процессе макрогенерации эта директива развернется в следующую последова- тельность строк: push ах push bx push сх push dx Директивы условной компиляции Последний тип макросредств — директивы условной компиляции. Существует два типа этих директив: О директивы компиляции по условию позволяют проанализировать определен- ные условия в ходе генерации макрорасширения и, при необходимости, из- менить этот процесс;
312 Урок 13. Макросредства языка ассемблера О директивы генерации ошибок по условию также контролируют ход генерации макрорасширения с целью генерации или обнаружения определенных ситу- аций, которые могут интерпретироваться как ошибочные. С этими директивами применяются директивы управления процессом генерации макрорасширений EXITM и GOTO. Директива EXITM не имеет операндов, и ее действие заключается в том, что она немедленно прекращает процесс генерации макрорасширения, начиная с того места, где она встретилась в макроопределении. Директива GOTO имя_метки переводит процесс генерации макроопределения в дру- гое место, прекращая тем самым последовательное разворачивание строк макро- определения. Метка, на которую передается управление, имеет специальный фор- мат: :имя_метки Примеры применения этих директив будут приведены ниже. Директивы компиляции по условию Данные директивы предназначены для организации выборочной трансляции фрагментов программного кода. Такая выборочная компиляция означает, что в макрорасширение включаются не все строки макроопределения, а только те, которые удовлетворяют определенным условиям. То, какие конкретно условия должны быть проверены, определяется типом условной директивы. Введение в язык ассемблера этих директив значительно повышает его мощь. Всего имеется 10 типов условных директив компиляции. Их логично попарно объединить в четыре группы: О директивы IF и IFE — условная трансляция по результату вычисления логи- ческого выражения; О директивы IFDEF и IFNDEF — условная трансляция по факту определения символического имени; О директивы IFB и IFNB — условная трансляция по факту определения факти- ческого аргумента при вызове макрокоманды; О директивы IFIDN, IFIDNI, IFDIF и IFDIFI — условная трансляция по результа- ту сравнения строк символов. Условные директивы компиляции имеют общий синтаксис и применяются в составе следующей синтаксической конструкции: IFxxx логическое_выражение_или_аргументы фрагмент_программы_1 ELSE фрагмент_программы_2 ENDIF
Директивы условной компиляции 313 Заключение некоторых фрагментов текста программы — фрагмент_программы_1 и фрагмент_программы_2 — между директивами IFxxx, ELSE и ENDIF приводит к их выборочному включению в объектный модуль. Какой именно из этих фрагмен- тов — фрагмент_программы_1 или фрагмент_программы_2 — будет включен в объект- ный модуль, зависит от конкретного типа условной директивы, задаваемого зна- чением ххх, и значения условия, определяемого операндом (операндами) условной директивы логическое_выражение_или_аргумент(ы). Директивы IF и IFE Синтаксис этих директив следующий: IF(E) логическое_выражение фрагмент_программы_1 ELSE фра гмент_про граммы_2 ENDIF Обработка этих директив макроассемблером заключается в вычислении логи- ческого выражения и включении в объектный модуль фрагмент_программы_1 или фрагмент_программы_2 в зависимости от того, в какой директиве IF или IFE, это выражение встретилось: О если в директиве IF логическое выражение истинно, то в объектный модуль помещается фрагмент_программы_1. Если логическое выражение ложно, то при наличии директивы ELSE в объектный код помещается фрагмент_про- граммы_2. Если же директивы ELSE нет, то вся часть программы между дирек- тивами IF и ENDIF игнорируется и в объектный модуль ничего не включает- ся. Кстати сказать, понятие истинности и ложности значения логического выражения весьма условно. Ложным оно будет считаться, если его значение равно нулю, а истинным — при любом значении, отличном от нуля. О директива IFE, аналогично директиве IF, анализирует значение логического выражения. Но теперь для включения фрагмент_программы_1 в объектный модуль требуется, чтобы логическое_выражение имело значение «ложь». Директивы IF и IFE очень удобно использовать при необходимости изменения текста программы в зависимости от некоторых условий. К примеру, составим макрос для определения в программе области памяти длиной не более 50 и не менее 10 байт (листинг 13.4). Листинг 13.4. Использование условных директив IF и IFE <1> ;prg_13_4.asm <2> masm <з> model small <4> stack 256 продолжение &
314 Урок 13. Макросредства языка ассемблера <5> def_tab_50 macro len <б> if len GE 50 <7> GOTO exit <8> endif <9> if len LT 10 <10> :exit <11> EXITM <12> endif <13> rept len <14> db 0 <15> endm <1б> endm <17> .data <18> def_tab_50 15 <19> def_tab_50 5 <20> .code <21> main: <22> mov ax,©data <23> mov ds, ax <24> exit: <25> mov ax,4c00h <2б> int 21 h <27> end main Введите и оттранслируйте листинг 13.4. Не забывайте о том, что условные ди- рективы действуют только на шаге трансляции, и поэтому результат их ра- боты можно увидеть только после макрогенерации, то есть в листинге програм- мы. В нем вы увидите, что в результате трансляции строка 18 листинга 13.3 развернется в пятнадцать нулевых байт, а строка 19 оставит макрогенератор совершенно равнодушным, так как значение фактического операнда в строках 6 и 9 будет ложным. Обратите внимание, что для обработки реакции на лож- ный результат анализа в условной директиве мы использовали макродиректи- вы EXITM и GOTO. Наверное, в данном случае можно было составить и более оптимальный вариант макрокоманды для резервирования некоторого про- странства памяти в сегменте данных, но такой способ был выбран, исходя из учебных целей. Другой интересный и полезный вариант применения директив IF и IFE — от- ладочная печать. Суть здесь в том, что в процессе отладки программы почти всегда возникает необходимость динамически отслеживать состояние опреде- ленных программно-аппаратных объектов, в качестве которых могут выступать
Директивы условной компиляции 315 переменные, регистры микропроцессора и т. п. После этапа отладки отпадает не- обходимость в таких диагностических сообщениях. Для их устранения нужно корректировать исходный текст программы, после чего ее следует подвергнуть повторной трансляции. Но есть более изящный выход. Можно определить в про- грамме некоторую переменную, к примеру debug, и использовать ее совместно с условными директивами IF или IFE. К примеру: <1> < 2> debug equ 1 <з> < 4> .code <5> < б> if debug < 7> ; любые команды и директивы ассемблера < 8> ; (вывод на печать или монитор) < 9> endif На время отладки и тестирования программы вы можете заключить отдельные участки кода в своеобразные операторные скобки в виде директив IF и ENDIF (строки 6-9 последнего фрагмента), которые реагируют на значение логичес- кой переменной debug. При значении debug = 0 транслятор полностью проигно- рирует текст внутри этих условных операторных скобок; при debug = 1, наобо- рот, будут выполнены все действия, описанные внутри них. Директивы IFDEF и IFNDEF Синтаксис этих директив следующий: IF(N)DEF символическое_имя фрагмент_программы_1 ELSE фрагмент_программы_2 ENDIF Данные директивы позволяют управлять трансляцией фрагментов программы в зависимости от того, определено или нет в программе некоторое символическо- е_имя. Директива IFDEF проверяет, описано или нет в программе символическое_имя, и если это так, то в объектный модуль помещается фрагмент_программы_1. В против- ном случае, при наличии директивы ELSE, в объектный код помещается фрагмент- _программы_2. Если же директивы ELSE нет (и символическое_имя в программе не описано), то вся часть программы между директивами IF и ENDIF игнорируется и в объектный модуль не включается. Действие IFNDEF обратно IFDEF. Если символического имени в программе нет, то транслируется фрагмент_программы_1. Если оно присутствует, то при наличии ELSE
316 Урок 13. Макросредства языка ассемблера транслируется фрагмент_программы_2. Если ELSE отсутствует, а символическое_имя в программе определено, то часть программы, заключенная между IFNDEF и ENDIF, игнорируется. В качестве примера рассмотрим ситуацию, когда в объектный модуль программы должен быть включен один из трех фрагментов кода. Какой из трех фрагментов будет включен в объектный модуль, зависит от значения некоторого идентифи- катора switch: О если switch = 0, то сгенерировать фрагмент для вычисления выражения у = х*2**п; О если switch = 1, то сгенерировать фрагмент для вычисления выражения у = х/2**п; О если switch не определен, то ничего не генерировать. Соответствующий фрагмент исходной программы может выглядеть так: ifndef sw ;если sw не определено, то выйти из макроса EXITM else ; иначе - на вычисление mov cl,n ife sw sal x,cl ; умножение на степень 2 ; сдвигом влево else sar x.cl ; деление на степень 2 ;сдвигом вправо endif endif Как видим, эти директивы логически связаны с директивами IF и IFE, то есть их можно применять в тех же самых случаях, что и последние. Есть еще одна интересная возможность использования этих директив. На уроке 4 мы обсуж- дали формат командной строки TASM и говорили об опциях, которые можно в ней задавать. Вспомните одну из них — опцию /d идентификатор_значение. Ее использование дает возможность управлять значением идентификатора прямо из командной строки, не изменяя при этом текста программы. В качест- ве примера рассмотрим листинг 13.5. В этом примере мы пытаемся с помо- щью макроса контролировать процесс резервирования и инициализации неко- торой области памяти в сегменте данных. Листинг 13.5. Инициализация значения идентификатора из командной строки < i> ; prg_13_5.asm < 2> masm < з> model small
Директивы условной компиляции 317 < 4> stack 256 < 5> def_tab_50 macro len <б> ifndeflen < 7> display "sizejn не определено, задайте значение 10<sizejn<50" < 8> exitm < 9> else < ю> if len GE 50 < и> GOTO exit < 12> endif < 13> if len LT 10 < 14> :exit < 15> EXITM < 16> endif < 17> rept len < 18> db 0 < 19> endm < 20> endif < 2i> endm < 22> ;size_m=15 < 23> .data < 24> def_tab_50 sizejn <25> < 2 6> .code < 27> main: < 2 8> mov ax, ©data < 29> mov ds, ax < зо> exit: < 3i> mov ax,4c00h < 32> int 21h < зз> end main Запустив этот пример на трансляцию, вы получите сообщение о том, что забыли определить значение переменной size_m. После этого попробуйте два варианта действий: О определите где-то в начале исходного текста программы значение этой пере- менной с помощью equ: sizejn equ 15
318 Урок 13. Макросредства языка ассемблера О запустите программу на трансляцию командной строкой вида tasm /dsize_m=15 /zi prg_13_2,,, В листинге 13.5 мы использовали еще одну возможность транслятора — дирек- тиву display, с помощью которой можно формировать пользовательское сообще- ние в процессе трансляции программы. Директива display будет рассмотрена в конце данного урока. Директивы IFB и IFNB Синтаксис этих директив следующий: IF(N)B аргумент фрагмент_программы_1 ELSE фрагмент_программы_2 ENDIF Данные директивы используются для проверки фактических параметров, переда- ваемых в макрос. При вызове макрокоманды они анализируют значение аргу- мента, и в зависимости от того, равно оно пробелу или нет, транслируется либо фрагмент_программы_1, либо фрагмент_программы_2. Какой именно фрагмент будет выбран, зависит от кода директивы: О Директива IFB проверяет равенство аргумента пробелу. В качестве аргумен- та может выступать имя или число. Если его значение равно пробелу (то есть фактический аргумент при вызове макрокоманды не был задан), то транслируется и помещается в объектный модуль фрагмент_программы_1. В противном случае, при наличии директивы ELSE, в объектный код помещает- ся фрагмент_программы_2. Если же директивы ELSE нет, то при равенстве аргу- мента пробелу вся часть программы между директивами IFB и ENDIF игнори- руется и в объектный модуль не включается. О Действие IFNB обратно IFB. Если значение аргумента в программе не равно пробелу, то транслируется фрагмент_программы_1. В противном случае, при наличии директивы ELSE, в объектный код помещается фрагмент_программы_2. Если же директивы ELSE нет, то вся часть программы (при неравенстве аргу- мента пробелу) между директивами IFNB и ENDIF игнорируется и в объект- ный модуль не включается. В качестве типичного примера применения этих директив предусмотрим стро- ки в макроопределении, которые будут проверять, указывается ли фактический аргумент при вызове соответствующей макрокоманды: show macro reg ifb <reg> display "не задан регистр"
Директивы условной компиляции 319 exitm endif endm Если теперь в сегменте кода вызвать макрос show без аргументов, то будет выве- дено сообщение о том, что не задан регистр и генерация макрорасширения будет прекращена директивой exitm. Директивы IFIDN, IFIDNI, IFDIF и IFDIFI Эти директивы позволяют не просто проверить наличие или значение аргумен- тов макрокоманды, но и выполнить идентификацию аргументов как строк сим- волов. Синтаксис этих директив: IFIDN(I) аргумент_1,аргумент_2 фрагмент_программы_1 ELSE фрагмент_программы_2 ENDIF IFDIF(I) аргумент-"!,аргумент_2 фрагмент_программы_1 ELSE фрагмент_программы_2 ENDIF В этих директивах проверяются аргумент-1 и аргумент_2 как строки символов. Ка- кой именно код — фрагмент_программы_1 или фрагмент_программы_2 — будет трансли- роваться по результатам сравнения, зависит от кода директивы. Парность этих директив объясняется тем, что они позволяют учитывать либо не учитывать раз- личие строчных и прописных букв. Так, директивы IFIDNI и IFDIFI игнорируют это различие, a IFIDN и IFDIF — учитывают: Директива IFIDN(I) сравнивает символьные значения аргументу и аргумент_2. Если результат сравнения положительный, то фрагмент_программы_1 транслируется и помещается в объектный модуль. В противном случае, при наличии директивы ELSE, в объектный код помещается фрагмент_программы_2. Если же директивы ELSE нет, то вся часть программы между директивами IFIDN(I) и ENDIF игнорируется и в объектный модуль не включается. Действие IFDIF(I) обратно IFIDN(I). Если результат сравнения отрицательный (строки не совпадают), транслируется фрагмент_программы_1. В противном случае все происходит аналогично вышерассмотренным директивам. Как мы уже упоминали выше, эти директивы удобно применять для проверки фактических аргументов макрокоманд. К примеру, проверим, какой из регист-
320 Урок 13. Макросредства языка ассемблера ров — al или ah — передан в макрос в качестве параметра (проверка проводится без учета различия строчных и прописных букв): show macro rg ifdifi <al>,<rg> goto M_al else ifdifi <ah>,<rg> goto M_ah else exitm endif endif :M_al :M_ah endm Вложенность директив условной трансляции Как мы неоднократно видели в приведенных выше примерах, TASM допускает вложенность условных директив компиляции. Более того, так как вложенность требуется довольно часто, TASM предоставляет набор дополнительных дирек- тив формата ELSEIFxxx, которые заменяют последовательность подряд идущих ELSE и IFxxx в структуре: IFxxx ELSE IFxxx ENDIF ENDIF Эту последовательность условных директив можно заменить эквивалентной пос- ледовательностью дополнительных директив: IFxxx ELSEIFxxx ENDIF
Директивы условной компиляции 321 Наличие ххх в ELSExxx говорит о том, что каждая из директив IF, IFB, IFIDN и т. д. имеет аналогичную директиву ELSEIF, ELSEIFB, ELSEIFIDN и т. д. В конечном итоге это улучшает читаемость кода. В последнем примере фрагмента макроса, проверяющем, имя какого регистра было передано в макрос, наблюдается по- добная ситуация. Последовательность ELSE и IFDIFI можно записать так, как в строке 4: < i> show macro rg < 2> ifdif i <al>, <rg> < з> goto M_al < 4> elseifdifi <ah>,<rg> < 5> goto M_ah < 6> else < 7> exitm < 8> endif < 9> :M_al <io> < n> :M_ah <12> < 13> endm Директивы генерации ошибок В языке TASM есть ряд директив, называемых директивами генерации пользова- тельской ошибки. Их можно рассматривать и как самостоятельное средство, и как метод, расширяющий возможности директив условной компиляции. Они предназначены для обнаружения различных ошибок в программе, таких как неопределенные метки или пропуск параметров макроса. Директивы генерации пользовательской ошибки по принципу работы можно разделить на два типа: О безусловные директивы, генерирующие ошибку трансляции без проверки каких-либо условий; О условные директивы, генерирующие ошибку трансляции после проверки определенных условий. Большинство директив генерации ошибок имеют два обозначения, хотя прин- цип их работы одинаков. Второе название отражает их сходство с директивами условной компиляции. При дальнейшем обсуждении такие парные директивы будут приводиться в скобках. Безусловная генерация пользовательской ошибки К безусловным директивам генерации пользовательской ошибки относится только одна директива — это ERR (.ERR).
322 Урок 13. Макросредства языка ассемблера Данная директива, будучи вставлена в текст программы, безусловно приводит к генерации ошибки на этапе трансляции и удалению объектного модуля. Она очень эффективна при ее использовании с директивами условной компиляции или в теле макрокоманды с целью отладки. К примеру, эту директиву можно было бы вставить в ту ветвь программы (в последнем рассмотренном нами макроопределении), которая выполняется, если указанный в качестве аргумен- та регистр отличен от al и ah: show macro 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) <имя_формального_аргумента> — генерация пользовательской ошибки, если <имя_формального_аргумента> пропущено;
Директивы условной компиляции 323 . ERRNB (ERRIFNB) <имя__формального__аргумента> — генерация пользовательской ошибки, если <имя__формального__аргумента> присутствует. Данные директивы применяются для генерации ошибки трансляции в зависимости от того, задан или нет при вызове макрокоманды фактический аргумент, соответ- ствующий формальному аргументу в заголовке макроопределения с именем <имя__- формального__аргумента>. По принципу действия эти директивы полностью аналогич- ны соответствующим директивам условной компиляции IFB и IFNB. Их обычно используют для проверки задания параметров при вызове макроса. Строка имя__формального_аргумента должна быть заключена в угловые скобки. К примеру, определим обязательность задания фактического аргумента, соответ- ствующего формальному аргументу гд, в макросе show: < i> show macro гд < 2> ;если гд в макрокоманде не будет задан, < з> ;то завершить компиляцию < 4> .errb <rg> < 5> ;текст макроопределения < б> ;... < 7> endm Директивы .ERRDEF (ERRIFDEF) и .ERRNDEF (ERRIFNDEF) Синтаксис директив: . ERRDEF (ERRIFDEF) символическое_имя — если указанное имя определено до выдачи этой директивы в программе, то генерируется пользовательская ошибка; . ERRNDEF(ERRIFNDEF) символическое_имя — если указанное символическое_имя не определено до момента обработки транслятором данной директивы, то генерируется пользовательская ошибка. Данные директивы генерируют ошибку трансляции в зависимости от того, опре- делено или нет некоторое символическое_имя в программе. Не забывайте о том, что компилятор TASM по умолчанию формирует объектный модуль за один проход исходного текста программы. Следовательно, директивы .ERRDEF (ERRIFDEF) и . ERRNDEF (ERRIFNDEF) отслеживают факт определения символического_имени только в той части исходного текста, которая находится до этих директив. Директивы .ERRDIF (ERRIFDIF) и .ERRIDN (ERRIFIDN) Синтаксис директив: . ERRDIF (ERRIFDIF) <строка_ 1>,<строка_2> — директива, генерирующая пользовательскую ошибку, если две строки посимвольно не совпадают.
324 Урок 13. Макросредства языка ассемблера Строки могут быть символическими именами, числами или выражениями и должны быть заключены в угловые скобки. Аналогично директиве условной компиляции IFDIF, при сравнении учитывается различие про- писных и строчных букв. . ERRIDN (ERRIFIDN) <строка_ 1>,<строка_2> — директива, генерирующая пользовательскую ошибку, если строки посимвольно идентичны. Строчное и прописное написание одной и той же буквы воспринимается как разные символы. Для того чтобы игнорировать различия строчных и прописных букв, су- ществуют аналогичные директивы: ERRIFDIFI <строка_1>,<строка_2> — то же, что и ERRIFDIF, но игнорируется различие строчных и прописных букв при сравнении <строка_1> и <строка_2>. ERRIFIDNI <строка_1>,<строка_2> — то же, что и ERRIFIDN, но игнорируется различие строчных и прописных букв при сравнении <строка_1> и <строка_2>. Данные директивы, как и соответствующие им директивы условной компиля- ции, удобно применять для проверки передаваемых в макрос фактических па- раметров. Директивы .ERRE (ERRIFE) и .ERRNZ (ERRIF) Синтаксис директив: . ERRE (ERRIFE) константное_выражение — директива вызывает пользова- тельскую ошибку, если константное_выражение ложно (равно нулю). Вы- числение константного_выражения должно приводить к абсолютному значе- нию, и это выражение не может содержать компонентов, являющихся ссылками вперед. . ERRNZ(ERRIF) константное_выражение — директива вызывает пользова- тельскую ошибку, если константное_выражение истинно (не равно нулю). Вычисление константного_выражения должно приводить к абсолютному зна- чению и неможет содержать компонентов, являющихся ссылками вперед. Константные выражения в условных директивах Как вы успели заметить, во многих условных директивах в формировании усло- вия участвуют выражения. Результат вычисления этого выражения обязательно должен быть константой. Хотя его компонентами могут быть и символические
Константные выражения в условных директива^ 325 параметры, но их сочетание в выражении должно давать абсолютный результат. К примеру: .data mas db len dd . code .erre (len-mas) It 10 ;генерация ошибки, если длина ;области mas меньше 10 байт Кроме того, выражение не должно содержать компоненты, которые транслятор еще не обработал к тому месту программы, где находится условная директива. Также мы отметили, что логические результаты «истина» и «ложь» являются условными в том смысле, что ноль соответствует логическому результату «ложь», а любое ненулевое значение — «истине». Но в языке ассемблера сущес- твуют операторы, которые позволяют сформировать и «чисто логический» ре- зультат. Это так называемые операторы отношений, выражающие отношение двух значений или константных выражений. В контексте условных директив вместе с операторами отношений можно рассматривать и логические операто- ры. Результатом работы и тех, и других может быть одно из двух значений: О истина — число, которое содержит двоичные единицы во всех разрядах; О ложь — число, которое содержит двоичные нули во всех разрядах. Операторы, которые можно применять в выражениях условных директив и которые формируют логические результаты, приведены в табл. 13.1 и 13.2. Таблица 13.1. Операторы отношений Оператор отношения Синтаксис Результат EQ (equal) — равно выражение_1 EQ выражение_2 Истина, если выражение_1 Равно выражение_2 NE (not equal) — выражение_1 NE Истина, если выражение_1 не равно выражение_2 не равно выражение_2 LT (less than) — выражение_1 LT Истина, если выражение_1 меньше выражение_2 меньше выражение_2 LE (less or equal) — выражение_1 LE Истина, если выражение_1 меньше или равно выражение_2 меньше или равно выражение_2 GT (greater than) — выражение_1 GT Истина, если выражение_1 больше выражение_2 больше выражение_2 GE (greater or equal) — выражение_1 GE Истина, если выражение_1 больше больше или равно выражение_2 или равно выражение_2
326 г Урок 13. Макросредства языка ассемблера Таблица 13.2. Логические операторы Логический оператор Синтаксис Результат NOT — логическое отрицание NOT выражение Истина, если выражение ложно; ложь, если выражение истинно AND — логическое И выражение_ 1 AND выражение_2 Истина, если выражение_1 и выражение_2 истинны OR — логическое ИЛИ выражение_ 1 OR выражение_2 Истина, если выражение_1 или выражение_2 истинны XOR — исключающее ИЛИ выражение_ 1 XOR выражение_2 Истина, если выражение_1 = (NOT выражение_2) Дополнительное управление трансляцией TASM предоставляет средства для вывода текстового сообщения во время трансляции программы — директивы DISPLAY и %0UT. С их помощью можно, при необходимости, следить за ходом трансляции. К примеру: display недопустимые аргументы макрокоманды %out недопустимое имя регистра В результате обработки этих директив на экран будут выведены тексты сооб- щений. Если эти директивы использовать совместно с директивами условной компиляции, то, к примеру, можно отслеживать путь, по которому осуществля- ется трансляция исходного текста программы. В заключение можно предложить читателю уже с этого момента начать фор- мировать набор полезных в его практической работе макрокоманд. В качестве основы вы можете взять файл (листинг 13.6), в который включены часто ис- пользуемые макросы. В дальнейшем, если в этом возникнет необходимость, вы будете самостоятельно дополнять его вашими макросами. Использовать макро- определения из этого файла очень просто, достаточно включить в нужном мес- те вашей программы строку с директивой include, в результате чего в ваш файл будут вставлены строки из файла, указанного в качестве операнда этой дирек- тивы. Листинг 13.6. Набор макрокоманд ;mac.inc OutStr macro str ; Вывод строки на экран. ;На входе - идентификатор начала выводимой строки. •.Строка должна заканчиваться символом ”$”.
Дополнительное управление трансляцией 327 ;На выходе- сообщение на экране. push ах mov ah,09h lea dx.str int 21h pop ax endm GetChar macro ;Ввод символа с клавиатуры. ;На выходе - в al введённый символ. push ах mov ah.Olh int 21h pop ax endm OutChar macro ; Вывод символа на экран. ;На входе - в dl выводимый символ. push ах mov ah,02h int 21h pop ax endm clear..r macro rg •.очистка регистра rg xor rg, rg endm conv-16-2 macro ; макрос преобразования символа шестнадцатеричной цифры ; в ее двоичный эквивалент в al sub dl,30h cmp dl,9h jle $+5 sub dl,7h endm GetSt r mac ro buf,max_len ;ввод строки произвольной длины (функция Oah int 21h) продолжение &
328 Урок 13. Макросредства языка ассемблера ;на входе: ;buf - адрес строки, куда будет помещен ввод ;max_len - максимальная длина вводимой строки ; на выходе - введенная строка по адресу buf ;al - длина введенной строки push es push dx push ex xor ex,ex mov buf,max_len mov ah.Oah lea dx.buf int 21h mov al,buf+1 mov cl,al ;длина введенной строки в al ; сдвиг al на два байта влево: push ds push es lea si,buf+2 lea di,buf repmovsb pop ex pop dx pop es GetStr endm init_ds macro ; макрос настройки ds на сегмент данных mov ах,data mov ds, ах xor ах,ах endm delay macro time local ext,iter ; макрос задержки. На входе - значение переменной задержки (в мкс). push сх mov cx.time
Дополнительное управление трансляцией 329 ext: push сх mov сх, 5000 ;это значение можно поменять, исходя из производительности процессора. iter: loop iter pop сх loop ext pop ex ENDM Exit macro ;Выход из программы. mov ax,4c00h int 21h endm Основная задача этой книги — научить вас программировать на языке ассемб- лера. Как вы уже успели понять, нельзя изучать этот язык в отрыве от рассмот- рения процессов, происходящих во время выполнения программы на компью- тере. Одно из средств изучения таких процессов — отладчик. Но он решает проблему глобально, что далеко не всегда нужно. Тем более, как мы увидим далее, возможности отладчика не безграничны. Поэтому необходимо иметь бо- лее универсальное средство, которое бы позволило осуществлять «подглядыва- ние» за содержимым регистра или области памяти динамически, во время вы- полнения программы. Для этого разработаем еще один макрос. Назовем его, к примеру, show. Его аргументом может быть один из четырех регистров — al, ah, ах, еах. С помощью этого макроса можно визуализировать содержимое лю- бого из доступных регистров или области памяти длиной до 32 бит. Для этого достаточно лишь переслать содержимое нужного объекта (регистра или ячейки памяти) с учетом его размера в один из регистров al, ah, ах, еах. Имя одного из этих регистров указывается затем в качестве фактического аргумента макроко- манды show. Второй аргумент этого макроса — позиция на экране. Задавая определенные значения, мы можем судить о том, какая именно макрокоманда show сработала. Еще одна немаловажная особенность данного макроса — в его возможности работать как в реальном, так и защищенном режимах. Распозна- вание текущего режима работы микропроцессора выполняется автоматически. Текст макроопределения show достаточно велик и по этой причине вынесен в приложение 9. Пример использования этого макроса приведен в листинге 13.7. Листинг 13.7. Пример использования макроса show ;prg_13_7.asm MASM MODEL small STACK 256 продолжение &
330 Урок 13. Макросредства языка ассемблера . 486р include show.inc .data pole dd 3cdf436fh .code main: mov ax,©data mov ds, ax xor ax,ax mov ax.lfOfh show al,0 show ah,160 show ax,320 mov eax,pole show eax,480 exit: mov ax,4c00h int 21h end main Посвятить время рассмотрению этого макроса полезно еще и потому, что при его разработке было использовано большинство средств, обсуждавшихся на данном уроке. Подведем некоторые итоги: 0 Преимущества языка ассемблера связаны, в частности, с макросредствами. Как говорят, если бы макросредств в нем не было, то их нужно было бы придумать. 0 Макросредства — это основные инструменты модификации текста програм- мы на этапе ее трансляции. Принцип работы макросредств основан на прин- ципе препроцессорной обработки, который заключается в том, что текст, поступающий на вход транслятора, перед собственно компиляцией подвер- гается преобразованию и может значительно отличаться от синтаксически правильного текста, воспринимаемого компилятором. Роль препроцессора в трансляторе TASM выполняет макрогенератор. 0 Для того чтобы макрогенератор мог выполнить свою работу, текст програм- мы должен удовлетворять определенным требованиям. Макрогенератору не- обходимо сообщить, на какие элементы исходного текста он должен реаги-
Дополнительное управление трансляцией 331 0 0 0 0 ровать и какие действия должны быть произведены. Можно выделить не- сколько типов таких элементов. Псевдооператоры equ и = — макрогенератор выполняет простейшие дей- ствия, заменяя в последующем тексте программы символическое имя из правой части этих операторов строкой из левой. Макрокоманда — строка в исходной программе, которой соответствует специальный блок — макроопределение. Макрокоманда может иметь аргу- менты, с помощью которых можно изменять текст макроопределения. Макрогенератор, встречая макрокоманду в тексте программы, корректиру- ет текст соответствующего макроопределения, исходя из аргументов этой макрокоманды, и вставляет его в текст программы вместо данной макроко- манды. Процесс такого замещения называется макрогенерацией. Условные директивы компиляции позволяют не просто модифицировать отдельные строки программы, но и, исходя из определенных условий, управ- лять включением в загрузочный модуль отдельных фрагментов программы. Эти директивы наиболее эффективны для организации работы с аргумен- тами, передаваемыми при макрогенерации в макроопределения из макро- команд, хотя отдельные директивы есть смысл применять и вне макрооп- ределений в любом месте программы. Директивы генерации ошибок — подобно условным директивам позволя- ют анализировать определенные условия в процессе трансляции програм- мы и генерировать ошибку по результатам анализа.
Модульное программирование □ Основы структурного программирования □ Средства ассемблера для поддержки структурного программирования □ Процедуры и организация связей между процедурами на языке ассемблера □ Связь между программами на языках высокого уровня и программами на ассемблере
На протяжении всей книги мы неоднократно подчеркивали тот факт, что од- ним из существенных недостатков программ на языке ассемблера, а значит, и самого языка, является их недостаточная наглядность. По прошествии даже небольшого времени программисту бывает порой трудно разобраться в деталях им же написанной программы. А о чужой программе и говорить не приходит- ся. Если в ней нет хотя бы минимальных комментариев, то разобраться с тем, что она делает, довольно трудно. Причины этого тоже понятны — при про- граммировании на языке ассемблера программисту необходимо производить самые элементарные действия. При этом он должен учитывать и контролиро- вать большое количество информации. Из-за того что производимые операции крайне элементарны, реализовать алгоритм задачи можно по крайней мере не- сколькими способами. А если способ решения не единственен, то и разобраться в программе подчас бывает нелегко. По мере накопления опыта эти проблемы частично снимаются. Но одного опы- та мало. Ситуация усугубляется, если работа идет в коллективе разработчиков. Тут уже нужны специальные средства. TASM предоставляет следующие орга- низационные и программные средства, позволяющие снять остроту этой про- блемы: О Документирование программистом своей работы и ее результатов. Делается это в первую очередь путем комментирования строк исходного текста про- граммы. При этом комментарии должны коротко, но точно выражать то, что делает данная программа в целом, выделять ее наиболее важные фрагменты и особенности применения отдельных команд. В конечном итоге, комменти- рование программы облегчает понимание замысла программы, но все-таки полностью не снимает проблему. О Упрощение кода программы путем замены сложных ее участков более по- нятным кодом. Для этого, в частности, можно использовать рассмотренный нами механизм макрокоманд. О Использование при разработке программных проектов достижений совре- менных технологий программирования. К настоящему моменту времени наиболее популярными и жизнеспособными оказались две технологии: структурная и объектно-ориентированная.
334 Урок 14. Модульное программирование Технологии программирования Последние версии языка ассемблера поддерживают объектно-ориентированное программирование, но реализация его достаточно сложна и требует отдельного рассмотрения. Типичному процессу написания программы на ассемблере более всего удовлетворяют концепции структурного программирования. Можно даже сказать, что для микропроцессора Intel эти концепции поддерживаются на ап- паратном уровне с помощью таких элементов архитектуры, как сегментация памяти и реализация команд передачи управления. На программном уровне поддержка заключается, в основном, в наличии соответствующих средств в конкретном компиляторе. Компилятор TASM имеет все необходимые базовые средства для поддержки структурного программирования. Наш урок будет по- священ рассмотрению этих программно-аппаратных средств. Структурное программирование Структурное программирование — методология программирования, базирую- щаяся на системном подходе к анализу, проектированию и реализации про- граммного обеспечения. Эта методология зародилась в начале 70-х годов и ока- залась настолько жизнеспособной, что и до сих пор является основной в большом количестве проектов. Основу этой технологии составляют следующие положения: О Сложная задача разбивается на более мелкие, функционально лучше управляемые задачи. Каждая задача имеет один вход и один выход. В этом случае управляющий поток программы состоит из совокупности элементарных подзадач с ясным функциональным назначением. О Простота управляющих структур, используемых в задаче. Это положе- ние означает, что логически задача должна состоять из минимальной, функционально полной совокупности достаточно простых управляющих структур. В качестве примера такой системы можно привести алгебру логики, в которой каждая функция может быть выражена через функцио- нально полную систему: дизъюнкцию, конъюнкцию и отрицание. О Разработка программы должна вестись поэтапно. На каждом этапе долж- но решаться ограниченное число четко поставленных задач с ясным по- ниманием их значения и роли в контексте всей задачи. Если такое пони- мание не достигается, это говорит о том, что данный этап слишком велик и его нужно разделить на более элементарные шаги. Понятно, что практическое использование этих положений должно быть под- креплено не только поддержкой со стороны программно-аппаратных средств, но и, в большей степени, личными качествами конкретного программиста, не последними из которых являются наличие опыта проектирования задач и уме- ние подчинить сам процесс программирования некоторой системе.
Технологии программирования 335 Конечно, язык ассемблера, в силу его специфики, довольно ограниченно реали- зует данные положения. В частности, это касается управляющих операторов цикла, условных операторов и операторов выбора. Эти операторы являются встроенными в язык высокого уровня, но в языке ассемблера их попросту нет. Были попытки разработать различные надстройки для языка ассемблера, реа- лизующие эти управляющие операторы, но широкого распространения они не получили. Мне кажется, что это и не нужно, так как реализуемые на языке ас- семблера программы решают специфичные системные задачи. Тогда возникает вопрос — а вообще совместимы ли структурное программирование и ассем- блер? Ответ положительный, особенно в том случае, если задача поддается раз- биению на более мелкие подзадачи. Тогда реализацию этих подзадач можно осуществить с использованием макрокоманд и процедур. Эти механизмы пол- ностью подходят для реализации так называемого модульного программирова- ния, которое является частью структурного подхода. Рассмотрим, что представ- ляет собой модульное программирование. Концепция модульного программирования Так же как и для структурной технологии программирования, концепцию мо- дульного программирования можно сформулировать в виде нескольких понятий и положений: О Функциональная декомпозиция задачи — разбиение большой задачи на ряд более мелких, функционально самостоятельных подзадач — модулей. Моду- ли связаны между собой только по входным и выходным данным. О Модуль — основа концепции модульного программирования. Каждый мо- дуль в функциональной декомпозиции представляет собой «черный ящик» с одним входом и одним выходом. Модульный подход позволяет безболез- ненно производить модернизацию программы в процессе ее эксплуатации и облегчает ее сопровождение. Дополнительно модульный подход позволяет разрабатывать части программ одного проекта на разных языках програм- мирования, после чего с помощью компоновочных средств объединять их в единый загрузочный модуль. О Реализуемые решения должны быть простыми и ясными. Если назначение модуля непонятно, то это говорит о том, что декомпозиция начальной или промежуточной задачи была проведена недостаточно качественно. В этом случае необходимо еще раз проанализировать задачу и, возможно, провести дополнительное разбиение на подзадачи. При наличии сложных мест в проекте их нужно подробнее документировать с помощью продуманной системы комментариев. Этот процесс нужно продолжать до тех пор, пока вы дей- ствительно не добьетесь ясного понимания назначения всех модулей задачи и их оптимального сочетания. О Назначение всех переменных модуля должно быть описано с помощью ком- ментариев по мере их определения.
336 Урок 14. Модульное программирование О Исходный текст модуля должен иметь заголовок, в котором отражены как назначение этого модуля, так и все его внешние связи. Этот заголовок мож- но назвать интерфейсной частью модуля. В этой части с использованием комментариев нужно поместить следующую информацию: © назначение модуля; о особенности функционирования; • описание входных аргументов; • описание выходных аргументов; о использование внешних модулей и переменных; • сведения о разработчике для защиты авторских прав. О В ходе разработки программы следует предусматривать специальные блоки операций, учитывающие реакцию на возможные ошибки в данных или в действиях пользователя. Это очень важный момент, который означает то, что не должно быть тупиковых ветвей в алгоритме программы, в результате работы которых программа «виснет» и перестает отвечать на запросы пользователя. Любые непредусмотренные действия пользователя должны приводить к генерации ошибочной ситуации или к предупреждению о воз- можности возникновения такой ситуации. Из этих положений видно, какое большое значение придается организации управляющих и информационных связей между структурными единицами про- граммы (модулями), совместно решающими одну или несколько больших задач. Применительно к языку ассемблера можно рассматривать несколько форм организации управляющих связей: О Использование механизма макроподстановок, позволяющего изменять ис- ходный текст программы в соответствии с некоторыми предварительно опи- санными параметризованными объектами. Эти объекты имеют формальные аргументы, что позволяет производить замещение их фактическими аргу- ментами в процессе макрогенерации. Такая форма образования структур- ных элементов носит некоторый предварительный характер из-за того, что процессы замены происходят на этапе компиляции и есть смысл рассматри- вать их только как настройку на определенные условия функционирования программы. О Использование механизма подпрограмм, написанных на ассемблере и струк- турно входящих в одну программу. В языке ассемблера такие подпрограм- мы называют процедурами. В отличие от макрокоманд, взаимодействие про- цедур осуществляется на этапе выполнения программы. О Использование механизма подпрограмм, написанных на разных языках про- граммирования и соединяемых в единый модуль на этапе компоновки. Эта возможность реализуется благодаря унифицированному формату объектно- го модуля, однозначным соглашениям по передаче аргументов и единым схемам организации памяти на этапе выполнения.
Процедуры в языке ассемблера 337 О Использование механизма динамического (то есть времени выполнения) вызова исполняемых модулей и подключения библиотек .dll для операци- онной системы Windows. В качестве основных информационных связей можно выделить следующие: О Использование общих областей памяти и общих программно-аппаратных ресурсов микропроцессора для связи модулей. О Унифицированную передачу аргументов при вызове модуля. Эту унифика- цию можно представлять двояко: на уровне пользователя и на уровне конк- ретного компилятора. О Унифицированную передачу аргументов при возврате управления из модуля. Чуть позже мы подробно рассмотрим процессы, происходящие при передаче аргументов. Сейчас в качестве некоторого итога приведенных выше общих рас- суждений перечислим средства языка ассемблера по осуществлению функцио- нальной декомпозиции программы: О макросредства; О процедуры; О средства компилятора ассемблера в форме директив организации оператив- ной памяти и ее сегментации. Макросредства подробно были рассмотрены на уроке 13. Предварительное об- суждение механизма процедур было проведено при обсуждении команд перехо- да на уроке 10. Но этот механизм содержит в себе более глубокие вещи, чем тривиальная передача управления из одной точки программы в другую. В част- ности, он тесно связан со средствами компилятора, поддерживающими организа- цию памяти и сегментацию. Поэтому дальнейшее наше обсуждение будет посвя- щено более глубокому изучению функциональной декомпозиции программ с использованием механизма процедур и связанных с ним средств компилятора. Процедуры в языке ассемблера В языке ассемблера для оформления процедур как отдельных объектов су- ществуют специальные директивы PROC/ENDP и машинная команда ret. Эти средства были подробно рассмотрены на уроке 10. Процедуры, так же как и макрокоманды, ценны тем, что могут быть активизированы в любом месте про- граммы. Процедурам, так же как и макрокомандам, могут быть переданы неко- торые аргументы, что позволяет, имея одну копию кода в памяти, изменять ее для каждого конкретного случая использования, хотя по гибкости использова- ния процедуры уступают макрокомандам. Особых проблем с разработкой и применением процедур нет. На уроке 10 нами были рассмотрены возможные варианты размещения процедур в программе: О в начале программы (до первой исполняемой команды); О в конце (после команды, возвращающей управление операционной системе);
338 Урок 14. Модульное программирование О промежуточный вариант — тело процедуры располагается внутри другой процедуры или основной программы. В этом случае необходимо предусмот- реть обход процедуры с помощью команды безусловного перехода jmp; О в другом модуле. Главное условие здесь то, чтобы на процедуру не попадало управление. Три первых варианта из этих четырех относятся к случаю, когда процедуры нахо- дятся в одном сегменте кода. Мы их достаточно подробно обсудили. Что же касается последнего варианта, то он предполагает, что процедуры находятся в разных модулях. А это дает нам возможность говорить уже не об одной про- грамме, а о нескольких. Эти программы должны быть связаны между собой по управлению и по данным. Если мы разберемся с тем, как организовать такую связь, то фактически сможем выполнить функциональную декомпозицию лю- бой большой программы на несколько более мелких. В первой части данного урока мы рассмотрим, как организуется связь по управлению и по данным между программами на ассемблере. Во второй мы разберем связь между про- граммами на ассемблере, Pascal и С. Сначала необходимо отметить один общий для всех этих трех языков момент. Так как отдельный модуль в соответствии с концепцией модульного програм- мирования — это функционально автономный объект, то он ничего не должен знать о внутреннем устройстве других модулей, и наоборот, другим модулям также ничего не известно о внутреннем устройстве данного модуля. Но долж- ны быть какие-то средства, с помощью которых можно связать модули. В каче- стве аналогии можно привести известную вам организацию связи телевизора и видеомагнитофона через разъем типа «скарт». Связь унифицирована, то есть известно, что один контакт предназначен для видеосигнала, другой — для пере- дачи звука и т. д. Телевизор и видеомагнитофон могут быть разными, но связь между ними одинакова. Та же идея лежит и в организации связи модулей. Внутреннее устройство модулей может совершенствоваться, они вообще могут в следующих версиях писаться на другом языке, но в процессе их объединения в единый исполняемый модуль этих особенностей не должно быть заметно. Таким образом, каждый модуль должен иметь такие средства, с помощью кото- рых он извещал бы транслятор о том, что некоторый объект (процедура, пе- ременная) должен быть видимым вне этого модуля. И наоборот, нужно объ- яснить транслятору, что некоторый объект находится вне данного модуля. Это позволит транслятору правильно сформировать машинные команды, оставив некоторые их поля незаполненными. Позднее, на этапе компоновки, программа TLINK или программа компоновки языка высокого уровня произведет на- стройку модулей и разрешит все внешние ссылки в объединяемых модулях. Для того чтобы объявить о подобного рода видимых извне объектах, програм- ма должна использовать две директивы TASM: extrn и public. Директива extrn предназначена для объявления некоторого имени внешним по отношению к данному модулю. Это имя в другом модуле должно быть объявлено в директи-
Процедуры в языке ассемблера 339 ве public. Директива public предназначена для объявления некоторого имени, определенного в этом модуле, и видимым в других модулях. Синтаксис этих директив следующий: extrn имя:тип,..., имя:тип public имя,... ,имя Здесь имя — идентификатор, определенный в другом модуле. В качестве иден- тификатора могут выступать: О имена переменных, определенных директивами типа db, dw и т. д.; О имена процедур; О имена констант, определенных операторами = и equ. Тип определяет тип идентификатора. Указание типа необходимо для того, что- бы транслятор правильно сформировал соответствующую машинную команду. Действительные адреса будут вычислены на этапе редактирования, когда будут разрешаться внешние ссылки. Возможные значения типа определяются допус- тимыми типами объектов для этих директив: О если имя — это имя переменной, то тип может принимать значения byte, word, dword, pword, fword, qword и tbyte; О если имя — это имя процедуры, то тип может принимать значение near или far; О если имя — это имя константы, то тип должен быть abs. Покажем принцип использования директив extrn и public на схеме связи двух модулей Модуль 1 и Модуль 2 (см. листинги 14.1 и 14.2). Листинг 14.1. Модуль 1 ;Модуль 1 masm .model small .stack 256 .data .code my_proc_1 proc my_proc_1 endp my_proc_2 proc my_proc_2 endp продолжение
340 Урок 14. Модульное программирование ;объявляем процедуру my_proc_1 видимой извне public my_proc_1 start: mov ax,©data end start Листинг 14.2. Модуль 2 ;Модуль 2 masm .model small .stack 256 .data . code extrn my_proc_1 ;объявляем процедуру my_proc_1 внешней start: mov ax,©data call my_proc_1 ;вызов my_proc_1 из модуля 1 end start Рассмотренная нами схема связи — это, фактически, связь по управлению. Но не менее важно организовать информационный обмен между модулями. Рас- смотрим основные способы организации такой связи. Организация интерфейса с процедурой Прежде всего определимся с такими понятиями, как аргумент, переменная, константа. Они часто используются в контексте других понятий — модуля и процедуры, которые можно считать синонимами, хотя модуль — это все же бо- лее общее понятие. Аргумент — это ссылка на некоторые данные, которые требуются для выпол- нения возложенных на модуль функций и размещенных вне этого модуля. По аналогии с макрокомандами рассматривают понятия формального и фактичес- кого аргументов. Исходя из этого, формальный аргумент можно рассматривать не как непосредственные данные или их адрес, а как «местодержатель» для действительных данных, которые будут подставлены в него с помощью факти- ческого аргумента. Формальный аргумент можно рассматривать как элемент интерфейса модуля (конкретный вывод «скарта»), а фактический аргумент — это то, что фактически передается на место формального аргумента.
Процедуры в языке ассемблера 341 Переменная — это нечто, размещенное в регистре или ячейке памяти, что мо- жет в дальнейшем подвергаться изменению. Константа — данные, значение которых никогда не изменяется. Таким образом, если некоторые данные в модуле могут подвергаться измене- нию, то это переменные. Если переменная находится за пределами модуля (процедуры) и должна быть как-то передана в него, то для модуля она является формальным аргументом. Значение переменной передается в модуль для заме- щения соответствующего параметра при помощи фактического аргумента. Те- перь, когда вы уяснили, чем отличаются формальные и фактические аргумен- ты, мы далее будем называть их обобщенно — аргументы, а вы по контексту будете понимать, о каком виде аргументов идет речь. Если входные данные для модуля — переменные, то один и тот же модуль можно использовать многократно для разных наборов значений этих перемен- ных. Но как организовать передачу значений переменных в модуль (процеду- ру)? При программировании на языке высокого уровня программист ограни- чен в выборе способов передачи аргументов теми рамками, которые для него оставляет компилятор. В языке ассемблера практически нет никаких ограниче- ний на этот счет, и фактически решение проблемы передачи аргументов предо- ставлено программисту. Поэтому существуют следующие варианты передачи аргументов в модуль (процедуру): О через регистры; О через общую область памяти; О через стек; О с помощью директив extrn и public. Передача аргументов через регистры Это наиболее простой в реализации способ передачи данных. Данные, передан- ные подобным способом, становятся доступными немедленно после передачи управления процедуре. Этот способ очень популярен при небольшом объеме передаваемых данных. Ограничения на способ передачи аргументов через регистры: О небольшое число доступных для пользователя регистров; О нужно постоянно помнить о том, какая информация в каком регистре нахо- дится; О ограничение размера передаваемых данных размерами регистра. Если раз- мер данных превышает 8, 16 или 32 бит, то передачу данных посредством регистров произвести нельзя. В этом случае передавать нужно не сами дан- ные, а указатели на них.
342 Урок 14. Модульное программирование Такой способ передачи аргументов широко применяется при вызове функций DOS. На уроке 13 (листинг 13.2) мы обсуждали программу, использующую макроко- манду, которая подсчитывала длину строки, оканчивающейся символом $. Для сравнения эффективности применения макрокоманд и процедур при программи- ровании разработаем аналогичную программу (листинг 14.3), но с использова- нием процедуры подсчета количества символов в строке с конечным симво- лом $. Для подсчета символов программа использует процедуру CountSymbol, располо- женную в конце программы. Длина строки — не более 99 символов. Адрес строки передается процедуре как аргумент через регистр si. Результат подсчета возвращается в регистр bl и выводится на экран в вызывающей программе. Для вывода на экран используется прямой доступ к видеобуферу. Листинг 14.3. Передача аргументов через регистры ;prg_14_3.asm MASM MODEL small ;модель памяти STACK 256 ;размер стека include iomac.inc подключение файла с макросами .data ;начало сегмента данных maskd db 71h ; маска вывода на экран string db "Строка для подсчета $” ; тестовая строка mesdb "В строке string " cntdb 2 dup ("*") ; количество символов в строке db ” символов",10,13, '$' .code main proc ; точка входа в главную процедуру mov ах,©data mov ds, ах ; загрузка адреса строки ; (для передачи смещения в процедуру) lea si,string ;вызов процедуры call CountSymbol mov cl, bl ; счетчик для lods и stosw lea si, string ; в si - указатель на строку mov ax,0b800h mov es.ax ; загрузка в es адреса видеопамяти mov ah,maskd ;маска вывода на экран
Процедуры в языке ассемблера 343 mov di, 160 ; позиция вывода на экран cld ; просмотр вперед - для lodsb и stosw disp: lodsb ;пересылка байта из ds:si в al stosw loop disp ;a теперь выведем mov al,bl aam ;копирование значения ах ;в es:di (видеобуфер) ;повтор цикла сх раз количество символов в строке ; в al две упакованные BCD-цифры or ax,3030h результата подсчета ;преобразование результата в код ASCII mov ent,ah mov cnt+1,al _0utStr mes exit: _Exit main endp ;вывод строки mes ;макрос выхода ;конец главной процедуры CountSymbol proc near процедура CountSymbol - подсчёт символов в строке. ;На входе: si - смещение строки ;На выходе: bl - длина в виде упакованного BCD-числа push push cld ax;сохранение используемых регистров сх ;просмотр вперед mov сх,100 максимальная длина строки ;блок подсчёта символов go: lodsb ;загрузка символа строки в al cmp al,'$• je endstr jcxz no_end inc bl ; приращение счетчика в bl - количества loop go ; подсчитанных символов в строке ; повтор цикла endstr: ;конец строки pop ex ; восстановление регистров из стека pop ax ret ;возврат из процедуры продолжение
344 Урок 14. Модульное программирование no_end: ; какие-то действия по обработке ситуации ; отсутствия в строке символа $ ret ; возврат из процедуры Countsymbol endp ;конец процедуры end main ; конец программы Передача аргументов через общую область памяти Этот вариант передачи аргументов предполагает, что вызывающая и вызываемая программы условились использовать некоторую область памяти как общую. Транслятор предоставляет специальное средство для организации такой области памяти. На уроке 6 мы разбирали директивы сегментации и их атрибуты. Один из них — атрибут комбинирования сегментов. Наличие этого атрибута указыва- ет компоновщику TLINK, как нужно комбинировать сегменты, имеющие одно имя. Значение common означает, что все сегменты, имеющие одинаковое имя в объединяемых модулях, будут располагаться компоновщиком, начиная с одного адреса оперативной памяти. Это значит, что они будут просто перекрываться в памяти и, следовательно, совместно использовать выделенную память. Недостатком этого способа в реальном режиме работы микропроцессора являет- ся отсутствие средств защиты данных от разрушения, так как нельзя проконтро- лировать соблюдение правил доступа к этим данным. В защищенном режиме ситуация выглядит лучше. Этот режим мы рассмотрим на уроке 16. Рассмотрим листинг 14.4 с примером использования общей области памяти для обмена данными между модулями. На этот раз программа состоит уже из двух независимых модулей, находящихся в разных файлах, и поэтому они представ- ляют собой отдельные единицы трансляции. Функционально эти модули реали- зуют несложную задачу, которая заключается в том, что вызываемые процедуры формируют строку символов и передают ее через общую область, а вызывающая их процедура main выводит строку на экран. Листинг 14.4. Передача аргументов через общую область памяти (модуль 1) ;prg14_4.asm include mac.inc ; подключение файла с макросами stksegment stack db 256 dup (0) stkends common_data segment para common’’data’’ ; начало общей ; области памяти bufdb 15 DUP (” ”) ; буфер для хранения строки temp dw 0 common_data ends
Процедуры в языке ассемблера 345 extrn PutChar:far,PutCharEnd:far code segment ;начало сегмента кода assume cs: code, es: common_data main proc mov ax,common_data mov es,ax ;вызов внешних процедур call PutChar call PutCharEnd push es pop ds _0utStr buf exit: .Exit стандартный выход main endp ; конец главной процедуры code ends end main Вызываемые процедуры находятся в другом модуле (листинг 14.5). Листинг 14.5. Передача аргументов через общую область памяти (модуль 2) :prg14_5.asm include mac.inc ; подключение файла с макросами stksegment stack db 256 dup (0) stkends pdata segment para public ‘'data’* mesdb ’’Общий сегмент",Oah,Odh, ’$’ tempi db ? temp2 dd ? temp3 dq ? pdata ends public PutChar, PutCharEnd common_data segment para common "data" ; начало общей ;области памяти buffer db 15 DUP (" ”) ; буфер для формирования строки tmpSI dw 0 common_data ends code segment ;начало сегмента кода продолжение
346 Урок 14. Модульное программирование assume cs: code, es: common_data, ds: pdata PutChar proc far ; обьявление процедуры cld mov si,0 mov buffer[si], ’P’ inc si mov buffer[si], ’a' inc si mov buffer[si], *6’ inc si mov buffer[si], ’o’ inc si mov buffer[si],’t’ inc si mov buffer[si],’a’ inc si mov buffer[si],'e* inc si mov buffer[si],’t’ inc si mov buffer[si], ’! ’ inc si mov tmpSI.si ret ; возврат из процедуры PutChar endp ; конец процедуры PutCharEnd proc far mov si,tmpSI mov buffer[si],’$’ ret PutCharEnd endp code ends end Обратите внимание, что совсем не обязательно, чтобы данные в сегментах common имели одинаковые имена. Главное, и за этим нужно следить с особой тщательно- стью, — структура общих сегментов. Она должна быть абсолютно идентична во всех модулях данной программы, использующих обмен данными через общую память. Так как в данном примере программа состоит уже из двух модулей, то у читате- ля наверняка возник вопрос, как собрать ее в один исполняемый модуль. Можно предложить следующую последовательность шагов:
Процедуры в языке ассемблера 347 1. Выполнить трансляцию модуля prg14_4.asm и получить объектный модуль prg14_4.obj. 2. Выполнить трансляцию модуля prg14_5.asm и получить объектный модуль prg14_5.obj. 3. Скомпоновать программу утилитой TLINK командной строкой вида tlink /v prg14_4.obj + prg14_5.obj В итоге будет создан исполняемый модуль prg14_4.exe. Вы можете исследовать этот модуль, используя отладчик, но имейте в виду следующее. В окне MODULE вы увидите только исходный текст программы prg14_4.asm. Для того чтобы войти по команде call в вызываемую процедуру, необходимо нажимать клави- шу F7. Обработка этой команды приведет к открытию второго окна, в котором будет выведен текст вызванной процедуры. Передача аргументов через стек Этот способ наиболее часто используется для передачи аргументов при вызове процедур. Суть его заключается в том, что вызывающая процедура самостоя- тельно заносит в стек передаваемые данные, после чего производит вызов вы- зываемой процедуры. На уроке 10 мы рассматривали процессы, происходящие при передаче управления процедуре и возврате из нее. При этом мы обсуждали содержимое стека до и после передачи управления процедуре (см. рис. 10.4 и 10.5). Как следует из этих рисунков, при передаче управления процедуре мик- ропроцессор автоматически записывает в вершину стека два (для процедур типа near) или четыре (для процедур типа far) байта. Вы помните, что эти байты являются адресом возврата в вызывающую программу. Если перед пере- дачей управления процедуре командой call в стек были записаны переданные процедуре данные или указатели на них, то они окажутся под адресом возврата. При рассмотрении архитектуры микропроцессора мы выяснили, что стек об- служивается тремя регистрами: ss, sp и bp. Микропроцессор автоматически ра- ботает с регистрами ss и sp в предположении, что они всегда указывают на вершину стека. По этой причине их содержимое изменять не рекомендуется. Для осуществления произвольного доступа к данным в стеке архитектура мик- ропроцессора имеет специальный регистр ebp\bp (Base Point — указатель базы). Так же как и для регистра esp\sp, использование ebp\bp автоматически предполагает работу с сегментом стека. Перед использованием этого регистра для доступа к данным стека его содержимое необходимо правильно инициали- зировать, что предполагает формирование в нем адреса, который бы указывал непосредственно на переданные данные. Для этого в начало процедуры реко- мендуется включить дополнительный фрагмент кода. Он имеет свое назва- ние — пролог процедуры. Типичный фрагмент программы, содержащий вызов
348 Урок 14. Модульное программирование процедуры с передачей аргументов через стек, может выглядеть так: 1> masm 2> model small 3> 4> proc_1proc near ; «близкая» процедура (near) с n аргументами 5> ;начало пролога 6> push bp 7> mov bp.sp 8> ; конец пролога 9> mov ax, [bp+4] ; доступ к аргументу arg_n для near-процедуры 10> mov ax, [bp+6] ; доступ к аргументу arg_{n-1} 11> ; команды процедуры 12> ; подготовка к выходу из процедуры 13> ;начало эпилога 14> mov sp.bp ;восстановление sp 15> pop bp восстановление значения старого bp 1б> ; до входа в процедуру :17> ret ; возврат в вызывающую программу :18> ;конец эпилога :19> ргос_1 endp :20> :21> .code :22> main proc :23> mov ax,@data :24> mov ds, ax :25> :26> push arg_1 ; запись в стек 1-го аргумента :27> push arg_2 ; запись в стек 2-го аргумента :28> :29> push arg_n ; запись в стек n-го аргумента :30> call ргос_1 ;вызов процедуры ргос_1 :31> ; действия по очистке стека после возврата из процедуры :32> । • > :33> ml: :34> .exit :35> main endp :36> end main Код пролога состоит всего из двух команд. Первая команда push bp сохраняет содержимое Ьр в стеке с тем, чтобы исключить порчу находящегося в нем зна- чения в вызываемой процедуре. Вторая команда пролога mov bp,sp настраивает । bp на вершину стека. После этого мы можем не волноваться о том, что содер- • жимое sp перестанет быть актуальным, и осуществлять прямой доступ к содер-
Процедуры в языке ассемблера 349 жимому стека. Что мы и делаем. Для доступа к arg_n достаточно сместиться от содержимого Ьр на 4, для arg_{n+1} — на 6 и т. д. Но эти смещения подходят только для процедур типа near. Для far-процедур эти значения необходимо скорректировать еще на 2, так как при вызове процедуры дальнего типа в стек записывается полный адрес — содержимое cs и ip. Поэтому для доступа к arg_n в строке 9 команда будет выглядеть mov ax,[bp+6], а для arg_{n+1}, соот- ветственно, mov ax,[bp+6] и т. д. Конец процедуры также должен быть оформлен особым образом и содержать действия, обеспечивающие корректный возврат из процедуры. Фрагмент кода, выполняющего такие действия, имеет свое название — эпилог процедуры. Код эпилога должен восстановить контекст программы в точке вызова вызываемой процедуры из вызывающей программы. При этом, в частности, нужно откор- ректировать содержимое стека, убрав из него ставшие ненужными аргументы, передававшиеся в процедуру. Это можно сделать несколькими способами: О используя последовательность из п команд pop хх. Лучше всего это делать в вызывающей программе сразу после возврата управления из процедуры; О откорректировать регистр указателя стека sp на величину 2*и, например, командой add sp,NN, где NN=2*n, и п — количество аргументов. Это также лучше делать после возврата управления вызывающей процедуре; О используя машинную команду ret п в качестве последней исполняемой ко- манды в процедуре, где п — количество байт, на которое нужно увеличить содержимое регистра esp\sp после того, как со стека будут сняты составляю- щие адреса возврата. Видно, что этот способ аналогичен предыдущему, но выполняется автоматически микропроцессором. В каком виде можно передавать аргументы в процедуру? Мы уже упоминали, что передаваться могут либо данные, либо их адреса (указатели на данные). В языке высокого уровня это называется передачей по значению и адресу соот- ветственно. Наиболее простой способ передачи аргументов в процедуру — передача по значению. Этот способ предполагает, что передаются сами данные, то есть их значения. Вызываемая программа получает значение аргумента через регистр или через стек. Естественно, что при передаче переменных через регистр или стек на их размерность накладываются ограничения, связанные с размерностью используемых регистров или стека. Другой важный момент заключается в том, что при передаче аргументов по значению в вызываемой процедуре обрабаты- ваются их копии. Поэтому значения переменных в вызывающей процедуре не изменяются. Способ передачи аргументов по адресу предполагает, что вызываемая процеду- ра получает не сами данные, а их адреса. В процедуре нужно извлечь эти адре- са тем же методом, как это делалось для данных, и загрузить их в соответству- ющие регистры. После этого, используя адреса в регистрах, следует выполнить необходимые операции над самими данными. В отличие от способа передачи t
350 Урок 14. Модульное программирование данных по значению, при передаче данных по адресу в вызываемой процедуре обрабатывается не копия, а оригинал передаваемых данных. Поэтому при из- менении данных в вызываемой процедуре они автоматически изменяются и в вызывающей программе, так как изменения касаются одной области памяти. Использование директив extrn и public Эти директивы мы уже упоминали выше, когда рассматривали варианты вза- имного расположения вызывающей программы и вызываемой процедуры. Ко всему сказанному добавим, что директивы extrn и public также можно исполь- зовать для обмена информацией между модулями. Назначение и форматы этих директив уже были рассмотрены, поэтому сейчас опишем только порядок их использования для обмена данными. Можно выделить несколько вариантов их применения: 1. оба модуля используют только сегмент данных вызывающей программы; 2. у каждого из модулей есть свой собственный сегмент данных; 3. использование атрибута комбинирования (объединения) сегментов private в директиве сегментации segment. Рассмотрим эти варианты на примере программы, которая определяет в сег- менте данных две символьные переменные и вызывает процедуру, выводящую эти символы на экран. Вариант 1. Два модуля используют только сегмент данных вызывающей про- граммы (листинги 14.6 и 14.7). В этом случае не требуется переопределения сегмента данных в вызываемой процедуре. В листинге 14.6 в вызывающей программе определены две перемен- ные, вывод на экран которых осуществляет вызываемая программа (лис- тинг 14.7). Листинг 14.6. Вариант 1 использования директив extrn и public (Модуль 1) ;prg14_6.asm вызывающий модуль include mac.inc extrn my_proc2:far public per1,per2 stk segment stack db 256 dup (0) stk ends data segment perl db “1” per2 db . ”2" data ends
Процедуры в языке ассемблера 351 code main segment proc far assume cs:code,ds:data,ss:stk mov ax,data mov call exit main code end main ds, ax my_proc2 endp ends Листинг 14.7. Вариант 1 использования директив extrn и public (Модуль 2) ;prg14_7.asm ; Вызываемый модуль include mac.inc extrn ре г1:byte,ре r2:byte public my_proc2 code segment my_proc2 proc far assume cs:code ; вывод символов на экран mov dl.perl OutChar mov dl,per2 OutChar ret my_proc2 endp code ends end Сборка программы из двух модулей для этого и следующих вариантов осущес- твляется аналогично листингам 13.4 и 13.5. Вариант 2. У каждого из модулей есть свой собственный сегмент данных. В этом случае для доступа к разделяемым переменным из другого модуля тре- буется переопределение сегмента данных в вызываемой процедуре (строки 17- 19 и 24-25 листинга 14.8). Листинг 14.8. Вариант 2 использования директив extrn и public ;prg14_8.asm вызывающий модуль - тот же, что и для предыдущего варианта. продолжение&
352 Урок 14. Модульное программирование < 1> вызываемый модуль < 2> include mac.inc < з> extrn perl:byte, per2: byte < 4> public my_proc2 < 5> data segment < 6> perO db ”0” < 7> data ends < 8> code segment < 9> my_proc2 proc far < 1 о > assume cs: code, ds: data < n> ; вывод символов на экран < 12> mov ax, data < 13> mov ds, ax < 14> mov dl.perO < 15> OutChar < 16> push ds сохранили ds < 17> mov ax, seg perl ; сегментный адрес perl в ds < 18> mov ds, ax < 19> mov dl.perl < 20> OutChar ; вывод perl < 2i> mov dl,per2 < 22> OutChar ;вывод per2 < 23> pop ds ; восстановили ds < 24> mov dl.perO < 25> OutChar ;и еще раз perO < 26> ret < 27> my_proc2 endp < 2 8> code ends < 29> end Вариант 2a. У каждого из модулей есть свой собственный сегмент данных (лис- тинг 14.9). Это несколько улучшенный вариант предыдущего примера, где мы использовали для адресации данных в разных сегментах данных один регистр ds. В этом случае для доступа к разделяемым переменным из другого модуля используется один из дополнительных сегментных регистров данных, к примеру es. Заметьте, что обра- щение к данным другого сегмента осуществляется с использованием префикса замены сегмента (строки 19 и 21). Листинг 14.9. Вариант 2а использования директив extrn и public ;prg14_9.asm вызывающий модуль - тот же, что и для предыдущего варианта.
Процедуры в языке ассемблера 353 <1> ; Вызываемый модуль <2> include iomac.inc <3> extrn per1:byte,per2:byte <4> public my_proc2 <5> data segment <6> perO db "0" <7> data ends <8> code segment <9> my_proc2 proc far <10> assume cs:code,ds:data <11> ;вывод символов на экран <12> mov ах,data <13> mov ds,ax <14> mov dl.perO <15> _OutChar <16> mov ax, seg perl <17> mov es.ax <18> mov dl,es:per1 <19> _OutChar <20> mov dl,es:per2 <21> _OutChar <22> mov dl.perO <23> _OutChar <24> ret <25> my_proc2 endp <26> code ends <27> end Вариант 3. Использование атрибута комбинирования (объединения) сегментов public в директиве сегментации segment для сегментов данных модулей (лис- тинги 14.10 и 14.11). Данное значение атрибута комбинирования заставляет компоновщик объеди- нить последовательно сегменты с одинаковыми именами. Все адреса и сме- щения будут вычисляться относительно начала этого нового сегмента. В этом случае не понадобится производить дополнительной настройки сегментных ре- гистров (как было в двух предыдущих случаях). Листинг 14.10. Вариант 3 использования директив extrn и public (Модуль 1) ;ргд14_10.asm вызывающий модуль <i> include mac.inc <2> extrn my_proc2:far,perO:byte продолжение &
354 Урок 14. Модульное программирование <3> public рег1,рег2 <4> stk segment stack <5> db 256 dup (0) <6> stk ends <7> data segment para public "data" <8> perl db "1" <9> per2 db "2" <10> data ends <11> code segment <12> main proc far <13> assume cs:code,ds:data, ss: stk <14> mov ax,data <15> mov ds,ax <16> mov dl.perO <17> OutChar <18> call my_proc2 <19> exit <20> main endp <21> code ends <22> end main Листинг 14.11. Вариант 3 использования директив extrn и public (Модуль 2) <1> ;prg14_11.asm <2> ; Вызываемый модуль <3> include mac.inc <4> extrn perl:byte,per2:byte <5> public my_proc2, perO <6> data segment para public "data" <7> perO db "0" <8> data ends <9> code segment <10> my_proc2 proc far <11> assume cs: code,ds:data <12> ;ds загружать не надо, так как компоновщик его присоединит <13> ;к сегменту данных первого модуля <14> ; вывод символов на экран <15> mov dl.perO <16> OutChar <17> mov dl,perl <18> OutChar <19> mov dl,per2
Процедуры в языке ассемблера 355 <20> OutChar <21> mov dl.perO <22> OutChar <23> ret <24> my_proc2 endp <25> code ends <2б> end Возврат результата из процедуры В отличие от языков высокого уровня, в языке ассемблера нет отдельных поня- тий для процедуры и функции. Организация возврата результата из процеду- ры полностью ложится на программиста. Как это сделать, для внимательного читателя, наверное, уже понятно, ибо получение результата тоже можно рас- сматривать как передачу аргументов. Поэтому мы ограничимся кратким пояс- нением. В общем случае программист располагает тремя вариантами возврата значений из процедуры: О С использованием регистров. Ограничения здесь те же, что и при передаче данных, — это небольшое количество доступных регистров и их фиксиро- ванный размер. Функции DOS используют именно этот способ. Из рассмат- риваемых здесь трех вариантов данный способ является наиболее быстрым, поэтому его есть смысл использовать для организации критичных по време- ни вызова процедур. О С использованием общей области памяти. Этот способ удобен при возврате большого количества данных, но требует внимательности в определении областей данных и подробного документирования для устранения неодно- значностей. О С использованием стека. Здесь, подобно передаче аргументов через стек, также нужно использовать регистр Ьр. При этом возможны следующие ва- рианты: • использование для возвращаемых аргументов тех же ячеек в стеке, кото- рые применялись для передачи аргументов в процедуру. То есть предпо- лагается замещение ставших ненужными входных аргументов выходны- ми данными; • предварительное помещение в стек наряду с передаваемыми аргумента- ми фиктивных аргументов с целью резервирования места для возвраща- емого значения. При использовании этого варианта процедура, конечно же, не должна пытаться очистить стек командой ret. Эту операцию при- дется делать в вызывающей программе, например командой pop. В ходе вышеприведенного обсуждения мы выяснили, что язык ассемблера не накладывает никаких ограничений на организацию процесса передачи данных и возврата значений между двумя процедурами, а в более общем случае — и
356 Урок 14. Модульное программирование между модулями, представляющими отдельные файлы. Наиболее быстрый спо- соб такого обмена — использование регистров. Но часто требуется связывать между собой не только программы, написанные на ассемблере, но и программы на разных языках. В этом случае универсальным становится способ обмена дан- ными с использованием стека. Ниже мы рассмотрим, как решается проблема связи программных модулей, написанных на языке ассемблера и языках высоко- го уровня. Связь ассемблера с языками высокого уровня На протяжении всей книги мы неоднократно подчеркивали сильные и слабые стороны языка ассемблера как языка программирования. Писать на нем доста- точно объемные программы утомительно. И всегда ли это нужно? Конечно, не всегда. Если программа не предназначена для решения каких-то системных за- дач, требующих максимально эффективного использования ресурсов компью- тера, если к ней не предъявляются сверхжесткие требования по размеру и вре- мени работы, если вы не «фанат» ассемблера — то, на мой взгляд, следует подумать о выборе одного из языков высокого уровня. Существует и третий, компромиссный путь — комбинирование программ на языке высокого уровня с кодом на ассемблере. Такой способ обычно используют в том случае, если в вашей программе есть участки, которые либо невозможно реализовать без ис- пользования ассемблера, либо его применение может значительно повысить эффективность работы программы. Большинство компиляторов учитывают возможность комбинирования их «род- ного» кода с ассемблером. Как именно? Это зависит от конкретного компиля- тора языка высокого уровня, поэтому нет смысла рассматривать данный воп- рос абстрактно. Учитывая, что большинство программистов работают или, по крайней мере, владеют основами программирования для языков С и Pascal, дальнейшее обсуждение будет вестись для компиляторов этих языков: Borland Pascal 7.0 и Visual C++ 4.0. Но не стоит думать, что если вы используете ком- пиляторы других фирм или других версий, то информация, приведенная ниже, вам не понадобится. Принципиальные моменты останутся теми же. Единствен- ное, что вам нужно будет сделать, — это уточнить в документации некоторые опции и, возможно, особенности организации связи с кодом на ассемблере. Так что вы можете быть уверены в актуальности информации, представленной ниже. Она далеко не избыточна. Ее назначение — лишь отразить наиболее принципиальные моменты связи программ на языках Pascal и С с ассемблером. Вначале мы отметим общие моменты, актуальные как для языка С, так и для Pascal. Затем на примерах конкретных программ мы обсудим моменты, специ- фичные для каждого из этих языков. Существуют следующие формы комбинирования программ на языках высоко- го уровня с ассемблером:
Связь ассемблера с языками высокого уровня 357 О Использование операторов типа inline1 и ассемблерных вставок. Эта форма сильно зависит от синтаксиса языка высокого уровня и конкретного компи- лятора. Она предполагает, что ассемблерные коды в виде команд ассемблера или прямо в машинных командах вставляются в текст программы на языке высокого уровня. Компилятор языка распознает их как команды ассемблера (машинные коды) и без изменений включает в формируемый им объектный код. Эта форма удобна, если надо вставить небольшой фрагмент. О Использование внешних процедур и функций. Это более универсальная форма комбинирования. У нее есть ряд преимуществ: • написание и отладку программ можно производить независимо; • написанные подпрограммы можно использовать в других проектах; • облегчаются модификация и сопровождение подпрограмм в течении жиз- ненного цикла проекта. К примеру, если ваша процедура на ассемблере производит работу с некоторым внешним устройством, то при смене ус- тройства вам нет необходимости перекраивать весь проект — достаточно заменить только работающую с ним процедуру, оставив неизменным ин- терфейс программы на языке высокого уровня с ассемблером. Так как использование операторов inline и ассемблерных вставок сильно зави- сит от синтаксиса конкретного языка, то мы рассматривать их не будем, а уде- лим основное внимание связи через внешние процедуры и функции. Как вы понимаете, здесь возможны два вида связи — программа на языке высокого уровня вызывает процедуру на ассемблере и наоборот. Здесь мы также ограни- чимся рассмотрением связи только в одну сторону, когда программа на языке высокого уровня вызывает процедуру на ассемблере. Это наиболее часто ис- пользуемый вид связи. Вспомним синтаксис директивы proc, введенной на уроке 10: имя_процедуры PROC [[модификатор_языка]язык] [расстояние] Один из операндов — язык. Он служит для того, чтобы компилятор мог пра- вильно организовать интерфейс (связь данных) между процедурой на ассемб- лере и программой на языке высокого уровня. Необходимость такого указания возникает вследствие того, что способы передачи аргументов при вызове про- цедур различны для разных языков высокого уровня. TASM поддерживает несколько значений операнда язык. В табл. 14.1 для неко- торых из этих значений приведены характерные особенности передачи аргу- ментов и соглашения о том, какая процедура очищает стек — вызывающая или вызываемая. Под направлением передачи аргументов понимается порядок, в ко- 1 Под операторами типа inline здесь понимаются средства языка высокого уровня, подобные оператору inline() языка Pascal. В скобках указывается строка машинных кодов. Для получения такой строки целесообразно написать нужный фрагмент на ассемблере, скомпилировать исполняемый модуль, а затем запустить отладчик. В окне CPU отладчика вы увидите машинные коды ваших инструкций; их нужно переписать и вставить в скобки оператора inline. При этом нужно учитывать синтаксис языка.
358 Урок 14. Модульное программирование тором аргументы включаются в стек, по сравнению с порядком их следования в вызове процедуры. Так, к примеру, для языка Pascal характерен прямой порядок включения аргументов в стек: первым в стек записывается первый передаваемый аргумент из оператора вызова процедуры, вторым — второй аргумент и т. д. На вершине стека после записи всех передаваемых аргументов оказывается после- дний аргумент. Для языка С, наоборот, характерен обратный порядок передачи аргументов. В соответствии с ним в стек сначала включается последний аргумент из оператора вызова процедуры (или функции), затем предпоследний и т. д. В конечном итоге на вершине стека оказывается первый аргумент. Что же касается очистки стека, то понятно, что должны быть определенные договоренности об этом. В языке Pascal эту операцию всегда совершает вызываемая процедура, в языке С — вызывающая. При разработке программы с использованием только одного языка высокого уровня об этом задумываться не имеет смысла, но если мы собираемся связывать несколько разноязыковых модулей, то эти соглашения нужно иметь в виду. Таблица 14.1. Передача аргументов в языках высокого уровня Операнд «язык» Язык Направление передачи аргументов Какая процедура очищает стек NOLANGUAGE Ассемблер Слева направо Вызываемая BASIC Basic Слева направо Вызываемая PROLOG Prolog Справа налево Вызывающая FORTRAN Fortran Слева направо Вызываемая С С Справа налево Вызывающая C++(СРР) C++ Справа налево Вызывающая PASCAL Pascal Слева направо Вызываемая SYSCALL C++ Справа налево Вызывающая Вспомните действия, которые мы делали для того, чтобы настроиться на аргу- менты в стеке. Теперь, указывая язык, с программой на котором будет осущес- твляться связь, все действия по настройке стека будут производиться компиля- тором. При этом в текст процедуры он включит дополнительные команды входа в процедуру (пролог) и выхода из нее (эпилог). При этом код эпилога может повторяться несколько раз — перед каждой командой ret. Для значения NO LANGUAGE и по умолчанию коды пролога и эпилога не создаются. Для пояснения последних замечаний рассмотрим на конкретных примерах организацию связей между модулями на ассемблере и модулями наиболее по- пулярных языков высокого уровня — С и Pascal. Просьба к читателю — не воспринимать нижеследующие примеры как образец оптимальности, а рас- сматривать их лишь как учебные для иллюстрации принципов организации межъязыковых связей.
Связь ассемблера с языками высокого уровня 359 Связь Pascal — ассемблер Организацию такой связи рассмотрим на примере следующей задачи: разработа- ем программу на языке Pascal, которая выводит символ заданное количество раз, начиная с определенной позиции на экране (листинги 14.12 и 14.13). Все число- вые аргументы определяются в программе на Pascal. Вывод символа осуществля- ет процедура ассемблера. Очевидно, что основная проблема в этой задаче — организация взаимодействия модулей на Pascal и ассемблере. Листинг 14.12. Взаимодействие Pascal—ассемблер (модуль на Pascal) <1> <2> <3> <4> <5> <б> <7> <8> <9> <10> <11> <12> <13> {prg14_12.pas} {Программа, вызывающая процедуру на ассемблере} program my_pas; {$D+} {включение полной информации для отладчика} uses crt; procedure asmproc(ch:char;х,у,koi:integer); external; {процедура asmproc обьявлена как внешняя} {$L c:\bp\work\prg14_12.obj} BEGIN clrscr; {очистка экрана} asmproc("a”,1,4,5); asmproc("s”,9,2t7); END. Листинг 14.13. Взаимодействие Pascal—ассемблер (модуль на ассемблере) <1> <2> <3> <4> <5> <6> <7> <8> <9> <10> <11> <12> <13> <14> <15> <16> <17> ;prg14_13.asm Процедура на ассемблере, которую вызывает ; программа на Pascal. ;Для вывода на экран используются службы BIOS: ;02h - позиционирование курсора. ;09h - вывод символа заданное количество раз. MASM MODEL small STACK 256 .code asmproc proc near PUBLIC asmproc ; объявлена как внешняя push bp ;пролог mov bp.sp mov dh,[bp+6] ; номер строки для вывода ;символа у - в dh mov dl,[bp+8] ; номер столбца для вывода продолжение &
360 Урок 14. Модульное программирование <18> •.символа х - в dl <19> mov ah,02h; номер службы BIOS <20> int lOh ; вызов прерывания BIOS <21> ;вызов функции 09h прерывания BIOS 10h: <22> ; вывод символа из al на экран <23> mov ah, 09h ; номер службы BIOS <24> mov al,[bp+10] ; символ ch в al <25> mov bl, 07h ; атрибут символа - в bl <2б> хог bh, bh <27> mov ex,[bp+4] ; количество «выводов^ <28> ;символа - в сх <29> int 10h ; вызов прерывания BIOS <30> pop bp ; восстановление bp <31> ; очистка стека и возврат из процедуры <32> ret 8 <33> asmproc endp ; конец процедуры <34> end Типовая схема организации такой связи следующая: О Написать процедуру на ассемблере дальнего (far) или ближнего типа (near). Назовем ее для примера asmproc. В программе на языке ассемблера (назовем ее prg14_.13.asm), в которую входит процедура asmproc, необходимо объявить имя этой процедуры внешним с помощью директивы PUBLIC: PUBLIC asmproc Для того чтобы процедура на ассемблере при компоновке с программой на Pascal воспринималась компилятором Borland Pascal 7.0 как far или near, недо- статочно будет просто объявить ее таковой в директиве proc (строка И листин- га 14.13). Кроме того, вам нужно включить или выключить опцию компилятора, доступную через меню интегрированной среды: Options|Compiler| Force far calls. Установка данной опции заставляет компилятор генерировать дальние вызовы подпрограмм. Альтернатива данной опции — ключ в программе {$F+} или {$F-} (соответственно, включено или выключено). Это — локальные ключи, то есть в исходном тексте программы на Pascal их может быть несколько, и они могут, чередуясь друг с другом, поочередно менять форму генерируемых адресов пере- хода — для одних подпрограмм дальние вызовы, для других — ближние. О Произвести компиляцию программы prg14_13.asm с целью устранения синтаксических ошибок и получения объектного модуля программы prg14_13.obj: tasm /zi prg14_13,,, О В программе на Pascal prg14_12. pas, которая будет вызывать внешнюю процедуру на ассемблере, следует вставить директиву компилятора
Связь ассемблера с языками высокого уровня 361 {$L \путь\рrg_14_12. obj}. Эта директива заставит компилятор в процессе ком- пиляции программы prg14_ 12. pas загрузить с диска объектный модуль про- граммы prgl4_12. obj. В программе prg14_ 12. pas необходимо объявить процеду- ру asmproc как внешнюю. В итоге последние два объявления в программе на Pascal будут выглядеть так: {$L my_asm} procedure asmproc(ch:char;kol,x,y:integer); external; О если вы собираетесь исследовать в отладчике работу программы, то необхо- димо потребовать, чтобы компилятор включил отладочную информацию в генерируемый им исполняемый модуль. Для этого есть две возможности. Первая заключается в использовании глобального ключа {$D+}. Этот ключ должен быть установлен сразу после заголовка программы на Pascal. Вторая, альтернативная возможность заключается в установке опции компилятора: Options|Compiler|Debug Information; О выполнить компиляцию программы на Pascal. Для компиляции удобно ис- пользовать интегрированную среду. Для изучения особенностей связки Pascal — ассемблер удобно прямо в интегрированной среде перейти к работе в отладчике через меню Tools|Turbo Debugger (или клавишами Shift-F4). Бу- дет загружен отладчик. Его среда вам хорошо знакома; в данном случае в окне Module вы увидите текст программы на Pascal. Нажимая клавишу F7, вы в пошаговом режиме будете исполнять программу на Pascal. Когда оче- редь дойдет до вызова процедуры на ассемблере, отладчик откроет окно с текстом программы на ассемблере. Но наш совет вам — не ждать этого мо- мента, так как вы уже пропустили некоторые интересные вещи. Дело в том, что отладчик скрывает от вас момент перехода из программы на Pascal в процедуру на ассемблере. Поэтому лучше всего исполнять программу при открытом окне CPU отладчика. И тогда вы станете свидетелями тех процес- сов, которые мы будем обсуждать ниже. Если бы взаимодействие программ ограничивалось только передачей и возвра- том управления, то на этом обсуждение можно было бы и закончить. Но дело значительно усложняется, когда требуется передать аргументы (в случае проце- дуры) или передать аргументы и возвратить результат (в случае функции). Рассмотрим процессы, которые при этом происходят. Передача аргументов при связи модулей на разных языках всегда производит- ся через стек. Компилятор Pascal генерирует соответствующие команды при обработке вызова процедуры ассемблера. Это как раз те команды, которые от- ладчик пытался скрыть от нас. Они записывают в стек аргументы и генериру- ют команду call для вызова процедуры ассемблера. Чтобы убедиться в этом, просмотрите исполнительный код программы в окне CPU отладчика. После обработки вызова процедуры и в момент передачи управления процедуре asmproc содержимое стека будет таким, как показано на рис. 14.1, а. Для досту-
362 Урок 14. Модульное программирование па к этим аргументам можно использовать различные методы; наиболее удобный из них — использование регистра Ьр. Оперативная память Оперативная память Старшие адреса оперативной памяти б Старшие адреса оперативной памяти в Старшие адреса оперативной памяти а Рис. 14.1. Изменение содержимого стека при передаче управления Pascal—ассемблер Регистр Ьр, как уже отмечалось, специально предназначен для организации про- извольного доступа к стеку. Когда мы рассматривали связь ассемблерных моду- лей, то говорили о необходимости добавления в текст вызываемого модуля фрагментов, настраивающих его на передаваемые ему аргументы. При организа- ции связи разноязыковых модулей также нужно вставлять подобные дополни- тельные фрагменты кода. Они, кроме всего прочего, позволят учесть особенности конкретного языка. Фрагмент, вставляемый в самое начало вызываемого модуля, называется прологом модуля (процедуры). Фрагмент, вставляемый перед коман- дами передачи управления вызывающему модулю, называется эпилогом модуля (процедуры). Его назначение — восстановление состояния вычислительной сре- ды на момент вызова данного модуля. Рассмотрим действия, выполняемые кодами пролога и эпилога при организации связи Pascal—ассемблер. Действия, выполняемые кодом пролога: 1. Сохранить значение Ьр в стеке. Это делается с целью запоминания контекста вызывающего модуля. Стек при этом будет выглядеть как на рис. 14.1, б. 2. Записать содержимое sp в Ьр. Тем самым Ьр теперь тоже будет указывать на вершину стека (рис. 14.1, в). После написания кода пролога все обращения к аргументам в стеке можно организовывать относительно содержимого регистра Ьр. Из рис. 14.1, в видно, что для обращения к верхнему и последующим аргументам в стеке содержимое Ьр необходимо откорректировать. Нетрудно посчитать, что величина корректи- ровки будет отличаться для процедур дальнего (far) и ближнего (near) типов. Причина понятна: при вызове near-процедуры, в зависимости от установленно- го режима адресации — use16 или use32 — в стек записывается 2/4 байта в ка-
Связь ассемблера с языками высокого уровня 363 честве адреса возврата (содержимое ip/eip), а при вызове far-процедуры в стек записывается 4/8 байт (содержимое ip/eip и cs)1. Таким образом, коды пролога для near- и far-процедур соответственно будут выглядеть следующим образом: asmproc proc near ;пролог для near-процедуры push bp mov bp,sp ; к прологу можно добавить команду •«корректировки Ьр на 4, с тем, чтобы регистр Ьр ;указывал на верхний из передаваемых аргументов в стеке add bp,4 ;теперь bp указывает на koi asmproc proc far ;пролог для far-процедуры push bp mov bp.sp ;к прологу можно добавить команду корректировки Ьр на 6, с тем, чтобы регистр Ьр •.указывал на верхний из передаваемых аргументов в стеке add bp,6 ;теперь bp указывает на koi Далее доступ к переданным в стеке данным осуществляется, как показано в ли- стинге 14.7. Как видите, все достаточно просто. Но если мы вдруг решили изменить тип на- шей процедуры ассемблера с far на near или наоборот, то нужно явно изменить и код пролога. Это не совсем удобно. TASM предоставляет выход в виде директи- вы ARG, которая служит для работы с аргументами процедуры. Формат директи- вы ARG следующий (рис. 14.2). Ч ARG |~г| аргумент} О аргумент: Рис. 14.2. Синтаксис директивы ARG 1 Если действует режим адресации use32, то в стек записываются двойные слова. По этой причине запись 16-разрядного регистра cs также производится четырьмя байтами, при этом два старших
364 Урок 14. Модульное программирование Несколько слов об обозначениях на рис. 14.2: имя — идентификатор переменной, который будет использоваться в проце- дуре на ассемблере для доступа к соответствующей переменной в стеке; тип — определяет тип данных аргумента (по умолчанию word для use16 и dword для use32); значение_1 — определяет количество аргументов с данным именем. Место в стеке для них будет определено, исходя из расчета: значение-1 * значе- ние-2 * (размер_типа). По умолчанию значение_1 = 1; значение_2 — определяет, сколько элементов данного типа задает данный аргумент. По умолчанию его значение равно 1, но для типа byte значе- ние-2 = 2, так как стековые команды не работают с отдельными байтами. Хотя, если явно задать значение_2 = 7, то транслятор действительно бу- дет считать, что в ячейку стека помещен один байт; идентификатор — имя константы, значение которой присваивает трансля- тор. Об идентификаторе мы подробно поговорим чуть ниже. Таким образом, директива ARG определяет аргументы, передаваемые в процеду- ру. Ее применение позволяет обращаться к аргументам по их именам, а не сме- щениям относительно содержимого Ьр. К примеру, если в начале рассматрива- емой нами процедуры на ассемблере asmproc задать директиву ARG в виде arg koi:word,у:word,х:word,chr:byte, то к аргументам процедуры можно будет обра- щаться по их именам, без подсчета смещений. Ассемблер сам выполнит всю необходимую работу. В этом можно убедиться, запустив программу в отлад- чике. Обратите внимание: порядок следования аргументов в директиве arg является обратным порядку их следования в описании процедуры (строка procedure asmproc(ch:char;х,у,koi:integer); external; в программе на Pascal). Процедура asmproc с использованием директивы arg представлена в листин- ге 14.14. После того как решена проблема передачи аргументов в процедуру и выполне- ны все необходимые действия, возникает очередной вопрос — как правильно возвратить управление? При возврате управления в программу на Pascal нуж- но помнить, что соглашения этого языка требуют, чтобы вызываемые процеду- ры самостоятельно очищали за собой стек. Программа на ассемблере также должна удовлетворять этому требованию и заботиться об очистке стека перед своим завершением. Для этого необходимо составить эпилог. Действия, выполняемые кодом эпилога для связи Pascal—ассемблер: 1. Восстановить сохраненный в стеке регистр Ьр. 2. Удалить из стека переданные процедуре аргументы. Для удаления из стека аргументов можно использовать различные способы: О явно скорректировать значение sp, переместив указатель стека на необходи- мое количество байт в положительную сторону. Это — не универсальный
Связь ассемблера с языками высокого уровня 365 способ, к тому же он чреват ошибками, особенно при частых модификациях программы; О использовать в директиве arg после записи последнего аргумента операнд, состоящий из символа равенства «=» и идентификатора, указанного за ним в следующей синтаксической конструкции: -идентификатор. В этом случае TASM при обработке директивы arg подсчитает количество байт, занятых всеми аргументами, и присвоит их значение идентификатору. В нашем случае директиву arg можно определить так: arg ch:byte;x:word;y:word;kol:word=a_size. TASM после обработки данной директивы присвоит имени a_size значение 8 (байт). Это имя впоследствии нужно будет указать в качестве операнда команды ret: ret a_size Листинг 14.14. Использование директивы arg {prg14_14.pas} {Программа на Pascal, вызывающая процедуру на ассемблере, полностью совпадает с листингом 14.12} ;prg14_14.asm MASM MODEL small STACK 256 .code main: asmproc proc near ;обьявление аргументов: arg PUBLIC koi:WORD,у:WORD,x:WORD,ch г:BYTE=a_size asmproc push bp сохранение указателя базы mov bp.sp ;настройка bp на стек через sp mov dh,byte ptr у ; у в dh mov dl,byte ptr x ; x в dl mov ah,02h ;номер службы BIOS int lOh ;вызов прерывания BIOS mov ah,09h ;номер службы BIOS mov al.chr символ - в al mov bl,07h ;маска вывода символа xor bh.bh mov ex,koi ; koi в сх int lOh ;вызов прерывания BIOS pop bp ;эпилог ret a_size ;будет ret 8 и выход из процедуры продолжение &
366 Урок 14. Модульное программирование asmproc endp ; конец процедуры endmain ;конец программы Есть еще одна возможность организации данных Pascal—ассемблер — использо- вание операндов директивы model. Вы помните, что она позволяет задать модель памяти и учесть соглашения языков высокого уровня о вызове процедур. Для связи Pascal — ассемблер ее можно задавать в виде MODEL large, pascal Задание в таком виде директивы model позволяет: О описать аргументы процедуры непосредственно в директиве ргос: asmproc proc near ch:byte,x:word,у:word,koi:word О автоматически сгенерировать код пролога и эпилога в процедуре на ассемб- лере; О для доступа к аргументам, объявленным в ргос, использовать их имена. В этом отношении данный вариант является аналогом предыдущего варианта с директивой arg. Листинг 14.15 демонстрирует, как отразились особенности данного варианта на тексте процедуры ассемблера. Обратите внимание, что пролога уже нет, так как он формируется транслятором автоматически; вместо эпилога обязательно нужно задавать только ret без операндов. Интересно изучить текст листинга, который получится в результате трансляции листинга 14.15. В нем видны сформированные транслятором коды пролога и эпилога. Кроме того, трансля- тор заместил команду ret без операндов командой ret 0008, которая, в соответ- ствии с требованиями к взаимодействию с программами на Pascal, удалит из стека аргументы, переданные вызываемой процедуре. Листинг 14.15. Использование директивы MODEL {prg14_15.pas} {Программа на Pascal, вызывающая процедуру на ассемблере, полностью совпадает с листингом 14.12} ;prg14_15.asm MASM MODEL large,pascal STACK 256 .code main: asmproc proc near chr:BYTE,x:WORD,y:WORD,kol:WORD PUBLIC asmproc mov dh.byte ptr у ; у в dh mov dl.byte ptr x ; x в dl mov ah,02h ;номер службы BIOS
Связь ассемблера с языками высокого уровня 367 int lOh ;вызов прерывания BIOS mov ah, 09h ; номер службы BIOS mov al.chr;символ - в al mov bl,07h; маска вывода символа xor bh.bh mov ex, koi; koi в сх int 10h ; вызов прерывания BIOS ret asmproc endp ; конец процедуры end main ;конец программы Листинг 14.16. Результат трансляции листинга 14.15 Turbo Assembler Version 4.1 17/04/98 22:30:57 Pagel prg14_82.asm 1 ;prg14_92.asm 2 MASM 3 0000 MODEL large,pascal 4 0000 STACK 256 5 0000 .code 6 0000 main: 70000 asmproc proc near chr:BYTE,x:W0RD,y:W0RD,kol:W0RD 8 PUBLIC asmproc 1 9 0000 55 PUSH BP 1 10 0001 8B EC MOV BP.SP 1 11 0003 8A 76 06 mov dh, byte ptr у ; у в dh 12 0006 8A 56 08 mov dl,byte ptr x ; x в 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, koi; koi в ex 20 0019 CO 10 int 10h ; вызов прерывания BIOS 1 21 001В 5D POP BP 1 22 001C C2 0008 RET 00008h 23 001F asmproc endp ; конец процедуры 24 end main ; конец программы Таким образом, можно считать, что мы разобрались со стандартными способами вызова ассемблерных процедур из программ на Pascal и передачи им аргументов. Эти способы будут работать всегда, но компилятор может предоставлять и более
368 Урок 14. Модульное программирование удобные средства. Их мы рассматривать не будем, так как читатель, наверное, уже понимает, что они будут сводиться, в конечном счете, к разобранной нами процедуре. Остались открытыми два вопроса: 1. Как быть с передачей данных остальных типов Pascal — ведь мы рассмотрели только данные размером в байт и слово? 2. Как возвратить значение в программу на Pascal? Что касается ответа на первый вопрос, то необходимо вспомнить о том, что в языке Pascal существует два способа передачи аргументов в процедуру: по ссылке и по значению. Тип аргументов, передаваемых по ссылке, совпадает с типом ассемблера dword и с типом pointer в Pascal. По сути, это указатель из четырех байт на некото- рый объект. Структура указателя обычная: два младших байта — смещение, два старших байта — значение сегментной составляющей адреса. С помощью такого указателя в программу на ассемблере передаются адреса следующих объектов: О всех аргументов, объявленные при описании в программе на Pascal как var, независимо от их типа; О аргументов pointer и longint; О строк string; О множеств; О массивов и записей, имеющих размер более четырех байт. Аргументы по значению передаются следующим образом: О для типов char и byte — как байт; О для типа boolean — как байт со значением 0 или 1; О для перечисляемых типов со значением 0...255 — как байт; более 255 — как два байта; О для типов integer и word — как два байта (слово); О для типа real — как шесть байт (три слова); О массивы и записи, длина которых не превышает четырех байт, передаются «как есть». Заметим, что аргументы таких типов, как single, double, extended и comp, пере- даются через стек сопроцессора. Что касается ответа на второй вопрос, то мы рассмотрим его на конкретном примере листингов 14.17 и 14.18. Помните, что мы рассматриваем вызов из программы на Pascal внешней процедуры на ассемблере. Понятно, что вызов ради вызова вряд ли нужен — вызываемая процедура должна иметь возмож- ность вернуть данные в вызывающую программу. Поэтому такую вызываемую
Связь ассемблера с языками высокого уровня 369 процедуру правильнее рассматривать как функцию. В связке Pascal—ассемблер для того чтобы возвратить результат, процедура на ассемблере должна поместить его значение в строго определенное место (табл. 14.2). Таблица 14.2. Возврат результата из процедуры на ассемблере в программу на Pascal Тип возвращаемого значения Куда записать результат? Байт al Слово ах Двойное слово dx:ax (старшее словогмладшее слово) Указатель dxiax (сегмент:смещение) В листинге 14.17 приведен текст вызывающего модуля на Pascal, а в листин- ге 14.18 — код вызываемого модуля на ассемблере. Программа на Pascal ини- циализирует две переменные valuel и value2, после чего вызывает функцию на ассемблере AddAsm для их сложения. Результат возвращается в программу на Pascal и присваивается переменной rez. Листинг 14.17. Вызывающая программа на Pascal {prg14_17.pas} program prg141O1; {внешние объявления} function AddAsm:word; external; {$L prg14_18.obj} var valuel: word; {здесь как внешние} value2: word; rez: word; begin valuel:=2; value2:=3; {вызов функции} rez:=AddAsm; writeln(“Результат: rez); end. Листинг 14.18. Вызываемая процедура на ассемблере ;prg14_18.asm MASM MODEL small data segment word public ;сегмент данных объявление внешних переменных продолжение &
370 Урок 14. Модульное программирование extrn value1:W0RD extrn value2:W0RD data ends ; конец сегмента данных .code assume ds:data ;привязка ds к сегменту ; данных программы на Pascal main: Add Asm proc near PUBLIC AddAsm; внешняя mov cx,ds:value1 ;value1s ex mov dx.ds:value2 ; value2s dx add cx.dx ;сложение mov ax, сх результат в ax, так как - слово ret ; возврат из функции AddAsm endp ; конец функции endmain ;конец программы Как вы, наверное, обратили внимание, здесь была использована еще одна воз- можность доступа к разделяемым данным — использование сегментов типа public (см. урок 5). Совместное использование сегментов данных стало воз- можным благодаря тому, что компилятор Pascal создает внутреннее представ- ление программы в виде сегментов, как и положено для программ, выполняю- щихся на микропроцессоре Intel. Сегмент данных в этом представлении тоже имеет название data, и директива segment для него выглядит так: data segment word public и т. д. Связь С—ассемблер Общие принципы организации такой связи напоминают только что рассмотрен- ное соединение Pascal и ассемблера. Поэтому мы коротко обсудим отличия на примере конкретных программ. Но прежде отметим, что язык C++ предоставляет дополнительные возможности связи программы с ассемблером, одновременно поддерживая традиционную организацию связи. Поэтому мы рассмотрим связь с ассемблером в стиле С как стандартную. При необходимости читатель, зная ос- новы подобной связи, без труда разберется с нюансами дополнительных возмож- ностей связи в стиле C++. Нас по-прежнему интересуют три вопроса: как передать аргументы в процедуру на ассемблере, как к ним обратиться в процедуре на ассемблере и как возвра- тить результат? Вначале отметим, что всегда нужно сохранять — и перед выходом из процедуры восстанавливать — содержимое регистров bp, sp, cs, ds и ss. Это делается перед вызовом процедуры. Остальные регистры нужно сохранять по необходимости, но, на мой взгляд, хорошим тоном является сохранение и последующее восста- новление всех регистров, которые подвергаются изменению внутри процедуры.
Связь ассемблера с языками высокого уровня 371 Передача аргументов в процедуру на ассемблере из программы на С осуществ- ляется также через стек (рис. 14.3), но порядок их размещения в стеке являет- ся обратным тому, что рассмотрен выше для связи Pascal — ассемблер. В каче- стве примера используем ту же задачу. После передачи управления ближнего типа процедуре на ассемблере стек имеет вид как на рис. 14.3, а. Оперативная память Старшие адреса оперативной памяти Старшие адреса оперативной памяти а б Рис. 14.3. Изменение содержимого стека при передаче управления С—ассемблер Процедуры на ассемблере получают доступ к аргументам, переданным в стеке, посредством регистра Ьр. Принцип доступа — тот же, что и выше (рис. 14.3, б). Прежде всего в начало процедуры ассемблера необходимо вставить код пролога: push bp mov bp.sp После этого доступ к аргументам в стеке осуществляется по смещению относи- тельно содержимого Ьр, например: mov ах,[Ьр+4] ;переписать значение ch ;из стека в ах mov bx,[bp+6] ;значение х-в регистр Ьх При организации связи С—ассемблер также можно использовать директиву arg. Это избавит нас от необходимости подсчитывать смещения в стеке для доступа к аргументам и позволит обращаться к ним просто по именам. a rg с h: by t е; х: wo г d; у: wo г d; ко 1: wo гd push bp mov bp.sp mov ax,[ch] ;переписать значение ch ;из стека в ах mov bx, [х] ;значение х-в регистр Ьх
372 Урок 14. Модульное программирование Чтобы не повторяться, мы рассмотрим, как изменятся вызываемый и вызываю- щий модули (см. листинги 14.19 и 14.20) для связи С—ассемблер по сравнению с листингами 14.17 и 14.18. Листинг 14.19. Вызывающий модуль на С (C++) //prg14_19.cpp #include<conio.h> extern void asmproc(char ch, unsigned x, unsigned y,unsigned koi); void main (void) { clrscrO; asmprocC’a", 2, 3, 5); asmprocf's", 9, 2, 7); } Листинг 14.20. Вызываемая процедура на ассемблере ; prg14_20.asm MASM MODEL small,С ; модель памяти и тип кода STACK 256 .code main: asmproc proc c near ch:BYTE,x:WORD,y:WORD,kol:WORD PUBLIC _asmproc ; символ подчеркивания обязателен mov dh,y ;у-координата символа в dh mov dl,x ;х-координата символа в dl mov ah, 02h; номер службы BIOS int 10h ;вызов прерывания BIOS mov ah,09h ; номер службы BIOS mov ex, koi; koi - количество ’’выводов" в сх mov bl, 07h; маска вывода в bl xor bh.bh mov al, ch ;ch - символ в al int lOh ;вызов прерывания BIOS ret ; возврат из процедуры asmproc endp endmain Что касается передачи аргументов С—ассемблер, то здесь, как видите, все до- вольно прозрачно. Как видите, в листинге 14.20 мы используем директиву MODEL с операндом С и директиву PROC с указанием языка С. Этим мы доверяем компилятору самому сформировать коды пролога и эпилога, а также
Связь ассемблера с языками высокого уровня 373 организовать обращение к переменным в стеке по их именам. Но при использо- вании конкретных программных средств организация такой связи выглядит намного проблематичней. Не в последнюю очередь это связано с тем, что компиляторы языка C/C++ разрабатывают множество фирм — в отличие от Pascal, компилятор для которого выпускает практически одна фирма Borland1. Это обстоятельство, на мой взгляд, — основная причина сложности связи С- ассемблер, так как каждая фирма реализует ее по-своему (хотя суть и остается практически неизменной). Поэтому, как мне кажется, нет смысла рассматри- вать множество частных случаев, тем более, что это не является целью данной книги. Обращайтесь к документации на ваш компилятор C/C++. Опыт пока- зывает, что достаточно хороший эффект дает применение ассемблерных вста- вок в программу на C/C++. Как правило, компиляторы позволяют связывать модули на C/C++ и ассемблере с использованием средств командной строки. Так как этот процесс достаточно стандартизован, есть смысл его рассмотреть. В качестве примера выберем компилятор Visual C++ 4.0 фирмы Microsoft. Скорее всего, для компиляторов других фирм изменятся только имена файлов транслятора и компоновщика. Типовая последовательность шагов выглядит примерно так: О Составить текст программы на C++ (листинг 14.19). В этой программе — объявить процедуру asmproc внешней: extern void asmproc(char ch, unsigned x, unsigned y, unsigned koi); О Выполнить трансляцию модуля C++ и получить объектный модуль: cl prg14_19.срр О Составить текст процедуры на ассемблере (листинг 14.20), в кото/ объявить процедуру asmproc общедоступной с помощью директивы PUBLIC. Заметьте, что идентификатору asmproc предшествует символ подчеркива- ния — asmproc. Компилятор добавляет знак подчеркивания ко всем гло- бальным идентификаторам. Поэтому для того, чтобы при компоновке про- грамма link восприняла asmproc и asmproc как один и тот же идентификатор, ко всем именам, объявляемым глобальными в программе на ассемблере ди- рективой PUBLIC, требуется добавить знак подчеркивания. О Выполнить трансляцию программы на ассемблере1 2 masm /zi prg14_20,,, 1 Известен также Microsoft Pascal. Язык Object Pascal также пользуется популярностью среди про- граммистов для Macintosh (собственно, Object Pascal и разрабатывался для компьютеров Apple). - Примеч. ред. 2 В данном случае я сторонник того, чтобы при разработке, а тем более при интегрировании про- грамм применять средства разработки одной фирмы. По этой причине для трансляции я предложил использовать компилятор ассемблера фирмы Microsoft — MASM. Для ассемблерных программ «общего назначения» я предпочел (как это видно из книги) использовать TASM.
374 Урок 14. Модульное программирование О Выполнить объединение объектных модулей с помощью программы-компо- новщика link из пакета Visual C++ 4.0: link prg14_19.obj prg14_20.obj, исполняемому модулю будет присвоено имя prg14_19.exe. Как возвратить результат в программу на С из процедуры на ассемблере? Для этого существуют стандартные соглашения (табл. 14.3). Перед возвратом управ- ления в программу на С в программе на ассемблере необходимо поместить ре- зультат или сформировать указатель в указанных регистрах. Для иллюстрации работы с функцией С, текст которой написан на ассемблере, рассмотрим лис- тинги 14.21 и 14.22. В них функция, написанная на ассемблере, подсчитывает сумму элементов массива. В функцию передаются адрес массива и его длина. Результат суммы элементов массива возвращается обратно в вызывающую программу на С. Таблица 14.3. Возврат аргументов из процедуры на ассемблере в программу на C/C++ Тип возвращаемого значения (C++) Куда поместить результат? unsigned char ax char ax enum ax unsigned short ax short ax unsigned int ax int ax unsigned long dx:ax long dx:ax указатель near ax указатель far dx:ax Листинг 14.21. Вызывающая программа на С /★prg14_21.c*/ «include <stdio.h> extern int sum_asm(int massiv[],int count); main() { int mas[5]={1,2,3,4,5}; int len=5; int sum; sum=sum_asm(mas,len); printf(*’%d\n”,sum);
Связь ассемблера с языками высокого уровня 375 return(O); } Листинг 14.22. Вызываемая процедура на ассемблере ;prg14_22.asm MASM MODEL small,с .stack 100h .code public sum_asm sum_asm p гос c nea r ad r jnas: wo rd, lenjnas: wo rd mov ax,0 mov cx.lenjnas ; длину массива - в сх mov si.adrjnas ;адрес массива - в si add ах, [si] ; сложение аккумулятора с элементом массива add si, 2 адресовать следующий элемент массива loop cycl ret ; возврат из функции, результат - в ах sum_asm endp end Таким образом, мы рассмотрели связь модулей на языках высокого уровня с модулями на ассемблере. Это обсуждение было несколько ограниченно; его можно рассматривать как введение (хотя и достаточно подробное) в предмет. Можно было уделить данному вопросу гораздо больше внимания. Но глав- ное — мы разобрались с принципами. Теперь дело за малым: взять литерату- ру — лучше документацию для конкретного языка высокого уровня — и раз- бираться с тем, как в языке разработчики реализовали связь с ассемблером. Затем следует понять техническое осуществление этой связи, используя осо- бенности и настройки конкретной среды программирования. Подведем некоторые итоги: 0 Язык ассемблера содержит достаточно мощные средства поддержки струк- турного программирования. В языке ассемблера эта технология поддержи- вается в основном с помощью механизма процедур и частично с использова- нием макрокоманд. 0 Гибкость интерфейса между процедурами достигается за счет разнообразия вариантов передачи аргументов в процедуру и возвращения результатов. Для этого могут использоваться регистры, общие области памяти, стек, ди- рективы extrn и public.
376 Урок 14. Модульное программирование 0 Средства TASM поддерживают связи между языками. Ключевой момент при этом — организация обмена данными. Обмен данными между процедурами на языках высокого уровня и ассемблера производится через стек. Для дос- тупа к аргументам используется регистр Ьр или (что более удобно) директи- ва arg. 0 Можно доверить компилятору самому формировать коды пролога и эпило- га, указав язык в директиве MODEL. Кроме того, указание языка позволяет задействовать символические имена аргументов, переданных процедуре в стеке, вместо прямого использования регистра Ьр для доступа к ним. Тем самым повышаются мобильность разрабатываемых вами программ и устой- чивость их к ошибкам. 0 Для возврата результата в программу на языке высокого уровня необходимо использовать конкретные регистры. Через них можно передать как сами дан- ные, так и указатели.
УРОК Прерывания □ Понятие прерывания □ Классификация прерываний для микропроцессоров Intel □ Характеристика аппаратных и программных средств обработки прерываний □ Программируемый контроллер прерываний 18259А □ Программирование контроллера 18259А □ Реальный режим работы микропроцессора □ Обработка прерываний в реальном режиме
На предыдущем уроке мы закончили рассмотрение всех основных конструк- ций языка ассемблера и базовых приемов программирования на нем. Обсужде- ние собственно языковых конструкций невольно сопровождалось выяснением тех или иных особенностей микропроцессора. Это естественно, так как язык ассемблера является символическим представлением машинного языка, кото- рый различен для разных компьютеров. Поэтому нет смысла рассматривать язык ассемблера в отрыве от принципов работы микропроцессора и наоборот. Заключительные три урока мы посвятим оставшимся за кадром, но довольно принципиальным вопросам работы микропроцессора. Тогда мы сможем полу- чить полную картину того, что происходит в компьютере на самом низком уровне. Понимание сути этих процессов, возможно, подвигнет вас к поиску оп- тимальных решений при разработке программ. Нажимая на клавиши клавиатуры, задумывались ли вы над тем, что в это время происходит в компьютере, как он узнает о том, что была нажата та или иная клавиша? Или, к примеру, каким образом ведется отсчет времени в компьюте- ре? Ведь каждый сигнал от различных устройств компьютера, чтобы стать «осознанным», должен быть обработан какой-то программой. А выполнять программы может только микропроцессор. Но он один в компьютере, следова- тельно, в каждый конкретный момент времени микропроцессор может быть за- нят выполнением только одной программы. Как же ему узнавать о нажатиях клавиш и других сигналах, постоянно возникающих во время работы компью- тера, если он выполняет некоторую программу? Возможным решением здесь может быть, например, периодическая остановка текущей программы и выполнение других программ, производящих опрос уст- ройств компьютера и, в свою очередь, запускающих необходимые программы для обслуживания этих устройств. Это далеко не оптимальный путь, значи- тельно снижающий производительность компьютера. Другой возможный под- ход к обслуживанию устройств — создание системной очереди на обслужива- ние. Этот подход предполагает некую очередь, в которую «выстраиваются» запросы на обслуживание от устройств. Микропроцессор периодически про- сматривает эту очередь и выполняет обслуживание запросов в ней. Этот вари- ант, хотя и лучше предыдущего, но тоже не оптимальный. В современных мик- ропроцессорах, каковыми являются микропроцессоры фирмы Intel, принят подход, основанный на понятии прерывания. Прерывание — инициируемый определенным образом процесс, временно переключающий микропроцессор на выполнение другой программы с последующим возобновлением выполнения прерванной программы.
379 Что дает использование механизма прерываний? Он позволяет обеспечить наиболее эффективное управление не только внешними устройствами, но, как мы увидим далее, и программами. Нажимая клавишу на клавиатуре, вы факти- чески инициируете посредством прерывания немедленный вызов программы, которая распознает нажатую клавишу, заносит ее код в буфер клавиатуры, от- куда он в дальнейшем считывается некоторой другой программой или опера- ционной системой. На время такой обработки микропроцессор прекращает выполнение некоторой программы и переключается на так называемую проце- дуру обработки прерывания. После того как данная процедура выполнит необ- ходимые действия, прерванная программа продолжит выполнение с точки, где было приостановлено ее выполнение. Некоторые операционные системы ис- пользуют механизм прерываний не только для обслуживания внешних уст- ройств, но и для предоставления своих «услуг». Так, хорошо известная и до сих пор достаточно широко используемая операционная система MS-DOS взаи- модействует с системными и прикладными программами преимущественно че- рез систему прерываний. Исходя из вышеприведенных рассуждений, можно сказать, что прерывания могут быть внешними и внутренними. Внешние прерывания вызываются внешними по отношению к микропроцессо- ру событиями. На рис. 15.1 схематически изображена подсистема прерываний компьютера на базе микропроцессора Intel. Рис. 15.1. Подсистема прерываний компьютера на базе микропроцессора Intel
380 Урок 15. Прерывания На рис. 15.1 видно, что у микропроцессора есть два физических контакта — INTR и NMI. На них и формируются внешние по отношению к микропроцессору сигна- лы, возрастающие фронты которых извещают микропроцессор о том, что некото- рое внешнее устройство просит уделить ему внимание. Вход INTR (INTerrupt Request) предназначен для фиксации запросов от различных периферийных устройств, например таких, как системные часы, клавиатура, жесткий диск и т. д. Вход NMI (NonMaskable Interrupt) — немаскируемое прерывание. Этот вход используют для того, чтобы сообщить микропроцессору о некотором событии, требующем безотлагательной обработки, или катастрофической ошибке. Внешние прерывания относятся, естественно, к непланируемым пре- рываниям. Внутренние прерывания возникают внутри микропроцессора во время вычис- лительного процесса. К их возбуждению приводит одна из двух причин: О ненормальное внутреннее состояние микропроцессора, возникшее при обра- ботке некоторой команды программы. Такие события принято называть ис- ключительными ситуациями, или просто исключениями. Этот вид прерыва- ний отчасти также можно отнести к непланируемым; О обработка машинной команды int хх. Этот тип прерываний называется про- граммным. Это — планируемые прерывания, так как с их помощью про- граммист обращается в нужное для него время за обслуживанием своих за- просов либо к операционной системе, либо к BIOS, либо к собственным программам обработки прерываний. Далее мы рассмотрим особенности обработки прерываний. Как уже отмеча- лось, микропроцессоры Intel имеют два режима работы — реальный и защи- щенный. В этих режимах обработка прерываний осуществляется принципиаль- но разными методами. Поэтому на данном уроке мы дадим характеристику реального режима и рассмотрим обработку прерываний в этом режиме. На сле- дующем уроке будет рассмотрен защищенный режим работы микропроцессора, и на последнем уроке мы рассмотрим обработку прерываний в этом режиме. Для глубокого понимания процессов, происходящих в компьютере при осущес- твлении прерывания, необходимо узнать о том, какие ресурсы компьютера при этом задействуются, каковы их характеристики и принципы функционирования. В общем случае, система прерываний — это совокупность программных и аппа- ратных средств, реализующих механизм прерываний. К аппаратным средствам системы прерываний относятся: О выводы микропроцессора: • INTR — вывод для входного сигнала внешнего прерывания. На этот вход поступает выходной сигнал от микросхемы контроллера прерываний 8259А; • INTA — вывод микропроцессора для выходного сигнала подтверждения получения сигнала прерывания микропроцессором. Этот выходной сиг-
Контроллер прерываний 381 нал поступает на одноименный вход INTA микросхемы контроллера преры- ваний 8259А; • NMI — вывод микропроцессора для входного сигнала немаскируемого прерывания; О микросхема программируемого контроллера прерываний 8259А. Она предназ- начена для фиксирования сигналов прерываний от восьми различных вне- шних устройств. В силу ее важной роли при работе всей вычислительной системы, мы ее подробно рассмотрим ниже; О внешние устройства: таймер, клавиатура, магнитные диски и т. д. К программным средствам системы прерываний реального режима относятся: О таблица векторов прерываний, В этой таблице в определенном формате, кото- рый зависит от режима работы микропроцессора, содержатся указатели на процедуры обработки соответствующих прерываний; О следующие флаги в регистре флагов f lags\ef lags: • IF (Interrupt Flag) — флаг прерывания. Предназначен для так называе- мого маскирования (запрещения) аппаратных прерываний, то есть пре- рываний по входу INTR. На обработку прерываний остальных типов флаг IF влияния не оказывает. Если IF = 1, микропроцессор обрабатывает внеш- ние прерывания, если IF = 0, микропроцессор игнорирует сигналы на входе INTR; • TF (Trace Flag) — флаг трассировки. Единичное состояние флага TF пере- водит микропроцессор в режим покомандной работы. В режиме поко- мандной работы после выполнения каждой машинной команды в микро- процессоре генерируется внутреннее прерывание с номером 1, и далее следуют действия в соответствии с алгоритмом обработки данного пре- рывания; О машинные команды микропроцессора: int, into, iret, cli, sti. Контроллер прерываний На наш взгляд, знакомство с системой прерываний микропроцессора Intel сле- дует начать с обсуждения организации обработки аппаратных прерываний. Как видно из рис. 15.1, центральное место в схеме обработки аппаратных пре- рываний занимает программируемый контроллер прерываний (ПКП), выпол- ненный в виде специальной микросхемы i8259A. Как мы уже говорили, эта микросхема может обрабатывать запросы от восьми источников внешних пре- рываний. Этого явно мало, поэтому в стандартной конфигурации вычисли- тельной системы обычно используют две последовательно соединенные микро- схемы 18259А. В результате такого соединения количество возможных источников внешних прерываний возрастает до 15.
382 Урок 15. Прерывания Для того чтобы разобраться с обработкой аппаратных прерываний, нам при- дется проникнуть внутрь микросхемы i8259A. Перечислим функции, выпол- няемые микросхемой контроллера прерываний: О фиксирование запросов на обработку прерывания от восьми источников, формирование единого запроса на прерывание и подача его на вход INTR микропроцессора; О формирование номера вектора прерывания и выдача его на шину данных; О организация приоритетной обработки прерываний; О запрещение (маскирование) прерываний с определенными номерами. На рис. 15.2 показано схематическое представление внутренней структуры и физических выводов микросхемы i8259A. d7 ... dO Блок управления чтением/записью Буфер данных Схема каскадирования Регистр состояния (ISR) Арбитр приоритетов Программируемый контроллер прерываний (ПКП) Регистр маскирования прерываний (IMR) Регистр прерываний (IRR) Схема управления ПКП irq7 irq5 irq3 irq6 irq4 irq2 irq! irqO INTA INT Puc. 15.2. Структурная схема и схематическое представление выводов 18259А Рассмотрим назначение тех выводов i8259A, которые представляют для нас интерес: dO.. .d7 — выводы i8259A, замыкающиеся на системную шину данных. По ним передается номер вектора прерывания и принимается управляющая информа- ция; INT — вывод выходного сигнала запроса на прерывание, который подается на вход микропроцессора INTR; INTA — вывод для сигнала от микропроцессора, подтверждающего факт приня- тия им прерывания на обслуживание; irqO... irq7 — выводы для входных сигналов запросов на прерывания от внеш- них устройств.
Контроллер прерываний 383 Важное свойство данного контроллера — возможность его программирования, что позволяет достаточно гибко изменять алгоритмы обработки аппаратных прерываний. Исходя из этого, микросхема i8259A имеет два состояния: О состояние настройки параметров обслуживания прерываний, во время кото- рого путем посылки в определенном порядке так называемых управляющих слов производится инициализация контроллера; О состояние работы — это обычное состояние контроллера, в котором произ- водится фиксация запросов на прерывание и формирование управляющей информации для микропроцессора в соответствии с параметрами наст- ройки. Рассмотрим назначение основных структурных компонентов контроллера пре- рываний (см. рис. 15.2): О регистр запросов на прерывания IRR (Interrupt Request Register) — восьми- разрядный регистр, фиксирующий поступление сигнала на один из входов i8259A irq0...irq7. Фиксация выражается в установке соответствующего бита в единичное состояние; О регистр маскирования прерываний IMR (Interrupt Mask Register) — восьми- разрядный регистр, с помощью которого можно запретить обработку запро- сов на прерывания, поступающих на соответствующие входы (уровни) irqO... irq_7. Для запрещения (маскирования) определенных уровней пре- рываний необходимо установить соответствующие биты регистра IMR. Эта операция осуществляется путем программирования порта 21h; О регистр обслуживаемых прерываний ISR (Interrupt Service Register) — вось- миразрядный регистр, единичное состояние разрядов которого показывает, прерывания каких уровней обрабатываются в данный момент в микропро- цессоре; О арбитр приоритетов PR (Priority Resolver) — функцией данного блока явля- ется разрешение конфликта при одновременном поступлении запросов на входы irqO... irq7; О блок управления — основной функцией данного блока является организация информационного обмена контроллера прерываний и микропроцессора че- рез шину данных. На этот блок замыкаются как выводы dO... d7, так и неко- торые другие (см. рис. 15.2). Рассмотрим возможное прохождение и обработку сигнала прерывания от неко- торого внешнего устройства. При этом воспользуемся структурной схемой кон- троллера прерываний и обозначениями на ней (см. рис. 15.2). Допустим, на вход irqO поступает сигнал прерывания, что приводит к установ- ке нулевого бита регистра IRR. Этот регистр связан с регистром маски IMR, со- стояние битов которого определяет, какие уровни прерываний запрещены (единичные биты) или разрешены к обработке (нулевые биты). Управление данным регистром осуществляется через порт 21h. Таким образом, если бит 0 в
384 Урок 15. Прерывания IMR равен нулю, то прерывание уровня 0 разрешено. Далее сигнал поступает к арбитру приоритетов. Как мы уже отметили, функция этого блока — разреше- ние конфликтов при одновременном поступлении запросов на несколько уров- ней. Обычно самый высокий приоритет у уровня irq_0, и далее приоритет уменьшается с возрастанием номера уровня. Если конфликта нет, то сигнал поступает на схему управления контроллером прерываний, которая формирует сигнал на выводе int. Этот вывод связан со входом микропроцессора INTR. Та- ким образом, сигнал на входе i8259A достиг микропроцессора. Что происходит далее в микропроцессоре, мы рассмотрим ниже. Сейчас отметим только значи- мые для данного обсуждения моменты. Итак, при поступлении сигнала на вход INTR в микропроцессоре происходят следующие процессы: 1. Анализируется флаг IF. Если вы помните, единичное состояние этого флага говорит о том, что аппаратные прерывания разрешены, нулевое — запре- щены. 2. Если прерывания запрещены, то запрос на прерывание «повисает» до мо- мента установки IF в единицу. 3. Если прерывания разрешены, микропроцессор выполняет следующие дей- ствия: О сбрасывает флаг IF в ноль; О формирует сигнал подтверждения прерывания на выводе микропроцес- сора INTA. Этот вывод микропроцессора замкнут на одноименный вывод микросхемы i8259A. Таким образом, сигнал о прерывании прошел через микропроцессор и вернулся обратно в контроллер прерываний i8259A через вывод INTA. Данный вывод внутри контроллера прерываний замкнут на его схему управления, которая выполняет сразу несколько действий при поступлении этого сигнала: 1. Сбрасывает бит в регистре IRR, соответствующий уровню прерывания i rq_O. 2. Устанавливает в 1 бит 0 регистра ISR, тем самым фиксируя факт обработки прерывания уровня 0 в микропроцессоре. 3. Формирует с помощью блока управления номер вектора прерывания, значе- ние которого формируется в буфере данных и далее поступает на выводы i8259A dO.. .d7. Выводы dO.. .d7 замкнуты на шину данных, по которой номер вектора поступает в микропроцессор. В микропроцессоре этот номер ис- пользуется для вызова соответствующей процедуры обработки прерывания. На данном этапе обработки прерывания, после того как номер прерывания по шине данных поступил в микропроцессор, последнему стало известно все об источнике прерывания. Далее микропроцессор осуществляет процедуру обра- ботки прерывания. Если в это время придет другой сигнал о прерывании того же уровня, то он будет запомнен установкой бита в IRR и обслуживание этого прерывания будет отложено. Если приходит прерывание другого уровня, то его дальнейшая обработка зависит от приоритета, который оно имеет по отноше-
Контроллер прерываний 385 нию к уже обрабатываемым прерываниям. Если приоритет выше, то текущая процедура обработки прерывания останавливается и вызывается процедура об- работки более приоритетного прерывания. Очень важный момент связан с процессом завершения обработки прерывания. Проблема здесь состоит в следующем. После принятия микропроцессором зап- роса на обслуживание прерывания в контроллере устанавливается бит в регис- тре ISR, номер этого бита соответствует уровню прерывания. Установка бита с данным номером блокирует все прерывания уровня, начиная с текущего, и ме- нее приоритетные в блоке-арбитре приоритетов. Если процедура прерывания закончит свою работу, то она сама должна этот бит сбросить, иначе все преры- вания этого уровня и менее приоритетные будут игнорироваться. Для осущес- твления такого сброса необходимо послать код 20h в порт 20h. Есть и другая возможность — установить такой режим работы микросхемы i8259A, когда сброс этого бита будет производиться автоматически. Тонкий момент заключа- ется в том, что происходить такой автоматический сброс будет одновременно с приходом сигнала INTA (то есть извещения о том, что запрос на обработку пре- рывания принят к обработке микропроцессором). Недостаток автоматического сброса в том, что существует вероятность прихода прерывания того же уровня, который уже обрабатывается в данный момент микропроцессором. В этом слу- чае процедура обработки прерывания должна обладать свойством реенте- рабельности, то есть допускать повторное обращение к себе до завершения обработки предыдущего обращения. Для того чтобы процедура была реентера- бельной, она должна иметь специфическую структуру, в частности, для каждо- го сеанса обращения к ней создается своя область для хранения переменных и значений регистров, а исполняемая часть процедуры находится в оперативной памяти только в одном экземпляре. Иногда может потребоваться подобный ав- томатический сброс, но надежнее и проще, конечно, контролировать этот про- цесс и самостоятельно сбрасывать бит в ISR. Это можно сделать либо в конце работы процедуры, либо в том месте процедуры, начиная с которого можно разрешить рекурсивный вызов данной процедуры, будучи уверенным в том, что она не разрушит никаких данных и работу программы в целом. Другой не менее интересный момент заключается в том, что микропроцессор при принятии к обработке запроса на прерывание сбросил флаг IF в ноль, тем самым запретив все последующие аппаратные прерывания. Этим обстоятель- ством программист может пользоваться по своему усмотрению. Вы, конечно, помните, что все запросы на прерывания с приоритетом, равным или меньшим текущему, будут запрещены в любом случае, — это обусловлено логикой рабо- ты контроллера i8259A. Поэтому программист должен решить, насколько его замыслам могут помешать запросы на более приоритетные прерывания. Если это некритично, то лучше сразу, в начале процедуры обработки прерывания установить флаг IF в единицу. В большинстве случаев эту операцию нужно делать как можно раньше. Для установки флага IF в единицу в системе команд микропроцессора есть специальная команда, не имеющая операндов: sti — разрешить аппаратные прерывания.
386 Урок 15. Прерывания Наиболее наглядный пример, показывающий важность своевременной установ- ки IF, связан с отсчетом времени. Если вы не знакомы с тем, как ведется учет времени в компьютере, то уделим этому немного внимания. Как после включе- ния компьютер определяет текущее время суток или как он запоминает ин- формацию о своей конфигурации после выключения? Все дело в том, что ком- пьютер имеет небольшую энергонезависимую память, которая питается от аккумулятора и не зависит от подключения к электросети. Конструктивно эта память выполнена на специальном типе полупроводниковых элементов с так называемой CMOS-структурой (Complementar Metal Oxide Semiconductor — ком- плиментарная МОП-структура). Особенность таких элементов памяти — в их пониженной по сравнению с обычными микросхемами потребляемой мощнос- ти (при этом они являются и более медленными, что в данном случае неприн- ципиально). Аккумулятор кроме CMOS-памяти питает еще и микросхему си- стемных часов, в функции которой входит отсчет текущих даты и времени суток. Таким образом, текущие значения даты и времени постоянно хранятся в CMOS-памяти и поддерживаются в актуальном состоянии даже после выключе- ния компьютера. Кроме того, в CMOS-памяти хранится некоторая другая ин- формация, в частности, о конфигурации компьютера. Во время загрузки компью- тера дата и время считываются в область данных BIOS. Дальнейший отсчет времени, после загрузки системы, ведется уже с помощью системного таймера — другой микросхемы на системной плате, в функции которой входит регулярно, примерно 18,2 раза в секунду, генерировать сигнал, который в качестве прерыва- ния подается на уровень irqO контроллера прерываний i8259A. Во время работы компьютера соответствующая программа BIOS обрабатывает прерывание данно- го уровня и ведет счет времени. Если терять такты по этому входу, то фактичес- кое время на часах будет отставать, и поэтому в большинстве случаев в обработ- чиках прерываний есть смысл как можно раньше выдавать команду sti. Программирование контроллера прерываний18259А Большая популярность применения этой микросхемы в качестве диспетчера аппаратных прерываний в компьютерах на базе микропроцессоров Intel объяс- няется наличием большого количества различных режимов ее работы, что по- зволяет сделать подсистему прерываний достаточно гибкой и эффективной. Действительно, если посмотреть на развитие аппаратной части компьютеров, начиная, например, с i8088/8086, то видно, что менялись самые разные компо- ненты, но подсистема прерываний, основанная на микросхеме i8259A, так и осталась неизменной. В процессе загрузки компьютера и в дальнейшем во время работы контроллер прерываний настраивается на работу в одном из четырех режимов: 1. FNM (Fully Nested Mode) — режим вложенных прерываний, В этом режиме каждому входу (уровню) irqO... irq7 присваивается фиксированное значение
Программирование контроллера прерываний i8259A 387 приоритета, причем уровень irqO имеет наивысший приоритет, a irq7 — наименьший. Приоритетность прерываний определяет их право на прерыва- ние обработки менее приоритетного прерывания более приоритетным (при условии, конечно, что IF = 1). 2. ARM (Automatic Rotation Mode) — режим циклической обработки прерыва- ний. В этом режиме значения приоритетов уровней прерываний также ли- нейно упорядочены, но уже не фиксированным образом, а изменяются пос- ле обработки очередного прерывания по следующему принципу: значению приоритета последнего обслуженного прерывания присваивается наимень- шее значение. Следующий по порядку уровень прерывания получает наи- высшее значение, и поэтому при одновременном приходе запросов на преры- вания от нескольких источников преимущество будет иметь этот уровень. Это дает возможность обеспечить «равноправие» при обработке преры- ваний. 3. SRM (Specific Rotation Mode) — режим адресуемых приоритетов. Этот ре- жим можно рассматривать как вариант режима ARM. В режиме SRM програм- мист или система самостоятельно могут назначить уровень прерывания с наивысшим приоритетом. 4. PM (Polling Mode) — режим опроса. Этот режим запрещает контроллеру ав- томатически прерывать работу микропроцессора при появлении прерыва- ния от некоторого внешнего устройства. Для того чтобы микропроцессор смог узнать о наличии того или иного запроса на прерывание, он должен сам обратиться к i8259A для получения содержимого IRR, проанализировать его и далее действовать по своему алгоритму. Данный режим моделирует так называемую опросную дисциплину обработки прерываний. Мы упомина- ли о ней в начале урока. Согласно этому подходу, инициатором обработки прерывания становится не само прерывание, как при векторной дисциплине, а микропроцессор, причем в определяемые им (точнее, операционной систе- мой, выполняемой на нем) моменты времени. Программирование контроллера прерываний осуществляется через адресное пространство ввода-вывода посредством двух 8-битовых портов с адресами 20h и 21 h. Управление контроллером осуществляется путем посылки в определен- ной последовательности в эти порты специальных приказов двух типов: О ICW (Initialization Control Word) — управляющее слово инициализации. Всего имеется четыре таких слова с жесткой внутренней структурой — ICW1... ICW4. Эти слова предназначены для задания режима работы контроллера. Коли- чество этих слов (4) определено количеством режимов (см. выше). О OCW (Operation Control Word) — операционное управляющее слово. Таких слов всего три, и они несут информационную нагрузку для определенных выше режимов работы контроллера прерываний. Обычно их обозначают 0CW1...0CW3.
388 Урок 15. Прерывания Как вы уже, наверное, успели понять, процесс программирования контроллера жестко регламентирован. Поэтому рассмотрим вначале формат приказов управ- ления, а затем их практическое применение. ICW1 — определить особенности последовательности приказов Состояние битов этого приказа определяет особенности в последовательности приказов при инициализации контроллера. Данный приказ посылается в порт 20h. Таблица 15.1. Формат приказа ICW1 Биты ICW1 Назначение и содержимое 0 1 — управляющее слово ICW4 будет присутствовать в данной 1 0 1 последовательности приказов — каскадное подключение i8259A (ICW3 будет в последова- тельности); — одиночное подключение i8259A (ICW3 не будет) 2 0 — не используется 3 4 0 1 — прерывание по перепаду сигнала — признак ICW1 5 0 — не используется 6 0 — не используется 7 0 — не используется 1CW2 — определение базового адреса Мне кажется, что настало время прояснить еще один принципиальный момент, до этого времени сознательно замалчиваемый. Он связан с тем, по какому принципу определяется числовой диапазон адресов векторов прерываний для аппаратных прерываний, замкнутых на контроллер прерываний. В реальном режиме работы микропроцессора для хранения указателей (векторов) на про- цедуры-обработчики прерываний используется специальная область памяти — таблица векторов прерываний. Эта таблица начинается с нулевого адреса опе- ративной памяти и занимает 1 Кбайт. Среди векторов есть, конечно, и вектора, указывающие на процедуры-обработчики тех прерываний, которые замкнуты на контроллер. Эти вектора располагаются в таблице последовательно, одной группой, и их нумерация начинается с некоторого номера вектора, называемого базовым. Приказ ICW2 позволяет задать номер этого базового вектора для кон- троллера прерываний в соответствии с тем номером, который назначен соот- ветствующему вектору в таблице векторов прерываний. В реальном режиме работы микропроцессора BIOS в процессе начальной загрузки системы ини-
Программирование контроллера прерываний i8259A 389 диализируется ведущий контроллер значением 08h, а ведомый — значением 70h. Теперь понятно, почему обработчику прерываний от таймера соответству- ет номер вектора 08h в таблице векторов прерываний, хотя физически он зам- кнут на уровень 0 контроллера i8259A. При желании мы вполне можем изме- нить значение базового номера на любой не используемый в системе номер, к примеру — 90h. Также следует учитывать, что некорректная установка нового номера этим приказом может полностью нарушить работу всей системы. Данный приказ посылается в порт 21 h. Таблица 15.2. Формат приказа ICW2 Биты ICW2 Назначение и содержимое 0 1 0 — не используется 0 — не используется 2 0 — не используется 3 Бит для задания номера базового вектора 4 Бит для задания номера базового вектора 5 Бит для задания номера базового вектора 6 Бит для задания номера базового вектора 7 Бит для задания номера базового вектора Как видно, для задания номера базового вектора используются биты с 3 по 7 приказа ICW2. Объяснить это можно тем, что на контроллер замыкаются 8 ис- точников прерываний. Выше мы отметили, что номера векторов, соответствую- щих прерываниям, замкнутых на контроллер, имеют последовательные номера, начиная с базового. Так, для контроллера, инициализированного значением ба- зового номера 08h, номера векторов в таблице векторов прерываний будут 08h, 09h, Oah, Obh и т. д. Отсюда и получается, что для задания базового номера биты 0...2 использовать нельзя, так как они применяются для формирования адресов векторов прерываний следующих после базового уровней. ICW3 — связь контроллеров Этот приказ предназначен для связи контроллеров в системе с несколькими контроллерами прерываний. Вариант работы с одной микросхемой i8259A, позволяющий обрабатывать за- просы от 8 источников, использовался в ранних системах на базе микропроцес- соров i8088\86 (в архитектуре XT). Но i8259A позволяет организовать так на- зываемое каскадное соединение этих микросхем, при котором выход INT од- ной микросхемы подается на вход одного из уровней irq другой микросхемы (см. рис. 15.1). Это позволяет организовать обработку запросов от большего числа источников. При этом один контроллер является ведущим, а другой — ведомым (тот, который подключен ко входу irq ведущего). Ниже мы разберем-
390 Урок 15. Прерывания ся с каскадированием более подробно. Сейчас отметим, что формат приказа ICW3 зависит от того, какой контроллер инициализируется — ведущий или ведомый. При инициализации ведущего контроллера ICW3 сообщает, к каким его входам irq подсоединены ведомые контроллеры. Соответственно, при инициализации ведомого контроллера нужна другая форма этого приказа, которая несет инфор- мацию о том, к какому входу ведущего подключен данный ведомый контроллер. Приказ ICW3 посылается в порт 21 h. Таблица 15.3. Формат приказа ICW3 (для ведущего контроллера) Биты ICW3 Назначение и содержимое 0 1 — если ко входу irqO подключен ведомый контроллер; 0 — если ко входу irqO подключено внешнее устройство 1 1 — если ко входу irql подключен ведомый контроллер; 0 — если ко входу irql подключено внешнее устройство 2 1 — если ко входу irq2 подключен ведомый контроллер; 0 — если ко входу irq2 подключено внешнее устройство 3 1 — если ко входу irq3 подключен ведомый контроллер; 0 — если ко входу irq3 подключено внешнее устройство 4 1 — если ко входу irq4 подключен ведомый контроллер; 0 — если ко входу irq4 подключено внешнее устройство 5 1 — если ко входу irq5 подключен ведомый контроллер; 0 — если ко входу irq5 подключено внешнее устройство 6 1 — если ко входу irq6 подключен ведомый контроллер; 0 — если ко входу irq6 подключено внешнее устройство 7 1 — если ко входу irq7 подключен ведомый контроллер; 0 — если ко входу irq7 подключено внешнее устройство Таблица 15.4. Формат приказа ICW3 (для ведомого контроллера) Биты ICW3 Назначение и содержимое 0...3 Код идентификации номера входа ведущего контроллера, к ко- торому подсоединен ведомый 4...7 Всегда 0 ICW4 — дополнительные особенности обработки прерываний Этот приказ определяет дополнительные особенности обработки прерываний контроллером i8259A. Данный приказ посылается в порт 21 h.
Программирование контроллера прерываний i8259A 391 Таблица 15.5. Формат приказа ICW4 Биты ICW4 Назначение и содержимое 0 Тип микропроцессора: 0 — i8080 1 — i80x86 (Pentium) 1 Особенности обработки конца прерывания (сигнала EOI): 0 — сброс бита в ISR производит программа обработки прерывания; 1 — установ- ка режима автоматического сброса бита в ISR (после получения сигнала INTA от микропроцессора) 2 0 — данный контроллер ведомый; 1 — данный контроллер ведущий 3 Указывает буферизованность системной шины данных: 0 — системная шина не буферизована; 1 — системная шина буферизована 4 0 — определяет использование специального вложенного режима 5...7 0 Таким образом, приказы инициализации задают контроллеру режимы работы в условиях вложенных прерываний. Если требуется конкретизировать порядок обработки для отдельных уровней прерываний, необходимо использовать спе- циальные операционные управляющие слова — 0CW, назначение и форматы ко- торых мы рассмотрим ниже. 0CW1 — управление регистром масок IMR Этот приказ предназначен для управления регистром масок IMR для маскирова- ния прерываний конкретных уровней. Данный приказ посылается в порт 21 h. Таблица 15.6. Формат приказа 0CW1 Биты 0CW1 Назначение и содержимое 0 0 — разрешить прерывания уровня 0; 1 — запретить прерывание уровня 0 1...6 Для уровней 1...6 — аналогично 7 0 — разрешить прерывания уровня 7; 1 — запретить прерывание уровня 7 0CW2 — управление приоритетом Этот приказ используется для управления приоритетом и учета особенностей завершения обслуживания прерывания в контроллере. Он определяет выпол- нение следующих действий: О сбросить бит в ISR с наибольшим приоритетом; О сбросить бит в ISR для определенного уровня прерываний;
392 Урок 15. Прерывания О установить низший приоритет для определенного уровня; О поменять приоритеты уровней с максимальным и минимальным приорите- тами; О поменять приоритеты уровней с заданным и минимальным приоритетами. Данный приказ посылается в порт 20h. Таблица 15.7. Формат приказа 0CW2 Биты 0CW2 Назначение и содержимое 0...2 000-ппп — код уровня запроса irq для действий, определяемых раз- рядами 5-7 3...4 00 — признак OCW2 5 0 — режим автоматического EOI; 1 — режим неавтоматического EOI 6...7 Задают операцию (возможные сочетания с 5-м битом): ООО — обычный режим приоритетов с автоматическим EOI; 001 — сброс бита с максимальным приоритетом в ISR; 011 — сброс бита в ISR для уровня с кодом ппп; 100 — установка режима циклической смены приоритета при авто- матическом EOI (см. выше); 101 — установка режима циклической смены приоритета при неавто- матическом EOI; 111 — установка режима циклической смены приоритета, но относи- тельно бита ппп 0CW3 — общее управление контроллером Этот приказ служит для общего управления контроллером. Приказ 0CW3 посылается в порт 20h. Таблица 15.8. Формат приказа OCW3 Биты 0CW3 Назначение и содержимое 0...1 10 — прочитать содержимое IRR (следующей командой из порта 020h); И — прочитать содержимое ISR (следующей командой из порта 020h); Кстати, содержимое IMR доступно постоянно как содержимое порта 021h 2 0 или 1 — устанавливает (или не устанавливает) режим опроса, в кото- ром можно непосредственно опрашивать контроллер о поступивших запросах на прерывание, что полезно, если прерывания в микропроцес- соре запрещены (IF = 0), или когда в системе много контроллеров пре- рываний 3...4 01 — признак OCW3
Программирование контроллера прерываний i8259A 393 Биты 0CW3 Назначение и содержимое 5...6 И — установить режим специального маскирования, в котором все немаскированные в IMR запросы будут обрабатываться микропроцессо- ром вне зависимости от состояния ISR; 10 — сбросить режим специального маскирования; 10 или 01 — ничего не менять 7 0 Этой информации уже вполне достаточно для написания и понимания реаль- ных программ. В качестве примера рассмотрим последовательность прика- зов, которые выдает BIOS при загрузке системы после включения питания (табл. 15.9). Это будет хорошей иллюстрацией к вышеприведенным рассужде- ниям. Таблица 15.9. Приказы BIOS для инициализации контроллера прерываний Код приказа Вид приказа и в какой контроллер посылается Порт 00010001 ICW1 в контроллер 1 020h 00001000 ICW2 в контроллер 1 — адрес вектора для 000020h 021h 00000100 ICW3 в контроллер 1 — указывает подсоединение ведомого 021h 00000001 ICW4 в контроллер 1 — режим 180x86 021h 00010001 ICW1 в контроллер 2 OaOh 01110000 ICW2 в контроллер 2 — адрес вектора для OOOlcOh Oalh 00000010 ICW3 в контроллер 2 — указывает индекс ведомого Oalh 00000001 ICW4 в контроллер 2 — режим 180x86 Oalh 10111000 В контроллер 1 — маска прерывания OCW1 02 Ih 10111101 В контроллер 2 — маска прерывания OCW2 Oalh Каскадирование микросхем 18259А Выше мы упоминали, что один контроллер прерываний обрабатывает запросы всего лишь от 8 источников. Компьютеры XT имели всего одну микросхему i8259A, которая обрабатывала прерывания от следующих источников: О уровень 0 — таймера; О уровень 1 — клавиатуры; О уровень 2 — зарезервировано для нестандартных внешних устройств; О уровень 3 — порта COM2; О уровень 4 — порта СОМ Г,
394 Урок 15. Прерывания О уровень 5 — жесткого диска; О уровень 6 — НГМД; О уровень 7 — параллельного порта — принтера. В компьютерах АТ (с микропроцессорами 1286 и выше) в связи с возросшей номенклатурой внешних устройств восьми источников прерываний стало недо- статочно. Нужно было как-то расширить этот диапазон. Здесь и пригодилась способность микросхем 18259А работать в связке, или каскадом. Максимально можно таким образом соединить 8 контроллеров, что позволит обработать за- просы от 64 источников. В архитектуре АТ используются два контроллера, со- единенные каскадом, что позволяет обрабатывать запросы от 15 источников прерываний (табл. 15.10). Один из этих контроллеров является ведущим, дру- гой — ведомым. Выход INT ведомого контроллера замкнут на вход уровня 2 ве- дущего контроллера (рис 15.1). При рассмотрении табл. 15.10 обратите внима- ние на распределение приоритетов. Таблица 15.10. Распределение и приоритеты аппаратных прерываний в архитектуре АТ Уровень (вход) Контроллер Источник прерывания Приоритет уровня irqO Ведущий Таймер 2 irql Ведущий Клавиатура 3 irq2 Ведущий Выход INT ведомого irq8 Ведомый Часы реального времени (CMOS-блок) 4 irq9 Ведомый Вход для устройства расширения 5 irqlO Ведомый Вход для устройства расширения 6 irqll Ведомый Вход для устройства расширения 7 irql 2 Ведомый Вход для устройства расширения 8 irql3 Ведомый Ошибка сопроцессора 9 irql 4 Ведомый Контроллер жесткого диска 10 irql 5 Ведомый Вход для устройства расширения И irq3 Ведущий Вход для устройства расширения (последовательный порт COM2) 12 irq4 Ведущий Вход для устройства расширения (последовательный порт СОМ1) 13 irq5 Ведущий Вход для устройства расширения (параллельный порт LPT2) 14 irq6 Ведущий Контроллер гибкого диска 15 irq7 Ведущий Вход для устройства расширения (параллельный порт LPT1) 16
Реальный режим работы микропроцессора 395 Реальный режим работы микропроцессора Для тех пользователей, которые работали с микропроцессорами 18086 или 18088, нет необходимости пояснять особенности этого режима. Относительно недавно это был единственный режим, в котором функционировала популяр- ная операционная система MS-DOS. Для нее был разработан большой объем программного обеспечения. Понимая все это и не желая терять рынок, фирма Intel во всех модернизациях своего микропроцессора поддерживает этот ре- жим. В нашей книге при написании программ мы, до сего момента, также под- разумевали реальный режим. Вот некоторые его характеристики: О пространство оперативной памяти делится на сегменты по 64 Кбайт. Сег- менты в памяти могут перекрываться; О страничное преобразование адреса запрещено, то есть физический адрес ра- вен линейному и формируется как сумма двух составляющих (см. урок 2): • 16-разрядного эффективного адреса, который, в свою очередь, является суммой трех составляющих: базы, смещения и индекса; • 20-разрядного результата сдвига содержимого конкретного сегментного регистра на 4 разряда влево; О максимальное значение физического адреса равно Off fffh, то есть 1 Мбайт, но, фактически, в реальном режиме микропроцессора адресуется на 64 Кбай- та больше, что следует из следующего вычисления: ffffO максимальное значение сегментной части адреса, сдвинутое на 4 раз- ряда влево; + Offff максимальное значение смещения; lOffef = 1 114 096 байт — максимальный физический адрес в реальном режиме. Этот пример говорит о том, что в модели микропроцессоров, начиная с 1286, при определенных обстоятельствах возможна адресация оперативной памяти за пределами первого мегабайта. Это обстоятельство даже использовалось пос- ледними версиями MS-DOS для размещения служебных программ в этом дополнительном сегменте памяти. Формирование значений адреса сразу за первым мегабайтом возможно и в микропроцессоре 18088/86. В нем при появ- лении физического адреса большего Offfffh, например 1 000 054h, микропро- цессор отбрасывает 21-й единичный бит. Происходит так называемое «завора- чивание» адреса, поэтому сформированный физический адрес на шине адреса будет равен 00054h. Для того чтобы обеспечить полную эмуляцию данной осо- бенности микропроцессора 18088/86, в моделях микропроцессоров, начиная с i80286, была предусмотрена возможность блокировки адресной линии А20 (управление тем самым 21-м битом адреса). Для обеспечения доступа к адре-
396 Урок 15. Прерывания сам оперативной памяти, лежащим за пределами первого мегабайта, необходимо специальным образом открывать эту адресную линию; О в реальном режиме схема распределения оперативной памяти — фиксирован- ная. Перечислим расположение некоторых из системных областей, которые потребуются нам в дальнейшем: • в диапазоне адресов OOOOOh—003ffh (первый мегабайт оперативной памя- ти) находится таблица векторов прерываний (ТВП). Она содержит 256 векторов прерываний размером 4 байта (указателей на программы обра- ботки прерываний); • в диапазоне адресов 00400h—006ffh сразу за таблицей векторов прерыва- ний располагается область памяти, содержащая жестко структурирован- ные данные, обеспечивающие работу BIOS и MS-DOS; • с адреса 0b8000h располагается область видеопамяти, в которой форми- руется изображение, которое мы видим на экране. Обработка прерываний в реальном режиме Обработка прерываний (как внешних, так и внутренних) в реальном режиме микропроцессора производится в три этапа: 1) прекращение выполнения текущей программы; 2) переход к выполнению и выполнение программы обработки прерываний; 3) возврат управления прерванной программе. Первый этап должен обеспечить временное прекращение выполнения текущей программы таким образом, чтобы потом прерванная программа продолжила свою работу так, как будто никакого прерывания не было. Любая программа, загруженная для выполнения операционной системой, занимает свое, отдель- ное от других программ, место в оперативной памяти. Разделяемыми между программами ресурсами являются регистры микропроцессора, в том числе ре- гистр флагов, поэтому их содержимое нужно сохранять. Обязательными для со- хранения являются регистры cs, ip и flags\eflags, поэтому они при возникнове- нии прерывания сохраняются микропроцессором автоматически. Пара cs:ip содержит адрес команды, с которой необходимо начать выполнение после возврата из программы обслуживания прерывания, a flags\eflags — состояние флагов после выполнения последней команды прерванной программы в момент передачи управления программе обработки прерывания. Сохранение содержи- мого остальных регистров должно обеспечиваться программистом в начале про- граммы обработки прерывания до их использования. Наиболее удобным местом хранения регистров является стек. В конце первого этапа микропроцессор пос- ле включения в стек регистров flags, cs и ip сбрасывает бит флага прерываний IF в регистре flags (но при этом в стек записывается предыдущее содержимое регистра flags с еще установленным IF). Тем самым предотвращаются возмож-
Реальный режим работы микропроцессора 397 ность возникновения вложенных прерываний по входу INTR и порча регистров исходной программы вследствие неконтролируемых действий со стороны про- граммы обработки вложенного прерывания. После того как необходимые дей- ствия по сохранению контекста завершены, обработчик аппаратного прерывания может разрешить вложенные прерывания командой sti. Набор действий по реализации второго этапа заключается в определении источ- ника прерывания и вызова соответствующей программы обработки. В реальном режиме микропроцессора допускается от 0 до 255 источников прерываний. Количество источников прерываний ограничено размером таблицы векторов прерываний. Эта таблица выступает связующим звеном между источником прерывания и процедурой обработки. Данная таблица располагается в памяти, начиная с адреса 0. Каждый элемент таблицы векторов прерываний занимает 4 байта и имеет следующую структуру: О 1-е слово элемента таблицы — значение смещения начала процедуры обра- ботки прерывания (и) от начала кодового сегмента; О 2-е слово элемента таблицы — значение базового адреса сегмента, в котором находится процедура обработки прерывания. Определить адрес, по которому находится вектор прерывания с номером и, можно следующим образом: смещение_элемента_таблицы_векторов_прерываний = п * 4 Таким образом, полный размер таблицы векторов прерываний 4 * 256 - s 1024 байт. Теперь понятно, что на втором этапе обработки прерывания микропроцессор выполняет следующие действия: 1. По номеру источника прерывания путем умножения на 4 определяет смеще- ние в таблице векторов прерываний. 2. Помещает первые два байта по вычисленному адресу в регистр ip. 3. Помещает вторые два байта по вычисленному адресу в регистр cs. 4. Передает управление по адресу, определяемому парой cs: ip. Далее выполняется сама программа обработки прерывания. Она, в свою оче- редь, также может быть прервана, например, поступлением запроса от более приоритетного источника. В этом случае этапы 1 и 2 будут повторены для вновь поступившего запроса. Набор действий по реализации этапа 3 заключается в восстановлении контек- ста прерванной программы. Так же, как и на этапе 1, на данном последнем эта- пе есть действия, выполняемые микропроцессором автоматически, и действия, задаваемые программистом. Основная задача на этапе 3 — привести стек в со- стояние, в котором он был сразу после передачи управления данной процедуре. Для этого программист указывает необходимые действия по восстановлению регистров и очистке стека. Этот участок кода необходимо защитить от возмож-
398 Урок 15. Прерывания ности искажения содержимого регистров (в результате появления аппаратного прерывания) с помощью команды cli. Последние команды в процедуре обра- ботки прерывания — sti и iret, при обработке которых микропроцессор вы- полняет следующие действия: 1) sti — разрешить аппаратные прерывания по входу INTR; 2) iret — извлечь последовательно три слова из стека и поместить их, соот- ветственно, в регистры ip, cs и flags. В результате этапа 3 управление возвращается очередной команде прерванной программы, которая должна была выполниться, если бы прерывания не было. Аппаратные прерывания могут быть инициированы программно командой микропроцессора int п, где п — номер аппаратного прерывания в соответствии с таблицей векторов прерываний. При этом микропроцессор также сбрасывает флаг IF, но не вырабатывает сигнал INTA. После такого обстоятельного обсуждения нам осталось рассмотреть хороший пример. Он должен показать нам ключевые моменты программирования про- граммных и аппаратных прерываний. Выберем одно программное и одно аппа- ратное прерывание. Наиболее «частое» аппаратное прерывание — прерывание от таймера. Что касается программного прерывания, то основное требование при его выборе для нашего эксперимента то, чтобы его номер не совпадал с номером какого-нибудь системного прерывания. Программа, которую мы должны будем разработать, выполняет следующие действия: подключает новый аппаратный обработчик прерываний от таймера 08h, который на каждый 4-й сигнал в цикле выводит на экран символы (0-9). Пользовательское прерывание (новый обработчик прерывания Offh) вызывает- ся после запуска прерывания от таймера. Его работа заключается в выдаче сиг- нала сирены циклически несколько раз. После этого пользовательское преры- вание производит восстановление вектора старого обработчика прерывания от таймера и завершает свою работу вместе со всей программой. Перед обсуждением программы нужно сделать следующее замечание. По сути, мы ставим себе цель дополнить программу обработки прерывания от таймера некоторым новым свойством. Одновременно, мы не хотим портить старый об- работчик этого прерывания. Такая ситуация встречается довольно часто. Су- ществуют различные способы сцепления системных обработчиков прерываний с пользовательскими. Прерывание от таймера 08h интересно тем, что програм- ма его обработки предусматривает возможность того, что пользователь захочет вставить в обработчик свой код. С этой целью из системной программы обра- ботки прерывания 08h делается вызов еще одного прерывания с номером Ich. Это пустое прерывание, обработчик которого содержит всего одну команду iret. Таким образом, пользователь имеет возможность решить проблему сцеп- ления своего обработчика с системным обработчиком прерывания 08h косвен- но — попросту заменив вектор обработчика прерывания Ich. Этот прием реа- лизован в листинге 15.1.
Реальный режим работы микропроцессора 399 Листинг 15.1. Обработка прерывания от таймера <1> ; prg15_1.asm <2> MASM <3> MODEL small ; модель памяти <4> STACK 256 ; размер стека <5> . 486р <6> delay macro time <7> local ext,iter <8> ; макрос задержки <9> ; На входе - значение переменной задержки (в мкс) <10> push сх <n> mov сх,time <12> ext: <13> push сх <14> ; в сх одна мкс, это значение можно <15> ; поменять в зависимости от производительности процессора <16> mov сх,5000 <17> iter: <18> loop iter <19> pop сх <20> loop ext <21> pop сх <22> endm ; конец макроса <23> .data <24> tonelow dw 2651 ; нижняя граница звучания 450 Гц <25> ent db 0 ; счётчик для выхода из программы <26> temp dw ? ; верхняя граница звучания <27> old_off8 dw 0 ;для хранения старых значений вектора <28> old_seg8 dw 0 ; сегмент и смещение <29> time_1ch dw 0 ; переменная для пересчета <30> .code ;начало сегмента кода <31> off_1ch equ 1ch*4 ; смещение вектора 1ch в ТВП <32> off_Offh equ 0ffh*4; смещение вектора ffh в ТВП <33> char db "О" ; символ для вывода на экран <34> maskf db 07h ; маска вывода символов на экран <35> position dw 2000 ; позиция на экране - почти центр <36> main proc <37> mov ax,©data <38> mov ds, ax продолжение
400 Урок 15. Прерывания 39> хог ax, ax 40> сП ; запрет аппаратных прерываний на время 41> ;замены векторов прерываний 42> ; замена старого вектора Ich на адрес new_1ch 43> ; настройка es на начало таблицы векторов 44> ;прерываний - в реальном режиме: 45> mov ax,0 46> mov es.ax 47> ; сохранить старый вектор 48> mov ax.es:[off_1ch] смещение старого вектора Ich в ах 49> mov old_off8,ax ; сохранение смещения в old_off8 50> mov ax.es:[off_1ch+2] сегмент старого вектора Ich в ах :51> mov old_seg8,ax сохранение сегмента в old_seg8 52> ; записать новый вектор в таблицу векторов прерываний 53> mov ax.offset new_1ch смещение нового обработчика в ах 54> mov es:off_1ch,ax 55> push cs 5б> pop ax ; настройка ах на cs 57> mov es:off_1ch+2,ax ;запись сегмента 58> ; инициализировать вектор пользовательского прерывания Offh 59> mov ax,offset new_Offh :60> mov es:off_Offh,ax ;прерывание Offh :61> push cs :62> pop ax :63> mov es:off_0ffh+2,ax :64> sti ;разрешение аппаратных прерываний :65> ;задержка, чтобы новый обработчик таймера вывел символы на экран :бб> delay 3500 :67> ; завершение программы :68> int Offh :69> exit: :70> mov ax,4c00h :71> int 21 h :72> main endp :73> new_1ch proc ; новый обработчик прерывания от таймера :74> сохранение в стеке используемых регистров :75> push ax :7б> push bx :77> push es :78> push ds
Реальный режим работы микропроцессора 401 <79> ; настройка ds на cs <80> push cs <81> POP ds <82> ; запись в es адреса начала видеопамяти - В800:0000 <83> mov ax,0b800h <84> mov es,ax <85> mov al,char ;символ в al <8б> mov ah.maskf ; маску вывода - в ah <87> mov bx,position ; позицию на экране - в Ьх <88> mov es:[bx],ax ; вывод символа в центр экрана <89> add bx,2 ; увеличение позиции <90> mov position,bx ; сохранение новой позиции <91> inc char ; следующий символ <92> восстановление используемых регистров: <93> pop ds <94> pop es <95> pop bx <9б> pop ax <97> iret ; возврат из прерывания <98> new_1ch endp ; конец обработчика <99> new_0ffh proc ;новый обработчик пользовательского прерывания <100> sirena: <101> ^охранение в стеке используемых регистров <102> push ax <103> push bx <104> ; проверка для пересчета на 4: <105> test time_1ch,03h <106> jnz leave_it ;если два правых бита не 11, то на выход, ;иначе: <107> go: <108> mov ax,0B06h ;заносим слово состояния 110110110b <109> ;(0B6h) - выбираем второй канал порта 43h (динамик) <110> out 43h,ах ;в порт 43h <111> in al,61h ;получим значение порта 61h в al <112> or al, 3 инициализируем динамик - подаем ток <113> out 61h,al ; в порт 61h <114> mov ex,2083 количество шагов <115> musicup: <116> ;значение нижней границы частоты в ах (1193000/2651=450 Гц), <117> ;где 1193000 - частота динамика продолжение &
402 Урок 15. Прерывания <118> mov ax.tonelow <119> out 42h,al;в порт 42h - младшее слово ax: al <120> mov al, ah ; обмен между al и ah <121> out 42h,al;в порте 42h уже старшее слово ах:ah <122> add tonelow, 1 ;увеличение частоты <123> delay 1 ;задержка на 1 мкс <124> mov dx, tonelow ; текущее значение частоты - в dx <125> mov temp.dx ;в temp - верхнее значение частоты <12б> loop musicup ; повторить цикл повышения <127> mov ex,2083 <128> musicdown: <129> mov ax,temp ; верхнее значение частоты - в ах <130> out 42h, al; младший байт ax: al в порт 42h <131> mov al, ah ; обмен между al и ah <132> out 42h,al;старший байт ax:ah в порт 42h <133> sub temp, 1; уменьшение частоты <134> delay 1 ;задержка на 1 мкс <135> loop musicdown ; повторить цикл понижения <13б> nosound: <137> in al, 61h ; значение порта 61h - в al <138> ; слово состояния Ofch - выключение динамика и таймера <139> and al,Ofch <140> out 61h,al;в порт 61h <141> mov dx,2651 ;для последующих циклов <142> mov tonelow, dx <143> inc ent ;инкремент количества проходов <144> cmp ent, 2 ;если сирена не звучала двух ;раз - повторный запуск <145> jne go <14б> leave.it: ;выход <147> inc time_1ch ;пересчет на 4 <148> [восстановление используемых регистров <149> pop bx <150> pop ax <151> ;восстановление вектора прерывания от таймера <152> cli ;запрет аппаратных прерываний <153> хог ax, ax •.снова настройка es на начало таблицы <154> mov es, ax ;векторов прерываний <155> mov ax,old_off8 ;записо в таблицу смещения старого <156> mov es:off_1ch,ax ;обработчика прерывания от таймера
Реальный режим работы микропроцессора 403 <157> mov ax,old_seg8 ;запись сегмента <158> mov es:off_1ch+2,ax <159> sti ; разрешение аппаратных прерываний <160> iret ; возврат из прерывания <161> new_Offh endp ; конец обработчика <1б2> end main ;конец программы Обсудим листинг 15.1. Основная процедура main (строки 36-72) выполняет ини- циализацию используемых векторов прерываний. При этом необходимо запом- нить содержимое старого вектора прерывания Ich (строки 45-51), так как его придется восстанавливать перед завершением программы. Содержимое вектора пользовательского прерывания Offh сохранять нет смысла, так как его номер выб- ран исходя из того, что он не используется при работе системы. При смене век- тора прерывания Ich необходимо запретить обработку аппаратных прерываний командой cli (строка 40), так как внешние прерывания являются асинхронными и могут прийти в самый неподходящий момент, в том числе и во время смены содержимого вектора. Перед завершением работы аппаратного прерывания необ- ходимо явно выдать сигнал E0I. Но в нашем случае это делать необязательно, так как за нас это сделает системный обработчик прерывания 08h, из которого вызы- вается обработчик для прерывания Ich. В строках 52-57 и 58-63 производится запись новых значений векторов Ich и Offh в таблицу векторов прерываний. После того как в строке 64 командой sti будут разрешены аппаратные прерыва- ния, на экран будут выведены символы. Эти действия выполняет новая програм- ма обработки прерывания для вектора Ich (строки 73-98). Эти символы будут выводиться до тех пор, пока действует программная задержка, которую мы орга- низовали в строке 66. После этого вызывается пользовательское прерывание Offh, программа обработки которого (строки 99-161) отрабатывает несколько циклов генерации сигнала «сирена» (мы обсуждали эту программу на уроке 7, и теперь вы уже в состоянии оформить ее в виде макроса или процедуры). Вызов программы обработки прерывания пользователя new_Off h осуществляется с помо- щью специальной команды int. Эта команда предназначена для того, чтобы пользователь сам мог инициировать вызов прерываний. Как видите, эти преры- вания являются планируемыми (синхронными), так как пользователь сам опре- деляет момент его вызова. После написания этой программы можно провести несколько экспериментов для исследования работы контроллера прерываний и системы прерываний в целом. К примеру, можно выполнить следующие операции: О Изменить базовый адрес ведущего контроллера прерываний. Как мы обсуж- дали выше, BIOS инициализирует ведущий контроллер таким образом, что он имеет базовый адрес 08h. Попробуйте теперь изменить значение базового вектора, например, на значение OfOh. Для этого необходимо выполнить ини- циализацию контроллера, которая заключается в последовательной посылке в него управляющих слов. Посмотрите последовательность приказов, кото-
404 Урок 15. Прерывания рые BIOS посылает в контроллер прерываний для его инициализации при загрузке системы (см. табл. 15.9). Нам тоже нужно будет их сформировать, но с нужными нам значениями, и послать в контроллер прерываний. Фраг- мент, осуществляющий такие действия, может выглядеть следующим обра- зом: mov al,00010001b out 20h,al ;ICW1 в порт 20h jmp $+2 jmp $+2 ;задержка, чтобы успела ;отработать аппаратура mov al,OfOh out 21h,al ;ICW2 в порт 20h - новый базовый номер jmp $+2 jmp $+2 ;задержка, чтобы успела ;отработать аппаратура mov al,00000100b out 21h,al ;ICW3 - ведомый подключается ;к уровню 2 (см. рис. 15.1) jmp $+2 jmp $+2 ;задержка, чтобы успела ;отработать аппаратура mov al,00000001b out 21h,al ;ICW4 - E0I выдает программа пользователя Данный фрагмент нужно вставить в начало процедуры main листинга 15.1 после команды cli. После этого вектору прерывания от таймера будет соот- ветствовать значение OfOh. Соответственно, если вы хотите, чтобы програм- ма листинга 15.1 работала как прежде, вам нужно настроить вектор OfOh на системную программу обработки прерывания 08h. Техника такой замены аналогична приведенной в листинге 15.1. После этого можно разрешить прерывания командой st 1. Но правильно работать будет только прерывание от таймера, все остальные прерывания (например, от клавиатуры) будут приводить к зависанию компьютера. Если вы подобным образом перепрог- раммировали контроллер, то перед завершением программы нужно провес- ти обратное перепрограммирование, чтобы вернуть старое значение базово- го адреса. Если этого не сделать, то работа системы будет нарушена — все аппаратные прерывания будут попадать «не туда».
Реальный режим работы микропроцессора 405 О Рассмотреть альтернативу команде cli, замаскировав аппаратные прерывания, используя прямое программирование регистра масок IMR: ; запретить прерывания mov al,Offh out 21И,а1;для ведущего контроллера out А1И,а1;для ведомого контроллера ;разрешить прерывания mov al.OOh out 21h,al; для ведущего контроллера out A1h,al; для ведомого контроллера Попробуйте использовать эти команды в листинге 15.1 вместо команд cli и sti. О Запретить аппаратные прерывания определенных уровней. Например, в сле- дующем фрагменте запрещаются прерывания от клавиатуры (см. табл. 15.6): in al,21h or al,00000010b out 21h,al О Исследовать, как меняется содержимое регистров IRR, IMR и ISR в ходе обра- ботки аппаратного прерывания, читая состояние описанных выше портов. Если у вас проснулся интерес к подобной исследовательской деятельности, то предлагаю вам самостоятельно написать эти фрагменты программ и ис- следовать их с использованием листинга 15.1. Подведем некоторые итоги: 0 Система прерываний микропроцессора Intel реализована весьма удачно. Ее применение позволяет достаточно гибко принимать и обрабатывать преры- вания от различных источников. 0 Источники прерываний делятся на внешние и внутренние. Количество внеш- них источников ограничено числом выводов микросхемы 18259А и не может превышать 15. К этому количеству нужно добавить еще одно прерывание — немаскируемое. Его инициируют источники, требующие безотлагательного вмешательства со стороны микропроцессора. Остальные источники преры- ваний являются внутренними. Общее количество источников прерываний в микропроцессоре не превышает 256. Внутренние источники прерываний также делятся на две группы: программные прерывания и исключения.
406 Урок 15. Прерывания 0 Любое из этих прерываний можно вызвать как стандартными для этого вида прерывания средствами, так и командой int хх. 0 Каждое прерывание связано с программой его обработки посредством таб- лицы векторов прерываний, которая в реальном режиме работы микропро- цессора находится в первом килобайте оперативной памяти. 0 Механизм обработки аппаратных прерываний основан на использовании микросхемы i8259A, которая позволяет организовать гибкую обработку пре- рываний. 0 Микросхема i8259A является программируемой, что позволяет выполнить такие операции, как задание различных дисциплин обслуживания прерыва- ний, запрещения отдельных прерываний и т. п. 0 Программирование микросхемы i8259A осуществляется специальными пос- ледовательностями управляющих и операционных слов.
урок Защищенный режим работы микропроцессора □ Характеристика защищенного режима работы микропроцессоров Intel □ Системные регистры микропроцессора □ Сегментные регистры и структуры данных защищенного режима □ Организация памяти в защищенном режиме □ Перевод микропроцессора в защищенный режим работы □ Основы программирования микропроцессора в защищенном режиме
На данном уроке мы рассмотрим защищенный режим работы микропроцессора. Впервые защищенный режим появился в микропроцессоре i80286 фирмы Intel. Именно этот режим позволяет полностью использовать все возможности, пре- доставляемые микропроцессором. Все современные многозадачные операцион- ные системы работают только в этом режиме. Такие операционные системы сей- час стали стандартом. Поэтому так важно для понимания всех процессов, происходящих в компьютере во время работы многозадачной операционной сис- темы, знать основы функционирования микропроцессора в защищенном режиме. Любой современный микропроцессор, находясь в реальном режиме, очень мало отличается от старого доброго i8086. Это лишь его более быстрый аналог с уве- личенным (до 32 бит) размером всех регистров, кроме сегментных. Чтобы по- лучить доступ ко всем остальным архитектурным и функциональным новшест- вам микропроцессора, необходимо перейти в защищенный режим. Если бы мы могли проникнуть внутрь компьютера после перехода в защищенный ре- жим, то увидели бы, что микропроцессор совершенно преобразился. Прежде всего, это стало бы заметно в изменении принципов работы микропроцессора с памятью. Она по-прежнему является сегментированной, но изменяются функ- ции и номенклатура программно-аппаратных компонентов, участвующих в сег- ментации. Вспомните, что в реальном режиме работы микропроцессора сегмент был длиной не более 64 Кбайт, а адрес области памяти сегмента располагался в одном из сегментных регистров. Функциональное назначение сегмента опреде- лялось тем, в каком из шести сегментных регистров находился его адрес. Аппа- ратные средства контроля доступа к сегменту отсутствовали. Если и можно было организовать такой контроль, то только со стороны операционной систе- мы. Реальный режим поддерживал выполнение всего одной программы. Для этого достаточно было простых механизмов распределения оперативной памя- ти и не было потребности в организации защиты программ от взаимного вли- яния и т. д. Поэтому все, что нужно было знать программе, — это адреса, по которым располагаются ее сегменты кода, данных и стека. Если же мы вдруг захотели бы поместить в одну программно-аппаратную среду несколько неза- висимых программ, то автоматически встал бы вопрос об их защите от взаим- ного влияния. Для решения этой проблемы микропроцессору уже недостаточ- но, используя сегментные регистры, знать, где располагаются сегменты программ. Для обеспечения совместной работы нескольких задач необходимо защитить их от взаимного влияния, а если возникает потребность во взаимо- действии между ними, то оно должно обязательно регулироваться.
409 Чтобы ввести такое регулирование, нужно иметь больше информации о самих задачах. Можно предложить несколько вариантов структурной организации и размещения такой информации. Фирма Intel не стала нарушать принцип сег- ментации. Так как каждая задача в системе занимает один или несколько сег- ментов в памяти, то логично иметь больше информации о них, как об объектах, реально существующих в данный момент в системе. Если каждому из этих объектов присвоить определенные атрибуты, то часть контроля за доступом к ним можно переложить на сам микропроцессор. Что и было сделано. Любой сегмент памяти в защищенном режиме имеет следующие атрибуты: О расположение сегмента в памяти; О размер сегмента; О уровень привилегий — определяет права данного сегмента относительно других сегментов; О тип доступа — определяет назначение сегмента; О некоторые другие. Состав перечисленных атрибутов показывает, что в защищенном режиме мик- ропроцессор поддерживает два типа защиты — по привилегиям и доступу к памяти. В отличие от реального режима в защищенном режиме программа уже не может запросто обратиться по любому физическому адресу памяти. Для этого она должна иметь определенные полномочия и удовлетворять ряду тре- бований. Ключевым объектом защищенного режима является специальная структура — дескриптор сегмента, который представляет собой 8-байтовый дескриптор (крат- кое описание) непрерывной области памяти, содержащий перечисленные выше атрибуты. Любая область памяти, которая логически может являться сегмен- том данных, стека или кода, должна быть описана таким дескриптором. Все дескрипторы собираются вместе в одну из трех дескрипторных таблиц. В ка- кую именно таблицу должен быть помещен дескриптор, определяется его на- значением. Адрес, по которому размещаются эти дескрипторные таблицы, мо- жет быть любым; он хранится в специально предназначенном для этого адреса системном регистре. Это — ключевые моменты. Далее мы подробно рассмотрим архитектуру мик- ропроцессора в защищенном режиме и основные правила программирования этого режима. На уроке 2 было отмечено, что программная модель микропроцессора содер- жит 32 доступных пользователю регистра, которые делятся на две большие группы: пользовательские и системные. К настоящему моменту вы уже доста- точно хорошо освоили работу с пользовательскими регистрами. Настало время разобраться с системными.
410 Урок 16. Защищенный режим работы микропроцессора Системные регистры микропроцессора Само название этих регистров говорит о том, что они выполняют специфичес- кие функции в системе. Использование этих регистров жестко регламентирова- но. Именно они обеспечивают работу защищенного режима. Их также можно рассматривать как часть архитектуры микропроцессора, которая намеренно ос- тавлена видимой для того, чтобы квалифицированный системный программист мог выполнить самые низкоуровневые операции. Системные регистры можно разделить на три группы: О четыре регистра управления; О четыре регистра системных адресов; О восемь регистров отладки. Регистры управления В группу регистров управления входят 4 регистра: crO, сг1, сг2, сгЗ. Эти регис- тры предназначены для общего управления системой. Регистры управления доступны только программам с уровнем привилегий 0. Хотя микропроцессор имеет четыре регистра управления, доступными являются только три из них; исключается сг1, функции которого пока не определены (он зарезервирован для будущего использования). Регистр сгО содержит системные флаги, управляющие режимами работы мик- ропроцессора и отражающие его состояние глобально, независимо от конкрет- ных выполняющихся задач. Назначение системных флагов: О ре (Protect Enable), бит 0 — разрешение защищенного режима работы. Со- стояние этого флага показывает, в каком из двух режимов — реальном (ре - 0) или защищенном (ре - 1) — работает микропроцессор в данный момент времени. Запомните этот флаг, мы к нему еще вернемся; О mp (Math Present), бит 1 — наличие сопроцессора. Всегда 1. О ts (Task Switched), бит 3 — переключение задач. Процессор автоматически устанавливает этот бит при переключении на выполнение другой задачи; О am (Alignment Mask), бит 18 — маска выравнивания. Этот бит разрешает (ат =1) или запрещает (ат - 0) контроль выравнивания; О cd (Cache Disable), бит 30 — запрещение кэш-памяти. С помощью этого бита можно запретить (cd = 1) или разрешить (cd = 0) использование внутренней кэш-памяти (кэш-памяти первого уровня); О pg (PaGing), бит 31 — разрешение (pg =1) или запрещение (pg я 0) стра- ничного преобразования. Регистр сгО используется при страничной модели организации памяти.
Системные регистры микропроцессора 411 Регистр сг2 используется при страничной организации оперативной памяти для регистрации ситуации, когда текущая команда обратилась по адресу, со- держащемуся в странице памяти, отсутствующей в данный момент времени в памяти. В такой ситуации в микропроцессоре возникает исключительная ситу- ация с номером 14, и линейный 32-битный адрес команды, вызвавшей это ис- ключение, записывается в регистр с г 2. Имея эту информацию, обработчик ис- ключения 14 определяет нужную страницу, осуществляет ее подкачку в память и возобновляет нормальную работу программы. Регистр сгЗ также используется при страничной организации памяти. Это так называемый регистр каталога страниц первого уровня. Он содержит 20-бит- ный физический базовый адрес каталога страниц текущей задачи. Этот каталог содержит 1024 32-битных дескриптора, каждый из которых содержит адрес таблицы страниц второго уровня. В свою очередь, каждая из таблиц страниц второго уровня содержит 1024 32-битных дескриптора, адресующих странич- ные кадры в памяти. Размер страничного кадра — 4 Кбайт. Регистры системных адресов Эти регистры еще называют регистрами управления памятью. Они предназна- чены для защиты программ и данных в мультизадачном режиме работы микро- процессора. При работе в защищенном режиме микропроцессора адресное про- странство делится: О на глобальное — общее для всех задач; О локальное — отдельное для каждой задачи. Этим разделением и объясняется то, что в архитектуре микропроцессора при- сутствуют следующие системные регистры: О регистр таблицы глобальных дескрипторов gdtr (Global Descriptor Table Register) имеет размер 48 бит и содержит 32-битовый (биты 16—47) базо- вый адрес глобальной дескрипторной таблицы GDT и 16-битовое (биты 0- 15) значение предела, представляющее собой размер в байтах таблицы GDT; О регистр таблицы локальных дескрипторов Idtr (Local Descriptor Table Register) имеет размер 16 бит и содержит так называемый селектор дес- криптора локальной дескрипторной таблицы LDT. Этот селектор является указателем в таблице GDT, который и описывает сегмент, содержащий ло- кальную дескрипторную таблицу LDT. О регистр таблицы дескрипторов прерываний idtr (Interrupt Descriptor Table Register) имеет размер 48 бит и содержит 32-битовый (биты 16—47) базо- вый адрес дескрипторной таблицы прерываний IDT и 16-битовое (биты 0- 15) значение предела, представляющее собой размер в байтах таблицы IDT; О 16-битовый регистр задачи tr (Task Register) подобно регистру Idtr содер- жит селектор, то есть указатель на дескриптор в таблице GDT. Этот дес- криптор описывает текущий сегмент состояния задачи (TSS — Task Segment
412 Урок 16. Защищенный режим работы микропроцессора Status). Этот сегмент создается для каждой задачи в системе, имеет жестко регламентированную структуру и содержит контекст (текущее состояние) задачи. Основное назначение сегментов TSS — сохранять текущее состояние задачи в момент переключения на другую задачу. Регистры отладки Это очень интересная группа регистров, предназначенных для аппаратной от- ладки. Средства аппаратной отладки впервые появились в микропроцессоре i486. Аппаратно микропроцессор содержит восемь регистров отладки, но реаль- но из них используются только 6. Регистры drO, dr1, dr2, dr3 имеют разрядность 32 бит и предназначены для за- дания линейных адресов четырех точек прерывания. Используемый при этом механизм следующий: любой формируемый текущей программой адрес срав- нивается с адресами в регистрах drO...dr3, и при совпадении генерируется ис- ключение отладки с номером 1; Регистр d гб называется регистром состояния отладки. Биты этого регистра ус- танавливаются в соответствии с причинами, которые вызвали возникновение последнего исключения с номером 1. Перечислим эти биты и их назначение: О ЬО — если этот бит установлен в 1, то последнее исключение (прерывание) возникло в результате достижения контрольной точки, определенной в ре- гистре drO; О Ы — аналогично ЬО, но для контрольной точки в регистре d г1; О Ь2 — аналогично ЬО, но для контрольной точки в регистре d г2; О ЬЗ — аналогично ЬО, но для контрольной точки в регистре d гЗ; О bd (бит 13) служит для защиты регистров отладки; О bs (бит 14) устанавливается в 1, если исключение 1 было вызвано состояни- ем флага tf = 1 в регистре eflags; О bt (бит 15) устанавливается в 1, если исключение 1 было вызвано переклю- чением на задачу с установленным битом ловушки в TSS t = 1. Все остальные биты в этом регистре заполняются нулями. Обработчик исклю- чения 1 по содержимому dr6 должен определить причину, по которой про- изошло исключение, и выполнить необходимые действия. Регистр dr7 называется регистром управления отладкой. В нем для каждого из четырех регистров контрольных точек отладки имеются поля, с помощью кото- рых можно уточнить следующие условия, при которых следует сгенерировать прерывание: О место регистрации контрольной точки — только в текущей задаче или в любой задаче. Соответствующие биты занимают младшие восемь бит регис- тра d г 7 (по два бита на каждую контрольную точку (фактически, точку пре-
Структуры данных защищенного режима 413 рывания), задаваемую регистрами d rO, dr1, dr2, dr3). Первый бит из каждой пары — это так называемое локальное разрешение, его установка говорит о том, что точка прерывания действует, если она находится в пределах адрес- ного пространства текущей задачи. Второй бит в каждой паре определяет глобальное разрешение, которое говорит о том, что данная контрольная точ- ка действует в пределах адресных пространств всех задач, находящихся в системе; О тип доступа, по которому инициируется прерывание: только при выборке команды, при записи или при записи/чтении данных. Биты, определяющие природу возникновения прерывания, локализуются в старшей части данно- го регистра. Большинство из системных регистров программно доступны. Не все из них понадобятся в нашем дальнейшем изложении, но, тем не менее, мы коротко рассмотрим их с тем, чтобы возбудить у читателя интерес к дальнейшему ис- следованию архитектуры микропроцессора. Структуры данных защищенного режима Мы уже упоминали о том, что в защищенном режиме любой запрос к памяти как со стороны операционной системы, так и со стороны прикладных про- грамм должен быть санкционирован. Микропроцессор аппаратно контролирует доступ программ к любому адресу в оперативной памяти. Для получения дос- тупа целевой адрес, к которому хочет получить доступ программа, должен быть описан для программы. Это означает, что участок физической памяти, содер- жащий нужный адрес, должен быть описан с помощью некоторого дескриптора сегмента, который помещается в одну из трех дескрипторных таблиц. Локали- зация этих таблиц осуществляется с использованием одного из рассмотренных нами системных регистров — gdtr, Idt г или idt г. Программе, которая желает использовать данный участок памяти, должен быть сообщен указатель на соот- ветствующий дескриптор в одной из двух дескрипторных таблиц — GDT или LDT. Что касается таблицы IDT, то работа с ней осуществляется по несколько иному принципу, поэтому о ней мы поговорим ниже. Указатель на дескриптор сегмента в одной из таблиц GDT или LDT, в зависимости от функционального назначения описываемого дескриптором участка памяти (сегмента), помещает- ся в один из шести сегментных регистров. Таким образом, в защищенном ре- жиме меняется роль сегментного регистра — теперь он содержит уже не адрес, а селектор или индекс в таблице дескрипторов сегментов. Но само назначение сегментных регистров не меняется — они по-прежнему указывают на сегменты команд, данных и стека, но делают это, используя принципиально иные меха- низмы. Структурно дескриптор сегмента представляет собой 8-байтовую структуру, изображенную на рис. 16.1.
414 Урок 16. Защищенный режим работы микропроцессора Тип сегмента Р DPL S 1 C/ED R/W А 7 65 4 3 2 1 0 ,base 3, Размер сегмента (16...19) ,base 2t Базовый адрес сегмента (31...24) Байт AR Базовый адрес сегмента (23... 16) 63 55 Базовый адрес сегмента (0...15) base_1 Размер сегмента (0...15) limit_1 31 Рис. 16.1. Структура дескриптора сегмента защищенного режима микропроцессора Поля в дескрипторе описаны в табл. 16.1. Из табл. 16.1 видно, что в защищенном режиме размер сегмента не фиксиро- ван, его расположение можно задать в пределах 4 Гбайт. Если посмотреть на рис. 16.1, то возникнет вопрос: почему разорваны поля, определяющие размер сегмента и его начальный (базовый) адрес? Это результат эволюции микро- процессоров. Мы упоминали о том, что защищенный режим впервые появился в микропроцессоре i80286. Этот микропроцессор имел 24-разрядную адресную шину и, соответственно, мог адресовать в защищенном режиме до 16 Мбайт оперативной памяти. Для этого ему достаточно было иметь в дескрипторе поле базового адреса 24 бита и поле размера сегмента 16 бит. После появления микропроцессора i80386 с 32-разрядной шиной команд и данных в целях сов- местимости программ разработчики не стали изменять формат дескриптора, а просто использовали свободные поля. Внутри микропроцессора эти поля объе- динены. Внешне же они остались разделены, и при программировании с этим приходится мириться. Следующий интересный момент связан с тем, что размер сегмента в защищен- ном режиме может достигать 4 Гбайт, то есть занимать все возможное физи- ческое пространство памяти. Как это возможно, если суммарный размер поля размера сегмента всего 20 бит, что соответствует величине 1 Мбайт? Секрет скрыт в поле гранулярности — бит G (см. рис. 16.1 и табл. 16.1). Если бит G = 0, то значение в поле размера сегмента означает размер сегмента в байтах, а если G = 1, то в страницах. Размер страницы составляет 4 Кбайт. Нетрудно под- считать, что когда максимальное значение поля размера сегмента составляет Offfffh, то это соответствует 1 М страниц, что и соответствует величине 1 М * 4 Кб = 4 Гб. Из вышесказанного понятно, что выведение информации о базовом адресе сег- мента и его размере на уровень микропроцессора позволяет аппаратно контро- лировать работу программ с памятью и предотвращать обращения по несущест- вующим адресам либо по адресам, находящимся вне предела, разрешенного полем размера сегмента limit.
Структуры данных защищенного режима 415 Таблица 16.1. Назначение полей дескриптора сегмента Номер байта Количество Символическое Назначение и содержание полей в дескрипторе бит в поле обозначение дескриптора 0...1 16 limit_l Младшие биты 0...15 20-разрядного поля предела сегмента, который определяет размер сегмента в единицах, определяе- мых битом гранулярности g 2...4 23 base_l Биты 0...23 32-разрядной базы сегмента, которая определяет значение линейно- го адреса начала сегмента в памяти 5 8 AR Байт, поля которого определяют права доступа к сегменту (табл. 16.2) 6 0..3 limit_2 Старшие биты 16... 19 20-разрядного пре- дела сегмента 6 1 и Бит пользователя (User). Не имеет спе- циального назначения и может исполь- зоваться по усмотрению программиста 6 1 - 0 — бит не используется 6 1 D Бит разрядности операндов и адресов: 0 — в программе используются 16-раз- рядные операнды и режимы 16-разряд- ной адресации; 1 — в программе используются 32-раз- рядные операнды и режимы 32-разряд- ной адресации 6 1 G Бит гранулярности: 0 — размер сегмента равен значению в поле limit в байтах; 1 — размер сегмента равен значению в поле limit в страницах 7 8 base_2 Биты 24...31 32-разрядной базы сегмента Другой аспект защиты заключается в том, что сегменты неравноправны в пра- вах доступа к ним. Информация об этом содержится в специальном байте AR, входящем в состав дескриптора. Формат этого байта приведен в виде выноски на рис. 16.1. В табл. 16.2 дана краткая характеристика его полей. Наиболее важные поля байта AR — это dpi и биты R/W, C/ED и I, которые вместе определяют тип сегмента. Поле dpi — часть механизма защиты по привилеги- ям. Суть этого механизма заключается в том, что конкретный сегмент может находиться на одном из трех уровней привилегированности с номерами 0, 1, 2 и 3. Самым привилегированным является уровень 0. Существует ряд ограниче- ний на взаимодействие сегментов с различными уровнями привилегий. В рам-
416 Урок 16. Защищенный режим работы микропроцессора ках данной книги мы считаем, что все сегменты находятся на нулевом уровне привилегий. Таблица 16.2. Байт AR дескриптора сегмента Номер бита в байте AR Символическое обозначение Назначение и содержимое 0 А Бит доступа (Accessed) к сегменту. Устанавливается аппаратно при обращении к сегменту 1 R Для сегментов кода — это бит доступа по чтению (Readable); определяет, возможно ли чтение из сегмен- та кода при осуществлении замены префикса сегмента: 0 — чтение из сегмента запрещено; 1 — чтение из сегмента разрешено W Для сегментов данных — это бит записи: 0 — модификация данных в сегменте запрещена; 1 — модификация данных в сегменте разрешена 2 С Для сегментов кода — это бит подчинения (Conforming): 1 — подчиненный сегмент кода; 0 — обычный сегмент кода; ED Для многозадачного режима определяет особенности смены значения текущего уровня привилегий. Для сег- ментов данных — это бит расширения вниз (Expand Down); служит для различения сегментов стека и дан- ных, а также определяет трактовку поля limit: 0 — сегмент данных; 1 — сегмент стека 3 I Бит предназначения (Intending): 0 — сегмент данных или стека; 1 — сегмент кода 4 S Если S = 1 — то это бит сегмента (Segment). Для лю- бых сегментов в памяти равен 1. Назначение и поря- док использования сегмента уточняется битами С и R; если = 0 — то это бит системный (System). Такое состояние бита S говорит о том, что данный дескрип- тор описывает специальный системный объект, кото- рый может и не быть сегментом в памяти 5...6 dpi Поле уровня привилегий сегмента (Descriptor Privilege Level). Содержит численное значение в диапазоне от 0 до 3 привилегированности сегмента, описываемого данным дескриптором 7 Р Бит присутствия (Present): 0 — сегмента нет в оперативной памяти в данный момент; 1 — сегмент находится в оперативной памяти в данный момент
Структуры данных защищенного режима 417 Полю типа сегмента мы уделим больше внимания, так как оно понадобится нам при разработке программы. Это поле определяет целевое назначение сег- мента. В табл. 16.2 биты, входящие в состав поля типа, разделены, но для об- легчения их совместной интерпретации обычно рассматривают комбинации этих битов в целом. В табл. 16.3 перечислено назначение некоторых комбина- ций этих битов. Таблица 16.3. Типичные комбинации в поле типа сегмента Комбинации битов в поле type.seg Назначение сегмента ООО 001 010 011 100 101 110 111 Сегмент данных, только для чтения Сегмент данных с разрешением чтения и записи Не определена Сегмент стека с разрешением чтения и записи Сегмент кода с разрешением только выполнения Сегмент кода с разрешением выполнения и чтения из него Подчиненный сегмент кода с разрешением выполнения Подчиненный сегмент кода с разрешением выполнения и чте- ния из него Как видно из содержимого поля типа в байте AR, возможны два принципиально разных вида сегментов: данных и кода. Сегмент стека является разновидностью сегмента данных, но с особой трактовкой поля размера сегмента. Это объясня- ется спецификой использования стека (он растет в направлении младших ад- ресов памяти). Таким образом, видно, что поле типа ограничивает использова- ние объявленных сегментов. В частности, программные сегменты не могут быть модифицированы без применения специальных приемов. Доступ к сегменту данных также может быть ограничен только на чтение. Итак, мы выяснили, что в защищенном режиме перед использованием любой области памяти должна быть проведена определенная работа по инициализа- ции соответствующего дескриптора. Эту работу выполняет операционная сис- тема или программа, сегменты которой также описываются подобными дес- крипторами. Интерес представляет то, каким образом микропроцессор переходит в защи- щенный режим. Сразу после включения питания или нажатия кнопки сброса микропроцессор начинает свою работу в реальном режиме. В этом режиме он производит действия по тестированию аппаратуры компьютера. После успеш- ного завершения тестирования микропроцессор выполняет начальную загрузку системы, используя программу начальной загрузки, хранящейся на нулевой дорожке диска. Программа начальной загрузки считывает с диска программу инициализации операционной системы и передает ей управление. Действие этой программы зависит от того, в каком режиме работы микропроцессора бу- дет осуществляться дальнейшее функционирование системы. Если в реальном режиме, то операционная система формирует среду и структуры данных для
418 Урок 16. Защищенный режим работы микропроцессора работы в этом режиме. Если же загружаемая операционная система собирается дальше работать в защищенном режиме, то она должна в него специальным образом перейти. Но прежде чем сделать это, операционная система формиру- ет системные структуры данных (в частности, рассмотренные нами дескрип- торные таблицы) для работы в защищенном режиме. Затем можно переходить в защищенный режим и выполнять дальнейшие действия. На этом уроке мы с вами попытаемся написать некоторый фрагмент такой операционной системы, а именно тот, который производит инициализацию структур защищенного ре- жима и перевод микропроцессора в этот режим. Если у вас возникнет желание, то далее вы можете написать свою маленькую операционную систему, выпол- няющую некоторые действия. Вначале — несколько организационных моментов. Понятно, что в своих дей- ствиях мы должны следовать логике, в соответствии с которой микропроцессор переводится в защищенный режим. Исходя из нее, наша операционная мини- система должна начинать свою работу в реальном режиме, поэтому и наша программа должна начать работать в среде такой операционной системы. Осо- бого выбора у нас нет: операционная система MS-DOS либо режим эмуляции MS-DOS в Windows 95. Запустить программу прямо из многозадачной опера- ционной системы, которой является, например, Windows 95, нельзя. Дело в том, что в целях защиты любая операционная система защищенного режима перекрывает на определенном уровне доступ к системным ресурсам со сторо- ны программ пользователя с тем, чтобы не нарушить свою работу. Делается это следующим образом. Мы с вами упоминали об уровне привилегий, как одном из атрибутов сегментов памяти в защищенном режиме. В этом режиме наша программа, написанная как приложение MS-DOS, будет выполняться в режиме виртуального 8086 с уровнем привилегий 3. Чтобы получить доступ к ресурсам микропроцессора, управляющим сегментацией, ей необходимо иметь уровень привилегий 0. Кроме того, находясь в защищенном режиме, не имеет смысла говорить о проблеме перехода в него. Поэтому у нас есть две возможности — загрузить MS-DOS с помощью систем- ной дискеты либо выполнить перезагрузку своего компьютера с тем, чтобы пе- ревести Windows 95 в режим эмуляции MS-DOS. Но одной загрузки MS-DOS мало, необходимо проследить, чтобы в файле config.sys были отключены все драйверы расширенной памяти типа emm386.exe, так как они неявно переводят микропроцессор в защищенный режим работы, исключая доступ к структурам данных этого режима. Впрочем, если вы забуде- те это сделать, то при первой же попытке вашей программы перейти в защи- щенный режим вы получите соответствующее сообщение. Закомментируйте соответствующие строки в файле config.sys, и проблема будет снята. Пример программы защищенного режима Во второй половине урока мы рассмотрим фрагменты нашей операционной мини-системы, которые подготавливают структуры данных защищенного ре- жима, переводят микропроцессор в этот режим, имитируют работу путем вы-
Пример программы защищенного режима 419 вода некоторого сообщения, после чего переводят микропроцессор обратно в ре- альный режим и заканчивают выполнение. Давайте перечислим и обсудим дей- ствия, необходимые для обеспечения функционирования этой программы: 1. Подготовка в оперативной памяти таблицы глобальных дескрипторов GDT. 2. Инициализация необходимых дескрипторов в таблице GDT. 3. Загрузка в регистр gdtr адреса и размера таблицы GDT. 4. Запрет обработки аппаратных прерываний. 5. Переключение микропроцессор в защищенный режим. 6. Организация работы в защищенном режиме: О настроить сегментные регистры; О выполнить собственно содержательную работу программы; в нашем слу- чае мы просто обозначим сам факт успешного перехода в защищенный режим; О подготовиться к возврату в реальный режим; О запретить аппаратные прерывания; 7. Переключение микропроцессора в реальный режим. 8. Настройка сегментных регистров для работы в реальном режиме. 9. Разрешение прерываний. 10. Стандартное для MS-DOS завершение работы программы. Далее мы подробно обсудим каждый этап и суммируем все в виде листин- га 16.1. Подготовка таблиц глобальных дескрипторов GDT Для того чтобы собрать информацию о всех программных объектах, находящих- ся в данный момент в памяти и в системе в целом, их дескрипторы собираются в таблицы, которые представляют собой массивы 8-байтовых элементов. Микропроцессор аппаратно поддерживает три типа дескрипторных таблиц: 1. Таблица GDT (Global Descriptor Table) — глобальная дескрипторная табли- ца, Это основная общесистемная таблица, к которой допускается обращение со стороны программ, обладающих достаточными привилегиями. Располо- жение таблицы GDT в памяти произвольно; оно локализуется с помощью специального регистра gdtr. В таблице GDT могут содержаться следующие типы дескрипторов: О дескрипторы сегментов кодов программ; О дескрипторы сегментов данных программ; О дескрипторы стековых сегментов программ;
420 Урок 16. Защищенный режим работы микропроцессора О дескрипторы TSS (Task Segment Status) — специальные системные объек- ты, называемые сегментами состояния задач; О дескрипторы для таблиц LDT; О шлюзы вызова; О шлюзы задач. 2. Таблица LDT (Local Descriptor Table) — локальная дескрипторная таблица. Для любой задачи в системе может быть создана своя дескрипторная табли- ца подобно общесистемной GDT. Тем самым адресное пространство задачи локализуется в пределах, установленных набором дескрипторов таблицы LDT. Для связи между таблицами GDT и LDT в таблице GDT создается дескриптор, описывающий область памяти, в которой находится LDT. Рас- положение таблицы LDT в памяти также произвольно и локализуется с по- мощью специального регистра Idt г. В таблице LDT могут содержаться сле- дующие типы дескрипторов: О дескрипторы сегментов кодов программ; О дескрипторы сегментов данных программ; О дескрипторы стековых сегментов программ; О шлюзы вызова; О шлюзы задач. 3. Таблица IDT (Interrupt Descriptor Table) — дескрипторная таблица преры- ваний. Данная таблица также является общесистемной и содержит дескрип- торы специального типа, которые определяют местоположение программ обработчиков всех видов прерываний. В качестве аналогии можно привести таблицу векторов прерываний реального режима. Расположение таблицы IDT в памяти произвольно и локализуется с помощью специального регис- тра idt г. Элементы данной таблицы называются шлюзами. Это элементы особого рода, и мы их рассмотрим на следующем уроке при изучении обра- ботки прерываний в защищенном режиме. Сейчас только отметим, что эти шлюзы бывают трех типов: О шлюзы задач; О шлюзы прерываний; О шлюзы ловушек. Каждая из дескрипторных таблиц может содержать до 8192 (213) дескрипторов. Данное значение определяется размерностью поля в сегментном регистре. Мы помним, что в защищенном режиме роль дескрипторов изменяется по сравне- нию с реальным режимом, и это отражается даже на их названии — в защи- щенном режиме они называются селекторами сегментов. Так как наша программа претендует лишь на роль фрагмента операционной системы, то вполне достаточно определить пока только одну дескрипторную таблицу — глобальную дескрипторную таблицу GDT. Прерывания мы пока обра-
Пример программы защищенного режима 421 обрабатывать не будем — подождем до следующего урока. Таблицу LDT есть смысл применять, когда в системе работают несколько задач и необходимо изо- лировать их друг от друга. Пока от нас этого не требуется. Тем не менее, отметим следующее. Выше мы сказали, что дескрипторы, описывающие сегменты некоторой програм- мы, могут содержаться как в глобальной (GDT), так и в локальной (LDT) деск- рипторных таблицах. Сегментные регистры содержат селекторы, которые явля- ются указателями на дескрипторы, описывающие соответствующие области памяти. Но как микропроцессор узнает о том, в какой из двух дескрипторных таблиц находится дескриптор, на который указывает селектор? Структура сегментного регистра в защищенном режиме представляется тремя полями. Поле RPL, занимающее два младших бита 0 и 1 (Request Privilege Level — запрашиваемый уровень привилегий), используется в механизме огра- ничения доступа по привилегиям. А вот состояние однобитового поля TI, зани- мающего бит 2 сегментного регистра, как раз и определяет, с какой именно таб- лицей идет работа: О если TI = 0, то сегментный регистр содержит селектор на дескриптор в гло- бальной дескрипторной таблице GDT; О если TI = 1, то сегментный регистр содержит селектор на дескриптор в ло- кальной дескрипторной таблице LDT. И наконец, сегментный регистр содержит поле селектора. Оно определяет число, кратное восьми (так как три младшие бита заняты под поля RPL и TI), являюще- еся указателем на дескриптор в одной из дескрипторных таблиц в соответствии со значением бита в поле TI. Управлять состоянием этого бита может либо сама программа, но тогда она должна обладать достаточным уровнем привилегий, либо операционная система, обеспечивающая ее работу. Для самой же програм- мы ничего не меняется. По прежнему базовые адреса ее сегментов определяются с помощью сегментных регистров, хотя и по-иному, чем в реальном режиме, принципу. В каждый момент времени микропроцессор может работать только с одной дескрипторной таблицей: GDT или LDT. В нашем случае мы будем счи- тать, что все сегменты программы находятся в глобальной дескрипторной табли- це GDT, то есть TI = 0 во всех используемых сегментных регистрах. Определимся теперь с набором дескрипторов в таблице GDT, которые понадо- бятся для нашей программы: О дескриптор для описания сегмента самой таблицы GDT; О дескриптор для описания сегмента данных программы; О дескриптор для описания сегмента команд программы; О дескриптор для описания сегмента стека программы; О дескриптор для описания сегмента, в котором будет находиться процедура для выдачи сигнала сирены; О дескриптор для описания видеопамяти.
422 Урок 16. Защищенный режим работы микропроцессора Формат дескриптора сегмента показан на рис. 16.1. В программе его удобнее описать в виде структуры: descr st rue limit dw 0 base_1 dw 0 base_2 db 0 atrdb 0 lim_atr db 0 base_3 db 0 ends Используя этот шаблон структуры, опишем таблицу GDT как массив структур: gdt_seg segment para gdt_O descr <0,0,0,0,0,0> gdt_gdt_8 desc r <0,0,0,0,0,0> gdt_ldt_10 descr <0,0,0,0,0,0> gdt_ds_18 descr <0,0,0,0,0,0> gdt_es_vbf_20 descr <0,0,0,0,0, 0> gdt_ss_28 descr <0,0,0,0,0, 0> gdt_cs_30 desc r <0,0,0,0,0, 0> gdt_size=$-gdt_0-1 ;определение размера таблицы GDT gdt_seg ends Инициализация дескрипторов в таблице GDT После того как мы определились с набором дескрипторов и даже сформировали глобальную дескрипторную таблицу, нужно разобраться с тем, где брать инфор- мацию для их заполнения и как это делать. Давайте обсудим этот вопрос для каждого поля в отдельности: О поле limit — размер сегмента. В это поле нужно поместить точный размер сегмента за вычетом единицы, так как адресация идет от нуля. Наиболее оптимальный способ заключается в использовании оператора $ — извлечь текущее значение счетчика адреса. Пример использования этого оператора для определения размера приведен выше при описании таблицы GDT; О поля base.1, base_2, base_3 — поля 32-разрядного базового адреса. Этот ад- рес будет известен после загрузки программы в оперативную память, и поля придется загружать на этапе выполнения при подготовке к переходу в за- щищенный режим. Фрагмент программы заполнения поля базового адреса для дескриптора gdt_gdt_8 может выглядеть так: хог еах,еах mov ax,gdt_seg ;адрес сегмента в ах ;сдвигом на 4 разряда получим физический
Пример программы защищенного режима 423 ; 20-разрядный адрес сегмента gdt.seg: shl еах, 4 mov base_1,ах rol еах, 16 ; меняем для получения ; оставшейся части адреса mov base_2,al Так как эту операцию нам придется проделывать несколько раз, для каждого из сегментов программы, то для удобства работы и повышения наглядности оформим этот фрагмент в виде макроса load_addr: load.addr mac ro desc г,seg.add г,seg_size mov descr.limit,seg.size xor eax,eax mov ax,seg_adr shl eax, 4 mov descr. base J, ax rol eax,16 mov descr.base_2,al endm Заодно, как видите, мы добавили к операциям, выполняемым макросом, и инициализацию поля предела; О поле at г — байт атрибутов. Структура этого байта представлена в табл. 16.2. Понятно, что для конкретного сегмента его значение будет всегда константой. Чтобы не запутывать себя окончательно, на мой взгляд, значение этого бай- та в целом удобнее формировать как логическую сумму нужных значений его полей для определения типа конкретного сегмента. Сформируем элемен- тарные константы полей этого байта в соответствии с битами табл. 16.2: ;биты 0, 4, 5, 6, 7 - постоянная часть ;байта AR для всех типов сегментов const equ 10010000b ;бит 1 - доступность сегментов по чтению/записи code_r_n equ 00000000b ;сегмент кода: ;чтение запрещено code_r_y equ 00000010b ;сегмент кода; ;чтение разрешено data_wm_ n equ 00000000b ;сегмент данных: ;модификация запрещена data_wm_ У equ 00000010b ;сегмент данных: ;модификация разрешена ;бит 2 - тип сегмента code_n equ 00000000b ;обычный сегмент кода code.p equ 00000100b •.подчиненный сегмент кода
424 Урок 16. Защищенный режим работы микропроцессора .data equ 00000000b ; сегмент данных .stackequ 00000100b ; сегмент стека ; бит 3 - предназначение .code equ 00001000b ; сегмент кода data.stk equ 00000000b ; сегмент данных или стека Теперь для получения значения атрибута для нужного сегмента достаточно подобрать нужные константы по битам и выполнить подсчет суммы, как, например, для дескриптора gdt_gdt_8: atr=const or data_wm_y or -data or data.stk Значение atr для дескриптора сегмента gdt_gdt_8 будет равно 10010010 = = 92h. Для удобства использования можно создать макрос, который будет создавать константу с именем, состоящим из двух частей: приставки atr и имени дескриптора, для которого формируется байт атрибута (к примеру, для дескриптора gdt_gdt_8 это будет at r_gdt_gdt_8): atr macro descr,bit1,bit2,bit3 atr&descr=const or bitl or bit2 or bit3 endm Имена формируемых констант нужно указывать при инициализации струк- тур для каждого дескриптора, к примеру для gdt_gdt_8: gdt.seg segment para gdt_O descr <0,0,0,0,0,0> atr gdt_gdt_8,data_wm_y,.data,data.stk gdt_gdt_8 descr <0,0,0, atr_gdt_gdt_8,0,0> gdt.seg ends О поле lim.atr — байт, состоящий из четырех старших битов размера сегмен- та и четырех атрибутов (см. табл. 16.1). В нашем случае размер сегмента небольшой, то есть четыре старших бита размера равны 0. И оставшиеся биты атрибутов для нашего случая также нулевые; О поле base_3 содержит старший байт 4-байтового физического адреса сегмен- та. Так как мы начинаем работу в реальном режиме, где размер максималь- ного физического адреса не превышает 20 бит, то этот байт также будет нулевым. Таким образом, в нашей программе инициализации будут подлежать три поля: limit, три первых байта адреса base-1 и base-2 и байт атрибута atr. Загрузка регистра gdtr В начале урока мы обсуждали набор и назначение системных регистров. Также мы говорили о том, что дескрипторные таблицы являются ключевыми для
Пример программы защищенного режима 425 организации работы в защищенном режиме. Их местоположение в памяти может быть любым. Микропроцессор узнает о том, где находятся эти таблицы, по со- держимому определенных системных регистров. Так, после того как нами была сформирована таблица GDT, ее адрес нужно поместить в регистр gdtr. Но этого мало, так как в этот же регистр нужно поместить и размер этой таблицы. Для загрузки именно этого регистра в системе команд микропроцессора есть специ- альная команда: Igdt адрес_48-битного_поля (Load GDT register) — загрузить регистр gdtr. Команда Igdt загружает системный регистр gdtr содержимым 6-байтового поля, адрес которого указан в качестве операнда. Из описания команды следует, что вначале необходимо сформировать поле из шести байт со структурой, аналогичной формату регистра gdtr, а затем указать адрес этого поля в качестве операнда команды Igdt. Для резервирования поля из шести байт (48 бит) TASM поддерживает специ- альные директивы резервирования и инициализации данных — dp и df. После выделения — с помощью одной из этих директив — области памяти в сегменте данных необходимо сформировать в этой области указатель на начало таблицы GDT и ее размер. Но удобнее использовать структуру. Пример ее использова- ния показан в следующем фрагменте программы: point st rue lim dw 0 adr dd 0 ends data segment point_gdt point <gdt_size,0> code segment xor eax,eax mov ax,gdt_seg shl eax,4 mov dword ptr point_gdt.adr,eax Igdt pword point_gdt Запрет обработки аппаратных прерываний Как мы увидим на следующем уроке, обработка прерываний в защищенном режиме принципиально отличается от обработки прерываний в реальном ре- жиме (см. урок 15). Поэтому, как только микропроцессор переключится в защищенный режим, первое же прерывание от таймера, которое происходит 18,2 раза в секунду, «подвесит» компьютер. До тех пор, пока мы не научимся обрабатывать прерывания как программные, так и внешние, их нужно будет
426 Урок 16. Защищенный режим работы микропроцессора запрещать. Для этого вы уже освоили два способа: прямым программированием контроллера прерываний и командой микропроцессора cli. Можете использо- вать любой, только не нужно их сочетать. Переключение микропроцессора в защищенный режим Теперь у нас все готово для того, чтобы корректно перейти в защищенный ре- жим. Специальных команд микропроцессора для выполнения такого перехода нет. О том, что микропроцессор находится в защищенном режиме, говорит лишь состояние бита ре в регистре сгО. Установить этот бит можно двумя спо- собами: О непосредственной установкой бита ре в регистре сгО. Состояние этого бита управляет режимами работы микропроцессора: если ре = 0, то микропроцес- сор работает в реальном режиме, если ре = 1, то микропроцессор работает в защищенном режиме; О использованием функции 89h прерывания 15h BIOS. Мы опишем оба способа, но использовать будем первый. Регистр сгО программно доступен, поэтому установить бит ре можно, исполь- зуя обычные команды ассемблера: mov еах.сгО or eax.OOOIh mov crO.eax Последняя команда mov переводит микропроцессор в защищенный режим. Функция 89h прерывания 15h выполняет это и некоторые другие действия не- явно. Прежде чем вызывать это прерывание, необходимо посредством регист- ров сообщить ему следующее: О ah = 89h; О bl = новый номер для аппаратного прерывания уровня irqO. Уровни irql...7 будут иметь следующие по порядку номера; О bh = новый номер для аппаратного прерывания уровня irq8. Уровни irq9...f будут иметь следующие по порядку номера; О ds:si = адрес GDT для защищенного режима; О сх = адрес первой выполняемой команды в защищенном режиме. Эта функция предполагает, что дескрипторы в таблице GDT расположены в определенной последовательности: О Oh — пустой дескриптор; О 8h — дескриптор таблицы GDT;
Пример программы защищенного режима 427 О 10h — дескриптор таблицы LDT; О 18h — дескриптор сегмента данных, на него указывает селектор в регистре ds; О 20h — дескриптор дополнительного сегмента данных, на него указывает се- лектор в регистре es; О 28h — дескриптор сегмента стека, на него указывает селектор в регистре ss; О 30h — дескриптор сегмента кода, на него указывает селектор в регистре cs; О с гальные дескрипторы. В нашей таблице GDT этот порядок следования соблюден, поэтому фрагмент программы перевода микропроцессора в защищенный режим может быть сле- дующим: code segment mov ah,89h mov bl,20h mov bh,28h mov ax,gdt.seg mov ds,ax mov si,0 lea ex,protect int 15h protect: ;работа в защищенном режиме Работа в защищенном режиме Настройка сегментных регистров После выполнения всех вышеописанных действий состояние микропроцессора можно сравнить с состоянием человека, которого после пребывания на ярком солнце завели в темную комнату. Что нужно сделать в такой ситуации для того, чтобы микропроцессор не уподобился слону в посудной лавке? Мы уже разобрались с вами, что содержимое сегментных регистров в реальном и защищенном режимах интерпретируется микропроцессором по-разному. Как только микропроцессор оказывается в защищенном режиме, первую же коман- ду он пытается выполнить традиционно: по содержимому пары cs:ip опреде- лить ее адрес, выбрать ее и т. д. Но содержимое cs должно быть индексом, ука- зывающим на дескриптор сегмента кода в таблице GDT. Но пока это не так, так как в данный момент cs все еще содержит физический адрес параграфа сег- мента кода, как этого требуют правила формирования физического адреса в реальном режиме. То же самое происходит и с другими регистрами. Но если
428 Урок 16. Защищенный режим работы микропроцессора содержимое других сегментные регистров можно подкорректировать в програм- ме, то в случае с регистром cs этого сделать нельзя, так как он в защищенном режиме программно недоступен. Нужно помочь микропроцессору сориентиро- ваться в этой затруднительной ситуации. Вспомним, что действие команд пере- хода основано как раз на изменении содержимого регистров cs и ip. Команды ближнего перехода изменяют только содержимое eip\ip, а команды дальнего пе- рехода — оба регистра cs и eip\ip. Воспользуемся этим обстоятельством, вдоба- вок существует и еще одно свойство команд передачи управления — они сбра- сывают конвейер микропроцессора, подготавливая его тем самым к приему команд, которые сформированы уже по правилам защищенного режима. Это же обстоятельство заставляет нас впрямую моделировать команду межсегментного перехода, чтобы поместить правильное значение селектора в сегментный регистр cs. Это можно сделать так: code segment I • • • db Oeah машинный код команды jmp dw offset protect ; смещение метки перехода ;в сегменте команд dw 30h ; селектор сегмента кода в таблице GDT protect: •«загрузить селекторы для остальных дескрипторов mov ax,18h mov ds, ах ; сегментный регистр данных mov ax,28h mov ss.ax ; сегментный регистр стека mov ax,20h mov es.ax; дополнительный сегмент данных: ;для указания на видеобуфер После этого можно работать, так как мы уже привыкли. Но за кадром остался один момент, на который нужно обязательно обратить внимание. В микропроцессоре каждому сегментному регистру соответствует свой теневой регистр дескриптора. Этот регистр имеет размер 64 бит и фор- мат дескриптора сегмента. Смена содержимого теневых регистров производит- ся автоматически всякий раз при смене содержимого соответствующего сег- ментного регистра. Последние наши действия по изменению содержимого сегментных регистров привели к тому, что мы неявно записали в теневые реги- стры микропроцессора содержимое соответствующих дескрипторов из GDT. Программисту теневые регистры недоступны, с ними работает только микро- процессор. Если есть необходимость изменить их содержимое, то для этого нужно сначала изменить сам дескриптор, а затем загрузить соответствующий ему селектор в нужный сегментный регистр.
Пример программы защищенного режима 429 Выполнение собственно программы защищенного режима Наша программа должна будет выполнить несколько действий. При этом, есть существенные ограничения на ее работу. Прежде всего, это связано с тем, что пока мы не умеем обрабатывать прерывания. Так что придется использовать прямой доступ к аппаратуре, используя пространство портов ввода-вывода (см. листинг 16.1). Подготовка к возврату в реальный режим Здесь возникает примерно та же проблема с сегментными регистрами, что была при входе в защищенный режим. Мы упоминали уже о теневых регистрах микропроцессора, но не сказали того, что микропроцессор использует их, даже работая в реальном режиме. При этом поля этих регистров заполнены, конеч- но, в соответствии с требованиями реального режима: О предел должен быть равен 64 К = Offffh; О бит G = 0 (значение размера в поле предела) — это значение в байтах; О байт атрибута равен 10010010 = 92h; О базовый адрес значения не имеет. Следовательно, перед переходом в реальный режим нужно сформировать эти значения в соответствующих дескрипторах и сделать актуальными эти измене- ния в теневых регистрах, для чего нужно перезагрузить сегментные регистры. После этого можно смело переходить в реальный режим. В программе все эти действия могут выглядеть так: <1> <2> code segment <3> ;мы в защищенном режиме и начинаем переход в реальный режим <4> ;загрузим значение Offffh в поле предела <5> mov gdt_ds_18.limit,Offffh <б> ;для регистров cs, ss, es, fs и gs аналогично <7> ;селектор - в регистр данных, чтобы обновить теневой регистр для ds <8> mov ах,18 <9> mov ds,ах <10> ;для регистров ss, es, fs и gs аналогично <11> ;для регистра cs загрузку селектора проведем <12> -.особым способом - командой jmp far <13> db Oeah <14> dw offset jump <15> dw 30h <1б> jump: <17> ;можно переходить в реальный режим
430 Урок 16. Защищенный режим работы микропроцессора Запрет аппаратных прерываний Для нашей программы этого делать и не нужно, так как мы их и не разрешали. Но в будущем вы должны иметь в виду, что перед переходом в реальный ре- жим кроме настройки сегментных регистров нужно будет перестраивать и сис- тему прерываний. Для этого на участке программы, производящем соответ- ствующие действия, необходимо запретить аппаратные прерывания одним из известных вам способов. Переключение микропроцессора в реальный режим Скорее всего, что после нашего обсуждения выполнение этой операции не со- ставит для вас труда. Нужно всего лишь сбросить нулевой бит регистра сгО. Это можно сделать несколькими способами. К примеру: mov еах.сгО and al.Ofeh mov сгО.еах После сброса бита ре микропроцессор снова оказался в реальном режиме. Настройка сегментных регистров После перехода в реальный режим опять возникает проблема с содержимым сегментных регистров. Решается она уже известным вам способом: моделирование команды дальнего перехода для загрузки cs db Oeah dw real_mode dw code realjnode: mov ax,data mov ds, ax mov ax,stk mov ss.ax Разрешение прерываний Теперь можно разрешить прерывания. Так как в системе прерываний мы ниче- го не меняли, то действия наши тоже были простейшими: а) запрещение аппа- ратных прерываний для того, чтобы на время смены режима работы микропро- цессора нас не беспокоило прерывание от таймера, б) последующее разрешение прерываний после возврата в реальный режим. Что может быть легче? На прак-
Пример программы защищенного режима 431 тике, конечно, без прерываний не обойтись, и, как мы увидим на следующем уроке, этот вопрос не менее сложен, чем перевод микропроцессора из одного ре- жима работы в другой. Пока же все просто: cli ;флаг IF в 0 - запретить прерывания от аппаратуры и sti ; флаг IF в 1 - разрешить прерывания от аппаратуры Стандартное для MS-DOS завершение работы программы Этот вопрос комментировать не имеет смысла. Если все было сделано правиль- но, то окончание работы программы выглядит стандартно. Теперь нам осталось собрать все вместе в одну программу, ввести ее в машину и приступить к экспериментам. Нужно отметить один момент, с которым вы столкнетесь при трансляции исходного модуля. Он связан с вычислением кон- станты code_size. Фрагмент листинга с этой ошибкой будет выгдядеть так: 184 load_descr gdt_cs_30,CODE,code_size 1 185 0072 C706 0030г 0197 mov gdt_cs_30.limit,code_size ★★Error** prg_16_1.asm(94) L0AD_DESCR(1) Forward reference needs override Природа этой ошибки понятна — опережающая ссылка на объект, о котором компилятор еще не имеет информации. Объектом в данном случае является константа code_size. Она вычисляется в конце сегмента кода, а применяется — в середине. Чтобы устранить эту ошибку, нужно заставить компилятор выпол- нить два прохода программы. Тогда за первый проход он получит представле- ние обо всех объектах программы, а на втором проходе сформирует правиль- ные команды. Чтобы указать компилятору на необходимость выполнения двух проходов, используется опция командной строки /m2 (см. приложение 1): tasm /m2 prg16_1.asm,,, Листинг 16.1. Программа перевода микропроцессора в защищенный режим ;prg16_1.asm . 586Р разрешение инструкций Pentium .MODEL large include mac.inc ;структура для описания дескрипторов сегментов descr STRUC limit dw 0 base_1 dw 0 base_2 db 0 attr db 0 lim.atr db 0
432 Урок 16. Защищенный режим работы микропроцессора base_3 db О ENDS ;макрос инициализации дескрипторов load.desc г MACRO des,seg.add г,seg.size mov des.limit,seg.size xor eax,eax mov ax,seg.addr shl eax, 4 mov des.base_1,ax rol eax,16 mov des.base_2,al ENDM atrMACRO descr,bit1,bit2,bit3 ; можно использовать условные директивы ;для проверки наличия параметров atr_&descr=constp or bitl or bit2 or bit3 ENDM структура для описания псевдодескриптора gdtr point STRUC limdw 0 adrdd 0 ENDS ; атрибуты для описания дескрипторов сегментов ; постоянная часть байта AR для всех ;сегментов - биты 0, 4, 5, 6, 7 constp equ 10010000b ; бит 1 code.r.n equ 00010000b ;кодовый сегмент: code_r_y equ 00000010b ; чтение запрещено; ; кодовый сегмент: data_wm_n equ 00000000b ; чтение разрешено -.сегмент данных: data_wm_y equ 00000010b ; модификация запрещена ; сегмент данных: ; бит 2 code_n equ 00000000b •.модификация разрешена; ; обычный сегмент кода code.p equ 00000100b подчиненный сегмент кода data. equ 00000000b ;для сегмента данных stack. equ 00000000b ;для сегмента стека ; бит 3 code. equ 00001000b ; сегмент кода
Пример программы защищенного режима 433 data.stk equ 00000000b ; сегмент данных или стека stksegment stack “stack” use16 db 256 dup (0) stkends ; таблица глобальных дескрипторов gdt.seg segment para public “data” use16 gdt_O descr <0,0,0,0,0,0> ; никогда не используется at r gdt_gdt_8,data.wm.y,data.,data_stk ;ниже описываем саму gdt gdt_gdt_8 desc r <0,0,0,at r_gdt_gdt_8, 0, 0> ;не используем gdt_ldt_10 descr <0,0,0,0,0,0> at г gdt.ds.18,data.wm.y,data_,data.stk ;дескриптор сегмента данных gdt.ds.18 desc г <0,0,0, at r_gdt_ds_18,0,0> atr gdt_vbf_20,data.wm.y,data_,data_stk gdt.es.vbf,20 desc r <0,0,0,at r.gdt.vbf,20, 0, 0> ;видеобуфер atr gdt_ss_28,data.wm.y,stack.,data.stk gdt_ss_28 descr <0,0,0,atr_gdt_ss_28,0,0>;сегмент стека at г gdt_cs_30,code,r_y,code_n,code, gdt_cs_30 desc r <0,0,0,at r_gdt_cs_30,0, 0>;се гмент кода gdt_size=$-gdt_0-1 ; размер GDT минус 1 GDT.SEG ENDS ; данные программы data segment para public "data” use16 ;сегмент данных point.gdt point <gdt_size,0> hello db «Вывод сообщения в защищенном режиме» hel_size=$-hello tonelow dw 2651 ; нижняя граница звучания 450 Гц cntdb 0 ; счётчик для выхода из программы temp dw ? ; верхняя граница звучания data_size=$-point_gdt-1 data ends code segment byte public “code” use16 ; сегмент кода с 16-разрядным режимом адресации assume cs:code,ss:stk main proc mov ax,stk mov ss.ax заполняем таблицу глобальных дескрипторов assume ds:gdt.seg продолжение &
434 Урок 16. Защищенный режим работы микропроцессора mov ax,gdt_seg mov ds,ax load_descr gdt_gdt_8,GDTJSEG,gdt_size load_descr gdt_ds_18,DATA,data.size load_desc r gdt_es_vbf_20,0b800h,3999 load_desc г gdt_ss_28,STK,255 load_desc r gdt_cs_30,CODE,Offffh ;code_size assume ds:data mov ax,data mov ds, ax загружаем gdtr xor eax,eax mov ax,gdt_seg shl eax,4 mov point_gdt. ad r, eax Igdt point_gdt запрещаем прерывания cli mov al,80h out 70h,al ; переключаемся в защищенный режим mov еах.сгО or al, 1 mov crO.eax •«настраиваем регистры db Oeah ; машинный код команды jmp dw offset protect ; смещение метки перехода ;в сегменте команд dw protect: 30h •«селектор сегмента кода ; в таблице GDT загрузить селекторы для остальных дескрипторов mov ах,18h mov ds,ах mov ax,20h mov es.ax mov ax,28h mov ss,ax ; работаем в защищенном режиме: ; выводим строку в видеобуфер mov cx,hel_size ;длина сообщения mov si,offset hello ;адрес строки сообщения
Пример программы защищенного режима 435 mov di, 1640 начальный адрес видеобуфера для вывода mov ah, 07h; атрибут выводимых символов outst г: mov al.[si] mov es:[di],ах inc si inc di inc di loop outstr ; запускаем сирену до: ; заносим слово состояния 110110110b (0B6h): ; выбираем второй канал порта 43h - динамик mov ax,0B06h out 43h,ax; в порт 43h in al, 61h ; получим значение порта 61h в al or al,3 ; инициализируем динамик - подаем ток out 61h,al;в порт 61h mov ex, 2083 ; количество шагов musicup: ;в ax значение нижней границы частоты 1193000/2651 = 450 Гц, ; где 1193000- частота динамика mov ax,tonelow out 42h, al; al в порт 42h mov al, ah ; обмен между al и ah out 42h, al; старший байт ax (ah) в порт 42h add tonelow,1 ; увеличиваем частоту delay 1 ;задержка на 1 мкс mov dx,tonelow ; текущее значение частоты в dx mov temp.dx ; temp - верхнее значение частоты loop musicup ; повторить цикл повышения mov ex,2083 musicdown: mov ax,temp ; верхнее значение частоты в ах out 42h,al; младший байт ax (al) в порт 42h mov al, ah ; обмен между al и ah out 42h,al; а порт 42h уже старший байт ax (ah) sub temp,1 уменьшаем значение частоты delay 1 ;задержка на 1 мкс loop musicdown ; повторить цикл понижения nosound: in al, 61h ; получим значение порта 61h в al продолжение &
436 Урок 16. Защищенный режим работы микропроцессора ; слово состояния - выключение динамика и таймера and al.OFCh out 61h,al;в порт 61h mov dx,2651 ;для последующих циклов mov tonelow,dx увеличиваем счётчик проходов - количество звучаний сирены inc ent cmp ent, 5 ;5раз jne go ;если нет - идти на метку до формирование дескрипторов для реального режима assume ds:gdt.seg mov ax,8h mov ds, ax mov gdt_ds_18.limit, Offffh mov gdt_es_vbf_20.limit,Offffh mov gdt_ss_28.limit,Offffh mov gdt_cs_30.limit, Offffh assume ds:data ; загрузка теневых дескрипторов mov ax,18h mov ds, ax mov ax,20h mov es.ax mov ax,28h mov ss.ax db Oeah dw offset jump dw 30h jump: mov eax.crO and al,Ofeh mov crO.eax db Oeah dw offset rjnode dw code rjnode: mov ax,data mov ds, ax mov ax,stk mov ss.ax ; разрешаем прерывания sti
Пример программы защищенного режима 437 xor al,al out 70h,al ;окончание работы программы (стандартно) mov ax,4c00h int 21h main endp code ends end main Подведем некоторые итоги: 0 Микропроцессор в защищенном режиме раскрывает все свои возможности. В этом режиме он позволяет увеличить объем адресуемой оперативной па- мяти до 4 Гбайт, поддерживает несколько моделей организации памяти: плоскую, многосегментную и страничную. 0 Основное достоинство защищенного режима — поддержка мультизадачнос- ти и еще одного, третьего режима работы микропроцессора, — режима вир- туального i8086. Этот режим позволяет работать параллельно нескольким программам, разработанным для микропроцессора i8086. 0 Многозадачность поддерживается микропроцессором на аппаратном уровне. Для этого он имеет специальные системные регистры. В некоторые из этих регистров должны быть загружены адреса системных таблиц. 0 Системные таблицы состоят из дескрипторов, которые описывают все ис- пользуемые в данный момент в системе области памяти. 0 Каждый дескриптор представляет собой структуру, которая определяет ад- рес участка памяти, его размер и ряд атрибутов, регулирующих доступ к нему. 0 После включения или сброса микропроцессор работает в реальном режиме. Для того чтобы микропроцессор начал функционировать в защищенном ре- жиме, необходимо выполнить ряд действий. Их цель — формирование струк- тур защищенного режима, настройка некоторых системных регистров. Лишь после этого микропроцессор можно переключить в защищенный режим.
№ УРОК Обработка прерываний в защищенном режиме □ Перечень и свойства прерываний в защищенном режиме □ Схема обработки прерываний в защищенном режиме □ Формат таблицы дескрипторов IDT и типы дескрипторов □ Практические проблемы обработки прерываний в защищенном режиме
Заключительный урок мы посвятим рассмотрению того, как организована об- работка прерываний в защищенном режиме работы микропроцессоров Intel. Это будет логическим завершением рассмотрения архитектуры микропроцес- сора (хотя мы не стали рассматривать средства поддержки многозадачности, защиты задач, аппаратной отладки, режим виртуального 18086 и многое другое, так как все это темы отдельного большого разговора). Кому-то из читателей может показаться, что на последних трех уроках мы от- клонились от рассмотрения основных вопросов книги — изучения непосред- ственно языка ассемблера, его конструкций, различных приемов и т. д. Сделали мы это намеренно, так как основная область применения языка ассемблера — разработка различных системных программ. А как известно, лучший способ обу- чения — это хорошие практические примеры. Поэтому мы намеренно перевели наше изложение в чисто практическую плоскость, где читатели могут самостоя- тельно попробовать свои силы, выработать свой стиль и т. д. Ведь вы уже поня- ли, что одно и то же действие на ассемблере можно реализовать по крайней мере несколькими способами. На уроке 15 мы обсудили понятие прерывания в микропроцессоре и то, как организуется обработка прерываний в реальном режиме. Обработка прерыва- ний в защищенном режиме отличается от обработки в реальном режиме так же сильно, как и сам защищенный режим отличается от реального. Причины здесь в том, что, во-первых, в защищенном режиме несколько иное распределение номеров векторов прерываний и, во-вторых, здесь принципиально иным явля- ется сам механизм обработки прерываний. Для того чтобы разобраться с первой причиной, посмотрим на распределение векторов прерываний в защищенном режиме. Чтобы сориентироваться в тер- минологии, посмотрите классификацию прерываний в уроке 15. В табл. 17.1 для удобства сравнительного анализа приведено распределение номеров пре- рываний в реальном и защищенном режимах. Таблица 17.1. Распределение прерываний в защищенном и реальном режимах Номер Реальный режим Защищенный режим Тип прерывания Код вектора ошибки 0 1 Деление на ноль Деление на ноль Ошибка Пошаговой работы Пошаговой работы Ошибка или ловушка Не формируется Не формируется — продолжение &
440 Урок 17. Обработка прерываний в защищенном режиме Таблица 17.1. (Продолжение) Номер вектора Реальный режим Защищенный режим Тип прерывания Код ошибки 2 Сигнал на входе NMI Сигнал на входе NMI Ловушка Не формируется 3 Контрольная точка Контрольная точка Ловушка Не формируется 4 Переполнение Переполнение Ловушка Не формируется 5 BIOS: печать экрана/нарушение границ массива Нарушение границ массива Ошибка Не формируется 6 Недопустимая команда Недопустимая команда Ошибка Не формируется 7 Обращение к отсутствующему сопроцессору Обращение к отсутствующему сопроцессору Ошибка Не формируется 8 Микросхема таймера/двойная ошибка Двойная ошибка Авария Формируется (всегда 0) 9 Клавиатура Не используется — Формируется 10 (OAh) Сигнал на линии IRQ^.2 Ошибочный TSS Ошибка Формируется 11(OBh) Сигнал на линии IRQJ} Отсутствие сегмента Ошибка Формируется 12 (OCh) Сигнал на линии 1К0_4/выход за пределы стека Выход за пределы стека или отсутствие стека Ошибка Формируется 13 (ODh) Сигнал на линии IRQ-5/нарушение общей защиты Нарушение общей защиты Ошибка Формируется 14(OEh) Контроллер НГМД сигнал на линии IRQ_6 Отсутствие страницы памяти Ошибка Формируется 15 (OFh) Сигнал на линии IRQJ7 Не используется — — 16 (lOh) BIOS: функции видеосистемы/ ошибка сопроцессора Ошибка сопроцессора Ошибка Не формируется 17 (llh) Используется для прерываний BIOS и DOS Ошибка выравнивания Ошибка Формируется (всегда 0) 18-31 (12h-19h) Используется для прерываний BIOS и DOS Зарезервировано — —
441 Номер вектора Реальный режим Защищенный режим Тип прерывания Код ошибки 32-255 Используется (20h-Offh) для прерываний BIOS и DOS Аппаратные прерывания; прерывания, определяемые пользователем и ОС Ловушка Не формируется Обратите внимание на то, что для некоторых прерываний в столбце для реаль- ного режима наблюдается двойственность источников прерываний (два воз- можных источника прерывания приведены через косую черту). Это объясняет- ся тем, что изначально в микропроцессоре i8086/88 были жестко определены всего 5 прерываний (типа исключений, см. урок 15) с номерами 0...4. В после- дующих моделях микропроцессора разработчики зарезервировали номера в таблице прерываний 0...31. Но, как мы узнали на уроке 15, программы BIOS тоже настроены на обработку аппаратных прерываний с определенными номе- рами, которые, в свою очередь, накладываются на номера исключений из диа- пазона 0...31. Это может быть источником конфликтов, суть которых в том, что непонятно, от какого источника пришло прерывание. Это обстоятельство нуж- но учитывать программисту, который самостоятельно пишет какой-либо из обработчиков прерываний для замены системного. Системные обработчики прерываний для предотвращения таких конфликтов выполняют дополнитель- ные действия по идентификации источника прерывания и лишь затем осуще- ствляют непосредственно обработку прерывания. Для того чтобы разобраться со второй причиной несовместимости механизмов обработки прерываний в реальном и защищенном режимах, рассмотрим схему обработки прерывания в защищенном режиме (рис. 17.1). Ключевыми компонентами в этой схеме являются дескрипторная таблица пре- рываний IDT и системный регистр idt г. При возникновении прерывания от источника с номером п микропроцессор, находясь в защищенном режиме, вы- полняет следующие действия (см. рис. 17.1): 1. Определяет местонахождение таблицы IDT, адрес и размер которой содер- жится в регистре idt г. 2. Складывает значение адреса, по которому размещена IDT, и значение и*8. По данному смещению в таблице IDT должен находиться 8-байтовый дес- криптор, определяющий местоположение процедуры обработки прерывания. 3. Переключается на процедуру обработки прерывания. Это очень обобщенная схема. На практике все гораздо глубже, интереснее и сложнее.
442 Урок 17. Обработка прерываний в защищенном режиме Рис, 17,1, Схема обработки прерывания в защищенном режиме Начнем с того, что в защищенном режиме прерывания и исключения можно раз- делить на несколько групп: О сбой; О ловушка; О аварийное завершение. Это деление производится в соответствии со следующими признаками: О какая информация сохраняется о месте возникновения прерывания или ис- ключения? О возможно ли возобновление прерванной программы? Исходя из этих признаков, можно дать следующие характеристики вышепере- численным группам: О Сбой (ошибка) — прерывание или исключение, при возникновении которо- го в стек записываются значения регистров cs: ip, указывающие на команду, вызвавшую данное прерывание. Это позволяет, получив доступ к сегменту кода, исправить ошибочную команду в обработчике прерывания и, вернув управление программе, фактически осуществить ее рестарт (вспомните, что в реальном режиме при возникновении прерывания в стеке всегда запоми- нается адрес команды, следующей за той, которая вызвала это прерывание). О Ловушка — прерывание или исключение, при возникновении которого в стек записываются значения регистров cs: ip, указывающие на команду, сле- дующую за командой, вызвавшей данное прерывание. Так же, как и в случае ошибок возможен рестарт программы. Для этого необходимо лишь испра- вить в обработчике прерывания соответствующие код или данные, послу-
443 жившие источником ошибки. После этого перед возвратом управления нужно скорректировать значение ip в стеке на длину команды, вызвавшей данное прерывание. Как видим, механизм ловушек похож на механизм прерываний в реальном режиме, хотя не во всем. Здесь есть один тонкий момент. Если пре- рывание типа ловушки возникло в команде передачи управления jmp, то со- держимое пары cs: ip в стеке будет отражать результат этого перехода, то есть соответствовать команде назначения. О Аварийное завершение — прерывание, при котором информация о месте его возникновения недоступна или неполна и поэтому рестарт практически не- возможен, если только данная ситуация не была запланирована заранее. Микропроцессор жестко определяет, какие прерывания являются ошибками, ловушками и авариями. Из приведенных выше описаний этих типов прерыва- ния видно, что соответствующие программы-обработчики будут отличаться алгоритмами работы. По этой причине полезной является информация, приве- денная в четвертом столбце табл. 17.1. В ней каждое из прерываний защищен- ного режима классифицировано в соответствии с приведенной выше схемой. Но в табл. 17.1 присутствует еще один столбец. Его наличие объясняется су- ществованием еще одной особенности: некоторые прерывания при своем воз- никновении дополнительно генерируют и записывают в стек так называемый код ошибки. Этот код может впоследствии использоваться для установления источника прерывания. Код ошибки, если он генерируется для данного преры- вания (табл. 17.1), записывается в стек вслед за содержимым регистров eflags, cs и eip (рис. 17.2). Старшие ядрена Старшие адреса оперативной памяти оперативной памяти а б Рис. 17.2. Стек для прерываний с кодом и без кода ошибки Какую информацию можно извлечь из кода ошибки? Код ошибки структурно представляет собой совокупность четырех полей и имеет размерность 16 бит. Если установлен режим адресации в сегменте use32, то при помещении в стек код ошибки расширяется нулями до 32 бит в сторону старших разрядов. Поля кода ошибки перечислены в табл. 17.2.
444 Урок 17. Обработка прерываний в защищенном режиме Таблица 17.2. Структура кода ошибки Номер бита Обозначение Назначение и содержимое 0 EXTENT Если EXTENT = 1, ИС вызвана аппаратным прерыванием; если EXTENT = 0, ИС вызвана последней выполнявшей- ся командой 1 IDT Если IDT = 1, в поле INDEX содержится индекс дескрип- тора в таблице IDT; если IDT = 0, в поле INDEX содержится индекс дескрип- тора в таблице GDT или LDT 2 TI Если TI = 0, значение в поле INDEX соответствует номе- ру дескриптора в таблице GDT; если TI = 1, значение в поле INDEX соответствует номе- ру дескриптора в таблице LDT 3...15 INDEX Номер дескриптора в одной из таблиц IDT, GDT или LDT в соответствии с состояниями полей EXTENT, IDT или TI; или 0 для некоторых исключений Теперь вам понятно, каким образом обработчик прерывания может распознать, что послужило истинной причиной прерывания, в случае, если возможна неод- нозначность (см. табл. 17.1). Что представляет собой дескрипторная таблица прерываний IDT? Посмотрите еще раз на рис. 17.1. На нем показаны роль и, соответственно, функ- циональное назначение таблицы прерываний защищенного режима IDT. Она связывает каждый вектор прерывания с дескриптором процедуры или задачи, которая будет обрабатывать это прерывание. Формат таблицы IDT подобен формату GDT и LDT, то есть представляет собой совокупность 8-байтовых дес- крипторов. Отличия таблицы IDT от указанных таблиц состоят в следующем: О нулевой дескриптор, в отличие от таблицы GDT, используется; он описыва- ет шлюз для программы обработки исключительной ситуации 0 (ошибка деления); О дескрипторы в таблице IDT строго упорядочены в соответствии с номерами прерываний. В таблицах GDT и LDT, как помните, порядок описания дес- крипторов роли не играет, хотя и допускается наличие некоторых соглаше- ний по их упорядоченности; О размерность таблицы IDT — не более 256 элементов размером по восемь байт, по числу возможных источников прерываний. В отдельных случаях есть смысл описывать все 256 дескрипторов этой таблицы, формируя для неиспользуемых номеров прерываний шлюзы-заглушки. Это позволит кор- ректно обрабатывать все прерывания, даже если они и не планируются к использованию в данной задаче. Если этого не сделать, то при незапланиро-
Шлюз ловушки 445 ванном прерывании с номером, превышающим пределы IDT для данной зада- чи, будет возникать исключительная ситуация общей защиты (с номером 13 (ODh)); О при работе с прерываниями микропроцессор всегда определяет местоположе- ние таблицы IDT по полю базового адреса в регистре idtr (см. табл. 17.2). Это он делает независимо от режима, в котором работает. В реальном режи- ме содержимое поля базового адреса в регистре idtr равно нулю, поэтому работа с таблицей векторов прерываний выполняется без проблем (структу- ра ее в данном случае не имеет значения). Из вышесказанного следует, что возможно произвольное размещение в памяти этой таблицы не только в за- щищенном режиме, но и реальном. При описании этих отличий вы встретили новое понятие — шлюз. Так обычно называют дескрипторы в таблице прерываний IDT для того, чтобы подчерк- нуть их специфику по сравнению с дескрипторами в таблицах GDT и LDT. Шлюзы предназначены для указания точки входа в программу обработки пре- рывания. В дескрипторной таблице прерываний IDT могут содержаться шлю- зы трех типов. Их существование объясняется наличием разных по характеру источников прерывания и особенностями передачи управления в программу обработки. По формату шлюзы похожи на дескрипторы в таблицах GDT и LDT. Физически микропроцессор отличает их по значению в поле типа и со- держимому остальных полей. Возможны три типа шлюзов: О шлюз ловушки; О шлюз прерывания; О шлюз задачи. Дадим им более подробную характеристику. Шлюз ловушки Шлюз ловушки — элемент таблицы IDT, имеющий формат, показанный на рис. 17.3. offeet_2 Р dpi 0 1111 ООО не используется 47 39 36 32 indicator offset_1 31 15 О Рис. 17.3. Формат шлюза ловушки
446 Урок 17. Обработка прерываний в защищенном режиме В табл. 17.3 приведены назначения полей в формате шлюза ловушки. Таблица 17.3. Поля шлюза ловушки Номера битов Обозначение Значение Назначение 0...15 offset_l ПП...ПП Смещение в сегменте (первая половина) 16...31 indicator mm...mm Селектор, указывающий на дескриптор в LDT или GDT 32...36 — Не используется 37...39 000 Постоянное значение 40...43 type 1111 Тип шлюза — ловушка 44 0 Постоянное значение 45...46 dpi nn (обычно dpi = 112) Определение минимального уровня приви- легий задачи, которая может передать управление обработчику прерываний через данный шлюз 47 P 0 или 1 Бит присутствия 48...63 offset_2 ПП...ПП Смещение в сегменте (вторая половина) Когда возникает прерывание и его вектор выбирает в таблице IDT дескриптор шлюза с типом ловушки, микропроцессор сохраняет в стеке информацию о месте, где он прервал работу текущей программы (см. рис. 17.2). После этого он передает управление в соответствии с полями indicator и offset. Поле indicator представляет селектор одной из таблиц, GDT или LDT, в зависимос- ти от состояния бита TI в нем. Поле offset определяет смещение в сегменте кода. Этот сегмент кода описывается дескриптором, на который указывает се- лектор в поле indicator. После того как управление было передано обработчи- ку прерывания, он выполняет свою работу до тех пор, пока не встретит коман- ду iret. Эта команда восстанавливает из стека состояние регистров eflags, cs и eip на момент возникновения прерывания, и работа приостановленной про- граммы продолжается. При подготовке выхода из программы обработки пре- рывания имейте в виду, что команда iret ничего не знает о возможности нали- чия в стеке кода ошибки, поэтому для корректного возврата управления не забудьте при необходимости предварительно удалить командой pop код ошиб- ки из стека.
Шлюз задачи 447 Шлюз прерывания Шлюз прерывания — элемент таблицы IDT, имеющий формат, показанный на рис. 17.4. type offset_2 Р dpi 0 1110 ООО не используется 63 47 39 36 32 indicator offset_1 31 15 Рис. 17.4. Формат шлюза прерывания Если внимательно посмотреть на этот формат, то можно сделать вывод, что его отличие по сравнению с форматом шлюза ловушки только в поле типа. Это имеет свою причину. При возникновении прерывания, которому соответствует шлюз прерывания, микропроцессор выполняет те же действия, что и для шлю- за ловушки, но с одним важным отличием. Когда микропроцессор передает управление обработчику прерывания через шлюз прерывания, то он сбрасыва- ет флаг прерывания в регистре eflags в 0, запрещая тем самым обработку аппа- ратных прерываний. Этот факт имеет важное значение для программирования обработчиков аппаратных и программных прерываний (см. обсуждение пре- рываний реального режима в уроке 15). Если у вас есть сомнение в том, какой из двух рассмотренных типов шлюзов использовать — применяйте шлюз пре- рывания. Шлюз задачи Шлюз задачи — элемент таблицы IDT, имеющий формат, показанный на рис. 17.5. type не используется Р dpi 0 0101 ООО не используется 63 47 39 36 32 indicator не используется 31 15 О Рис. 17.5. Формат шлюза задачи Когда микропроцессор при выполнении прерывания встречает в таблице IDT шлюз задачи, это означает, что он должен осуществить переход на особый объект. С подобными объектами, называемыми задачами, мы пока еще не
448 Урок 17. Обработка прерываний в защищенном режиме встречались. Задача является частью механизма многозадачности. На самом деле, многозадачность не означает того, что в некоторый момент времени мик- ропроцессор может одновременно выполнять несколько командных потоков. Микропроцессор занят в каждый конкретный момент времени выполнением команд только одного потока. Поэтому многозадачность в случае нашего мик- ропроцессора на самом деле является псевдомногозадачностью. Это означает, что микропроцессор, выполняя команды одного потока, по истечении некото- рого времени должен переключиться на выполнение команд другого потока и т. д. За счет высокого быстродействия микропроцессора создается иллюзия того, что несколько задач выполняются одновременно. Но как быть с ресурса- ми, которые эти потоки команд разделяют? Такими ресурсами являются, в част- ности, регистры. Их нужно где-то сохранять на то время, пока работает другая задача. В микропроцессорах Intel эта проблема решена следующим образом. Для каждой задачи определяется сегмент состояния задачи TSS (Task Segment Status) со строго определенной структурой. В этом сегменте есть поля для со- хранения всех регистров общего назначения, некоторых системных регистров и другой информации. Всю совокупность этой информации называют контек- стом задачи. Этот сегмент описывается, подобно другим сегментам, дескрипто- ром в таблице GDT или LDT. Если с помощью некоторого селектора обратить- ся к такому дескриптору, то микропроцессор осуществит переключение на соответствующую задачу. Подобные переключения могут, в частности, осущест- вляться операционной системой, поддерживающей многозадачность, в соответ- ствии с некоторой дисциплиной разделения времени между задачами. Пере- ключение задач может производиться обычными командами межсегментной передачи управления либо по возникновению прерывания при переходе к об- работчику прерывания через шлюз задачи. Таким образом, наличие дескриптора с типом шлюза задачи в таблице IDT позволяет при возникновении соответствующего прерывания переключаться на некоторую задачу, которая будет осуществлять обработку прерывания. Поле indicator в шлюзе задачи содержит селектор, который определяет местонахож- дение дескриптора сегмента TSS — в таблице GDT или LDT (в зависимости от состояния бита TI селектора). В этой книге мы, к сожалению, не имеем воз- можности более подробно рассмотреть реализацию многозадачности для мик- ропроцессоров Intel. Итак, еще раз подчеркнем отличия перечисленных типов шлюзов. Шлюзы ло- вушки и прерывания с помощью полей indicator и offset определяют адрес, по которому находится точка входа в программу обработки прерывания. Шлюз задачи предназначен для реализации принципиально иного перехода к обра- ботчику прерываний — с использованием механизма переключения задач. После такого подробного обсуждения мы готовы к тому, чтобы рассмотреть, каким образом практически реализуется обработка прерываний в защищенном режиме. Так как при этом возникает достаточно много нюансов, то мы рас- смотрим задачу, которая бы отражала суть большинства из них. В нашей про- грамме мы будем обрабатывать одно аппаратное прерывание (от таймера), две разнотипные исключительные ситуации и обычное пользовательское прерыва- ние. На примере этой задачи будут показаны все аспекты обработки прерыва-
Шлюз задачи 449 ний в защищенном режиме, за исключением использования задач в качестве об- работчика прерываний (см. выше обсуждение шлюзов задач). Вначале, так же как и для программы перехода в защищенный режим, обсудим некоторые ключевые моменты. Наша программа по-прежнему является опера- ционной мини-системой, так как она сама обеспечивает свое функционирова- ние на «голом» микропроцессоре. Поэтому за основу мы возьмем программу прошлого урока и дополним ее функционально, научив обрабатывать некото- рые прерывания. В общем случае для того, чтобы сделать возможным обработку прерываний в защищенном режиме, необходимо выполнить следующие действия: О инициализировать таблицу IDT; О составить процедуры обработчиков прерываний; О запретить аппаратные прерывания; О перепрограммировать контроллер прерываний i8259A; О загрузить регистр IDTR адресом и размером таблицы IDT; О перейти в защищенный режим; О разрешить обработку прерываний. Далее микропроцессор, находясь в защищенном режиме, может производить обработку необходимых прерываний. По окончании работы для возврата в ре- альный режим нужно будет все «поставить на свои места», выполнив для этого последовательность следующих действий: О запретить аппаратные прерывания; О выполнить обратное перепрограммирование контроллера прерываний; О перейти в реальный режим работы микропроцессора; О разрешить обработку аппаратных прерываний. Обсудим эти действия подробнее. Инициализация таблицы IDT Выше мы уже определились с тем, что так же, как и в реальном режиме, все прерывания защищенного режима имеют свои номера от 0 до 255. Отличие их от прерываний реального режима в том, что под цели микропроцессора (ис- ключительные ситуации или просто исключения) отдано гораздо большее ко- личество номеров — первые 32 вектора с номерами 0...31. Из этих 32 векторов реально используются только 0...17, остальные вектора 18...31 пока зарезерви- рованы для будущих разработок. Для того чтобы сформировать таблицу IDT в целом, необходимо определиться с описанием отдельных ее дескрипторов — шлюзов. Можно предложить следующий, оформленный в виде структуры, шаб- лон для описания дескрипторов в таблице IDT: descr_idt st rue offs_1 dw 0 ;младшая часть адреса смещения обработчика прерывания
450 Урок 17. Обработка прерываний в защищенном режиме seldw 30 ; селектор на сегмент команд в таблице GDT no_use db О type_attrdb 8eh ; по умолчанию шлюз прерывания, ;для ловушки - 8fh offs_2 dw 0 ; старшая часть адреса смещения обработчика прерывания ends Теперь можно приступить к формированию таблицы IDT. Это можно делать как в отдельном сегменте, так и в сегменте данных. Чтобы не загромождать сегмент данных, оформим таблицу IDT в виде отдельного сегмента. idt_seg segment rept 5 descr_idt <dummy,,,,> endm int05h descr_idt <int_05h,,,, > rept 7 descr_idt <dummy_err,,,, > endm intOdh descr_idt <int_Odh,,,,> rept 3 descr_idt <dummy,,,,> endm intllh descr_idt <dummy_err,,,,> rept 14 descr_idt <dummy,,,,> endm int20h descr_idt <new_08h,,,,> int21h desc r_idt <si rena,38,,,> rept 221 descr_idt <dummy,,,,> endm idt_seg ends Как видите, мы полностью описали всю таблицу прерываний. При этом мы инициализировали все дескрипторы именем процедур обработки прерываний. Основная часть дескрипторов имеет в качестве обработчиков процедуру dummy, то есть они будут обрабатываться одной программой. Для некоторых прерыва- ний мы ввели уникальные имена; эти прерывания будут иметь свои процедуры обработки в нашей программе. Обработчики прерываний Расположение и порядок следования процедур обработки прерываний в памя- ти может быть произвольным. Главное, не забывать поместить их адреса в со- ответствующие дескрипторы.
Загрузка регистра IDTR 451 При написании самих программ обработки прерываний мы должны помнить о том, что при возникновении некоторых прерываний в стек вслед за содержи- мым регистров eip, cs и eflags может записываться еще и код ошибки. Поэтому в общем случае процедура обработки прерывания должна происходить по сле- дующей схеме действий: 1. Снять со стека и проанализировать код ошибки (если он есть). 2. Сохранить в стеке используемые в обработчике регистры микропроцессора. 3. Выполнить необходимые действия, в том числе подготовить возможный ре- старт команды, вызвавшей прерывание. Подобный рестарт подразумевает возобновление выполнения прерванной программы, начиная с команды, инициировавшей процесс прерывания. 4. Восстановить сохраненные на шаге 2 регистры; 5. Выдать команду iret. Прерываний, для которых микропроцессор формирует код ошибки, немного — всего 8. В программе из листинга 17.1 приведены все возможные типы преры- ваний защищенного режима. Так, для исследования исключений типа ошибок взяты исключения 5 и 13 (ODh), а для ловушек — 3 и обработка прерывания пользователя. Программирование контроллера прерываний18259А На уроке 15 мы подробно разобрались с системой прерываний микропроцес- сора. Выяснили, что при загрузке BIOS производит инициализацию контрол- леров прерываний (ведущего и ведомого). При этом BIOS назначает им базо- вые номера: ведущему — 08h, ведомому — 70h. Из этого следует, что если мы разрешим обработку аппаратных прерываний в защищенном режиме, то первое же прерывание от таймера заставит микропроцессор через таблицу IDT обра- титься к процедуре dummy. Что же делать? Ничего не остается, как перепрограм- мировать ведущий контроллер на другое значение базового вектора. Ведомый контроллер перепрограммировать не нужно — значение его базового вектора ничем нам не мешает, так как оно находится в области, отведенной под преры- вания пользователя. Перепрограммировать контроллер прерываний мы уже умеем. Эту операцию мы проделывали на уроке 15. В листинге 17.1 мы ее просто повторим, назна- чив, к примеру, значение 20h базовому вектору ведущего контроллера. Загрузка регистра IDTR Аналогично таблице GDT нужно сообщить микропроцессору, где находится таблица IDT. Это делается с помощью специально выделенного для этой цели
452 Урок 17. Обработка прерываний в защищенном режиме регистра idt г. Таким образом, в защищенном режиме любая задача, имея доста- точный уровень привилегий, может создать свою среду для обработки прерыва- ний. Для загрузки регистра idtг можно использовать ту же структуру point, что и для регистра gdtr. Загрузка регистра idtг производится специальной командой lidt, которая имеет следующий формат: lidt адрес_48-битного_поля (Load IDT register) — загрузить регистр idt г. Команда lidt загружает системный регистр idt г содержимым 6-байтового поля, адрес которого указан в качестве операнда, point struc lim dw О adr dd 0 ends data segment point_idt point <idt_size,0> data ends code segment xor eax,eax mov ax,idt_seg shl eax,4 mov dword ptr point.idt.adr,eax Igdt pword point_idt code ends После этого можно перейти в защищенный режим и разрешить аппаратные прерывания. Выполнив работу в защищенном режиме, мы готовимся к обрат- ному переходу в реальный режим. Для этого, кроме восстановления вычисли- тельной среды этого режима, необходимо восстановить и соответствующий механизм прерываний. Для повышения наглядности программы вы можете процесс перепрограммирования контроллера оформить в виде процедуры или макрокоманды. Теперь соберем фрагменты в листинг 17.1. Листинг 17.1. Обработка прерываний в защищенном режиме .386Р разрешение инструкций 1386 .MODEL large include show.inc include mac.inc структура для описания дескрипторов сегментов descr STRUC limit dw 0
Загрузка регистра IDTR 453 base_1 dw О base_2 db О attr db О lim_atr db 0 base_3 db О ENDS ; макрос инициализации дескрипторов load_descr MACRO des,seg_addr,seg_size mov des.limit,seg_size xor eax,eax mov ax,seg_addr shl eax, 4 mov des.base_1,ax rol eax,16 mov des.base_2,al ENDM at rMACRO descr, bitl, bit2, bit3 atr_&descr=constp or bitl or bit2 or bit3 ENDM структура для описания псевдодескриптора gdtr и idtr point STRUC limdw 0 adrdd 0 ENDS ; структура для описания дескрипторов таблицы idt descr_idt STRUC offs_1 dw 0 seldw 30h ; селектор сегмента команд в таблице GDT no.use db О type_attrdb 8eh ; шлюз прерывания offs_2 dw О ENDS ; атрибуты для описания дескрипторов сегментов constp equ 10010000b code_r_n equ 00010000b code_r_y equ 00000010b data_wm_ n equ 00000000b data_wm_y equ 00000010b code.n equ 00000000b code_p equ 00000100b data_ equ 00000000b stack_ equ 00000000b продолжение
454 Урок 17. Обработка прерываний в защищенном режиме code. equ 00001000b data_stk equ 00000000b stksegment stack “stack” db256 dup (0) stkends ; сегмент с таблицей глобальных дескрипторов gdt.seg segment para public “data” use16 gdt_O descr <0,0,0,0,0,0> atrgdt_gdt_8,data_wm_y,data.,data.stk ; описывает саму GDT gdt_gdt_8 desc r <0,0,0,at r_gdt_gdt_8,0,0> gdt_ldt_10 descr <0,0,0,0,0,0> ;не используем atrgdt_ds_18,data.wm.y,data.,data.stk ; дескриптор сегмента данных gdt_ds_18 descr <0,0,0,atr_gdt_ds_18,0,0> atrgdt_vbf_20,data_wm_y,data., data.stk gdt_es_vbf_20 descr <0,0,0,atr_gdt_vbf_20,0,0> видеобуфер atrgdt_ss_28,data.wm.y,stack.,data.stk gdt_ss_28 descr <0,0,0, atr_gdt_ss_28,0,0> ; сегмент стека atrgdt_cs_30,code.r.y,code.n,code. gdt_cs_30 descr <0,0,0, atr_gdt_cs_30,0,0>; сегмент кода atrgdt_sirena_38,code.r.y,code.n,code. gdt.si rena_38 desc r <0,0,0,at r_gdt_si rena_38,0, 0> gdt_size=$-gdt_0-1 ; размер GDT минус 1 gdt.seg ends idt.seg segment para public “data" use16 intOOh descr.idt <dummy,,,,> REPT 2 descr.idt <dummy, ,,,> ENDM int03h descr.idt <int_03h,,,,> descr.idt <dummy,,,,> int05h descr.idt <int_05h,,,,> REPT 7 descr.idt <dummy_err,,,,> ENDM intOdh descr.idt <int_Odh,,,,> REPT 3 descr.idt <dummy,,,,> ENDM intllh descr.idt <dummy_err,,,,>
Загрузка регистра IDTR 455 REPT 14 descr_idt <dummy,,,,> ENDM int20h descrjldt <new_08h,,,, > int21h descr_idt <sirena,38h,,, > REPT 222 descrjldt <dummy,,, ,> ENDM idt_size=$-int00h-1 idt_seg ends ;данные программы data segment para public "data” use16 ;сегмент данных point_gdt point <gdt_size,0> point_idt point <idt_size,0> char db ”0" maskf db 07h position dw 2000 tonelow dw 2651 ; нижняя граница звучания 450 Гц cntdb 0 ; счётчик для выхода из программы temp dw ? ; верхняя граница звучания min_index dw О max_index dw 99 data_size=$-point_gdt-1 data ends sound segment byte private use16 assume cs: sound,ds:data,ss:stk sirena PROC пользовательское прерывание push ds push ax push ex push dx go: ; заносим слово состояния 10110110b(0B6h) в командный регистр (порт 43h) mov al,0B6h out 43h,al in al, 61h ; получим значение порта 61h в al oral,3 инициализируем динамик и подаем ток в порт 61h out 61h,al mov cx,2083 ; количество шагов ступенчатого изменения тона musicup: ;в ах значение нижней границы частоты mov ах,tonelow продолжение &
456 Урок 17. Обработка прерываний в защищенном режиме out 42h, al; в порт 42h младшее слово ах :а1 xchg al, ah ; обмен между al и ah out 42h, al; в порт 42h старшее слово ах: ah add tonelow,1 ; повышаем тон delay 1 ;задержка на 1 мкс mov dx, tonelow ; в dx текущее значение высоты mov temp.dx ; temp - верхнее значение высоты loop musicup ; повторить цикл повышения mov ex,2083 ; восстановить счетчик цикла musicdown mov ax,temp ;в ах верхнее значение высоты out 42h,al;e порт 42h младшее слово ах : al mov al, ah ; обмен между al и ah out 42h,al; в порт 42h старшее слово ах : ah sub temp, 1; понижаем высоту delay 1 ;задержка на 1 мкс loop musicdown ; повторить цикл понижения nosound: in al, 61h ; получим значение порта 61h в AL and al,OFCh ;выключить динамик out 61h, al; в порт 61h mov dx,2651 ;для последующих циклов mov tonelow,dx inc ent ;увеличиваем счётчик проходов, то есть ; количество звучаний сирены cmp ent, 5 ;5раз? jne go ;если нет - идти на метку до pop dx pop сх pop ах pop ds mov bp.sp mov eax,[bp] show eax.O mov eax,[bp+4] show eax,160 mov eax,[bp+8] show eax,320 db 66h iret endp sound_size=$-sirena-1 sound ends
Загрузка регистра IDTR 457 code segment byte public “code” use16 ; сегмент кода с 16-разрядным режимом адресации assume cs:code,ss:stk dummy proc ; исключения без кода ошибки mov ах,Offffh show ax,1600 db 66h iret endp dummy_err proc исключения с кодом ошибки pop eax ; снимаем co стека код ошибки и на экран show еах,1760 db 66h iret endp int_03h proc ;для этого исключения не формируется кода ошибки, ; поэтому анализируем содержимое eip в стеке и возвращаемся в программу pop еах show еах,0 push еах db 66h iret endp int_05h proc ; обработчик для 5-го исключения - команда bound ;код ошибки не формируется, поэтому корректируем ; индекс и выходим mov al, 5 mov ah, al mov si, 2 db 66h iret endp int.Odh proc pop eax ; снимаем co стека код ошибки sub bx,4 исправляем адрес - причину возникновения исключения db66h ; возвращаемся обратно и рестарт виноватой команды iret endp new_08h proc ; новое прерывание от таймера assume ds:data push ds push es продолжение &
458 Урок 17. Обработка прерываний в защищенном режиме push ax push bx mov ax,20h mov es.ax scr: mov al,char mov ah.maskf mov bx, position mov es:[bx],ax add bx,2 mov position,bx inc char pop bx pop ax pop es pop ds mov al,20h out 20h,al db66h iret endp new_8259a proc ;b al - значение нового базового ; вектора для ведущего контроллера push ах mov al, 00010001b out 20h, al; ICW1 в порт 20h jmp $+2 jmp $+2 ; задержка, чтобы успела отработать аппаратура pop ах out 21h, al; ICW2 в порт 20h - новый базовый номер jmp $+2 jmp $+2 ; задержка, чтобы успела ; отработать аппаратура mov al,00000100b out 21h,al; ICW3 - ведомый подключается ; к уровню 2 (см. рис. 15.1) jmp $+2 jmp $+2 ; задержка, чтобы успела ; отработать аппаратура mov al,00000001b out 21h,al;ICW4 - E0I выдает программа пользователя ret
Загрузка регистра IDTR 459 endp main proc mov ax,stk mov ss.ax наполняем таблицу глобальных дескрипторов assume ds:GDT_SEG mov ax,GDT_SEG mov ds,ax load jJescr gdt_gdt_8,GDT_SEG,gdt_size loadjJescr gdtjJs_1 8,DATA,data_size loadjJescr gdt_es_vbf_20,0b800h,3999 loadjJescr gdt_ss_28, STK, 255 loadjJescr gdt_cs_30,CODE,code_size loadjJescr gdt_sirena_38,SOUND,sound_size assume ds:data mov ax,data mov ds,ax нагружаем gdtr xor eax,eax mov ax,GDT_SEG shl eax,4 mov point j)dt. adr, eax Igdt point_gdt ;запрещаем прерывания cli mov al,80h out 70h,al mov al,20h ;новое значение базового вектора call new_8259A нагружаем idtr xor еах,eax mov ax,IDT_SEG shl eax,4 mov point_idt.adr, eax lidt point_idt ;переключаемся в защищенный режим mov eax.crO oral,1 mov crO.eax настраиваем регистры dbOeah ;машинный код команды jmp dwoffset protect ;смещение метки перехода ;в сегменте команд dw30h ;селектор сегмента кода в GDT protect: продолжение
460 Урок 17. Обработка прерываний в защищенном режиме [загрузить селекторы для остальных дескрипторов mov ах,18h mov ds,ах mov ax,20h mov es.ax mov ax,28h mov ss.ax ;работаем в защищенном режиме: [разрешаем прерывания от таймера, наблюдаем sti delay 3500 cli ;далее имитируем возникновение двух [исключительных ситуаций (типа ошибки): 5 и 13 mov si,130 bound si.dword ptr min_index mov bx,data_size mov ax,[bx] ;a теперь имитируем возникновение исключительной ситуации типа ловушки - 3: int 3 :обрабатываем их и в знак успеха запускаем ;из другого сегмента команд сирену int 21h ;готовимся к переходу в реальный режим :прерывания запрещены ;перепрограммируем контроллер mov al,08h call new_8259A [формирование дескрипторов для реального режима assume ds:GDT_SEG mov ax,8h mov ds,ax mov gdt_ds_18.limit,Offffh mov gdt_es_vbf_20.limit, Offffh mov gdt_ss_28.limit,Offffh mov gdt_cs_30.limit,Offffh assume ds:DATA [загрузка теневых дескрипторов mov ax,18h mov ds,ax mov ax,20h mov es.ax mov ax,28h mov ss.ax dbOeah
Загрузка регистра IDTR 461 dwoffset jump dw30h jump: mov eax.crO and al,Ofeh mov crO.eax dbOeah dwoffset rjnode dwCODE r_mode: mov ax,DATA mov ds,ax mov ax,STK mov ss,ax mov ax,3ffh mov point_idt.lim,ax xor eax,eax mov point_idt.adr,eax lidt point_idt ;разрешаем прерывания sti xor al,al out 70h,al окончание работы программы (стандартно) mov ax,4C00h int 21h main ENDP code_size=$-dummy code ends end main Ну вот и все! Остается надеяться, что читатель, мужественно прочитавший книгу до самого конца, смог почерпнуть для себя много нового и полезного. Хочется верить, что после первого прочтения книга приобретет новое качест- во — станет вашим настольным справочником по языку ассемблера. Подведем некоторые итоги: 0 Механизм прерываний защищенного режима гораздо более гибок, чем в ре- альном режиме. Механизмы прерываний реального и защищенного режимов несовместимы. 0 Ключевыми компонентами при обработке прерываний защищенного режи- ма являются таблица IDT и регистр idtr, в который загружаются адрес и размер таблицы IDT. 0 Все прерывания защищенного режима по типу запоминаемого контекста де- лятся на три группы: ошибки, ловушки и аварии. От того, какого типа воз-
462 Урок 17. Обработка прерываний в защищенном режиме никло прерывание, зависит, можно ли восстановить нормальное функциони- рование программы. 0 Для восьми прерываний микропроцессор формирует код ошибки, по кото- рому можно получить дополнительную информацию о характере и месте ошибки. 0 Таблица IDT содержит три типа дескрипторов, которые называют шлюзами: шлюзы ловушек, прерываний и задач. Тип дескриптора, а значит, и действия микропроцессора, определяет значение в поле типа. 0 При возникновении прерывания с некоторым номером п микропроцессор выбирает со смещением п*8 от начала IDT шлюз и далее действует в зависи- мости от типа этого шлюза. Шлюзы ловушек и прерываний работают прак- тически одинаково; отличие заключается только в том, что когда управление передается через шлюз прерывания, то микропроцессор сбрасывает в ноль флаг прерывания IF. Поэтому шлюзы прерываний обычно используются для обработки аппаратных и тех программных прерываний, для которых жела- тельно запрещение прерываний от аппаратуры во время их работы. Шлюзы ловушек можно применять для обработки исключительных ситуаций, а так- же для перехода к обработчикам программных прерываний, для которых не требуется сброс флага IF. 0 Шлюзы задач представляют собой совершенно иной способ перехода к проце- дуре обработки прерывания, основанный на механизме переключения задач. 0 При разработке программы, переводящей микропроцессор в защищенный режим без поддержки операционной системы, требуется производить пере- программирование ведущего контроллера прерываний. Это вызвано тем, что программы загрузки BIOS инициализируют базовый вектор ведущего кон- троллера прерываний значением 08h для реального режима работы микро- процессора. Значение базового вектора ведомого контроллера менять не нужно, так как его значение (70h) находится вне диапазона номеров исключений, зарезервированных для исцрльзования микропроцессором в защищенном режиме.
ПРИЛОЖЕНИЕ Опции транслятора TASM и редактора связей TLINK
В данном приложении приведены опции командной строки для транслятора Turbo Assembler фирмы Borland1 (TASM) (версия 3.0 и выше) и редактора свя- зей TLINK. Во избежание несовместимости используйте программы TLINK и TASM одной версии. Таблица П1.1. Опции транслятора TASM Опции Значение /а, /s /а — сегменты в объектном файле должны быть размещены в алфавитном порядке; /s — сегменты в объектном файле следуют в порядке их опи- сания в программе /с Указание на включение в файл листинга с информацией о перекрестных ссылках /<1имя_идентификатора (“значение] Определяет идентификатор; эквивалент директивы ассемб- лера =, как если бы она была записана вначале исход- ного текста программы /е. /г /е — генерация инструкций эмуляции операций с плава- ющей точкой; /г — разрешение трансляции действительных инструкций с плавающей точкой, которые должны выполняться реаль- ным арифметическим сопроцессором Л/? Вывод на экран справочной информации; эквивалентно запуску TASM без параметров /шуть Задает путь к включаемому по директиве INCLUDE файлу; л у синтаксис аргумента «путь» такой же, как для кома- нды PATH файла autoexec.bat /]директива_ТА8М Определяет директивы, которые будут транслироваться перед началом трансляции исходного файла программы на ассемблере. В директиве не должно быть аргументов 1 В настоящее время — Inprise. — Примеч. ред.
465 Опции Значение /khn Задает максимальное количество идентификаторов, которое может содержать исходная программа, то есть, фактически, задается размер таблицы символов транслятора. По умолча- нию программа может содержать до 16 384 идентификато- ров. Это значение можно увеличить (но не более чем до 32 768) или уменьшить до п. Сигналом к тому, что необ- ходимо использовать данный параметр, служит появление сообщения 4 Out of hash space» (4 Буферное пространство ис- черпано») /1, /1а /1 указывает на необходимость создания файла листинга, даже если он не 4заказывается» в командной строке; /1а — показать в листинге код, вставляемый транслятором для организации интерфейса с языком высокого уровня по директиве MODEL /ml, /тх, /mu /ml — различать во всех идентификаторах прописные и строчные буквы; /тх — различать строчные и прописные символы во внеш- них и общих идентификаторах. Это важно при компоновке с программами на тех языках высокого уровня, в которых строчные и прописные символы в идентификаторах различа- ются; /mu — воспринимать все символы идентификаторов как прописные /mvn Определение максимальной длины идентификаторов. Мини- мальное значение п равно 12 /тп Установка количества (и) проходов транслятора TASM. По умолчанию транслятор выполняет один проход. Максималь- но, при необходимости, можно задать выполнение до 5 про- ходов /п Не выдавать в файле листинга таблицы идентификаторов (в таких таблицах содержатся все имена идентификаторов и их значения) /os, /о, /ор, /oi Генерация оверлейного кода /р Проверять наличие кода с побочными эффектами при работе в защищенном режиме /Ч Удаление из объектной программы лишней информации, не- нужной на этапе компоновки /1 Подавление вывода всех сообщений при условном ассембли- ровании, кроме сообщений об ошибках (то есть тестирова- ние программы на предмет выявления синтаксических ошибок) — - — — продолжение &
466 Приложение 1. Опции транслятора TASM и редактора связей TLINK Таблица П1.1. (продолжение) Опции Значение /wO, /wl, /w2 Генерация предупреждающих сообщений разного уровня полноты: wO — сообщения не генерируются; wl, w2 — сообщения генерируются /w-ххх, /w+ххх Генерация предупреждающих сообщений класса ххх (эти же функции выполняют директивы WARN и NOWARN). Знак «-» означает «запретить генерацию сообщений класса ххх». Знак «+» означает «разрешить генерацию сообщений класса ххх». Классы предупреждающех сообщений обозначаются идентификатором из трех символов: ALN — выравнивание сегмента в памяти; ASS — подразумевается использование 16-разрядного сег- мента; BRK — требуются квадратные скобки; ICG — неэффективная генерация кода; LCO — переполнение счетчика адреса; OPI — открытый блок условия IF; OPP — открытая процедура; OPS — открытый сегмент; OVF — арифметическое переполнение; PDC — конструкция, зависящая от прохода; PRO — запись в память в защищенном режиме требует пе- реопределения регистра CS. Использование этого класса предупреждений имеет смысл при написании программ, ра- ботающих в защищенном режиме (под Windows). На уро- ке 16 обсуждался момент, связанный с тем, что в защищен- ном режиме запрещено производить запись в сегмент кода. Класс предупреждений PRO призван уже на стадии трансля- ции программы предупредить такого рода ошибки; RES — предупреждение о резервируемом слове; TPI — предупреждение о недопустимости в Turbo Pascal. /W+ — разрешить все сообщения; /W- — запретить все сообщения /X Включить в листинг все блоки условного ассемблирования для директив IF, IFNDEF, IFDEF и т. п., в том числе и не- выполняющиеся /z При возникновении ошибок наряду с сообщением о них вы- водить соответствующие строки текста /zi, /zd, /zn /zi — включить в объектный файл информацию для отладки; /zd — поместить в объектный файл информацию о номерах строк, что необходимо для работы отладчика на уровне ис- ходного текста программы; /zn — запретить помещение в объектный файл отладочной информации
467 Опция Значение А /т /S Не создавать файл карты (тар) Создать файл карты То же, что /т, но дополнительно в файл карты включается информа- ция о сегментах (адрес, длина в байтах, класс, имя сегмента и т. д.) /1 /п /С Создать раздел в файле карты с номерами строк Игнорировать библиотеки, указываемые другими компиляторами Различать строчные и прописные буквы в идентификаторах (в том числе и внешних) /V /з /d Л Включить отладочную информацию в выполняемый файл Поддержка 32-битного кода Предупреждать о дублировании символов в компонуемых библиотеках Создать файл типа .сот (по умолчанию .ехе)
Z ) ПРИЛОЖЕНИЕ Описание системы команд микропроцессоров Intel
Материал, приведенный в данном приложении, связан с уроком 6, на котором мы рассматривали формат машинной команды микропроцессора и систему его команд в целом. Порядок описания команд будет следующим: О название команды с расшифровкой ее мнемонического обозначения — это облегчит процесс запоминания и последующего использования команды в соответствии с ее функциональным назначением; О синтаксическое описание команды, поясняющее возможные сочетания опе- рандов для данной команды. При этом сложные синтаксические описания будут приведены в виде синтаксических диаграмм, что позволит в наиболее компактной форме изобразить все возможные сочетания операндов; О состояние флагов после выполнения команды; О описание типового применения команды с примером и (или) ссылка на урок, в котором демонстрируется пример применения команды; О список команд, которые функционально связаны с данной командой. При описании команд будем применять следующие обозначения. Для описания состояния флагов после выполнения некоторой команды будем использовать выборку из таблицы, отражающей структуру регистра флагов eflagOs: 31 18 17 16 15 14 13 12 И 10 09 08 07 06 05 04 03 02 01 00 0 0 VM RF 0 NT IO PL OF DF IF TF SF ZF 0 AF 0 PF 1 CF В нижней строке этой таблицы будут приводиться значения флагов после вы- полнения команды. При этом используются следующие обозначения: 1 — после выполнения команды флаг устанавливается (равен 1); 0 — после выполнения команды флаг сбрасывается (равен 0); г — значение флага зависит от результата работы команды; ? — после выполнения команды флаг не определен; пробел — после выполнения команды флаг не изменяется.
470 Приложение 2. Описание системы команд микропроцессоров Intel Для представления операндов в синтаксических диаграммах используются сле- дующие обозначения: О г8, г 16, г32 — операнд в одном из регистров размером байт, слово или двой- ное слово; О ш8, т16, ш32, ш48 — операнд в памяти размером байт, слово, двойное слово или 48 бит; О i8, 116, 132 — непосредственный операнд размером байт, слово или двойное слово; О а8, а16, а32 — относительный адрес (смещение) в сегменте кода. И еще об обозначениях на синтаксических диаграммах. На многих диаграммах в целях компактности возможные сочетания операндов показаны в виде следу- ющей конструкции (рис. П2.1). -| r8,16,32 HSH т8,16,32]— Рис. П2.1. Описание сочетания операндов в синтаксических диаграммах Конструируя команду на основе подобной синтаксической диаграммы, вы дол- жны помнить о соответствии типов. В подобной диаграмме допустимы только следующие сочетания: «г8, т8», «г16, т16», «г32, т32». Например, сочетание «г8, ml6» недопустимо. Однако есть единичные случаи, когда подобные сочета- ния возможны; тогда они оговариваются специальным образом. Описанная в данном приложении система команд в полном объеме поддержи- вается микропроцессором Pentium. Предыдущие модели микропроцессоров Intel могут не поддерживать отдельные команды. Чтобы прояснить этот мо- мент, мы будем указывать в примерах для каждой команды директиву типа .286. Это будет означать, что описываемая команда поддерживается всеми мо- делями микропроцессора, начиная с 1286. Если ничего не указывается, то это означает, что данная команда работает на всех моделях микропроцессоров Intel, начиная с i8086/8088. ААА (Ascii Adjust after Addition) ASCII-коррекция после сложения ааа Назначение: корректировка неупакованного результата сложения двух одноразрядных не- упакованных BCD-чисел.
AAA (Ascii Adjust after Addition) 471 Синтаксис: aaa Алгоритм работы: О Проанализировать значение младшего полубайта регистра al и значение флага af; О если (значение младшего полубайта регистра al >9) или (AF=1), то выпол- нить следующие действия: 1. Увеличить значение al на 6. 2. Очистить старший полубайт регистра al. 3. Увеличить значение ah на 1. 4. Установить флаги afel, cf-l; О иначе сбросить флаги af=0 и cf=0. Состояние флагов после выполнения команды: 11 07 06 04 02 00 OF SF ZF AF PF CF ? ? ? r ? г Применение: Обычно команда ааа используется после сложения каждого разряда распако- ванных BCD-чисел командой add. Каждая цифра неупакованного BCD-числа занимает младший полубайт байта. Если результат сложения двух одноразряд- ных BCD-чисел больше 9, то число в младшем полубайте результата не есть BCD-число. Поэтому результат нужно корректировать командой ааа. Эта ко- манда позволяет сформировать правильное BCD-число в младшем полубайте и запомнить единицу переноса в старший разряд путем увеличения содержимого регистра ah на 1. К примеру, сложить два неупакованных BCD-числа: 08+05 mov ah,08h ;ah=08h mov al,05h ;al= 05h add al,ah ;al=al+ah=05h+08h=0dh - не BCD-число xor ah,ah ;ah=O aaa ;ah=01h,al=03h - результат скорректирован См. также: урок 8, приложение 7 и команды aad, aam, aas, daa, das
472 Приложение 2. Описание системы команд микропроцессоров Intel AAD (Ascii Adjust before Division) ASCII-коррекция перед делением aad Назначение: О подготовка двух неупакованных BCD-чисел для операции деления; О преобразование двузначного неупакованного BCD-числа меньшего 63h (9910) в двоичное представление. Синтаксис: aad Алгоритм работы: О Умножить значение регистра ah на 10 и сложить полученное значение с со- держимым регистра al: (ahri0)+al; О присвоить регистру al значение (ahxl0)+al; О обнулить регистр ah. Состояние флагов после выполнения команды: И 07 06 04 02 00 OF SF ZF AF PF CF ? г г ? г ? Применение: Команду aad используют для подготовки двузначного неупакованного BCD-чис- ла в регистре ах для операции деления. Так как в системе команд микропроцес- сора нет команды деления для BCD-чисел, такое число нужно предварительно преобразовать к двоичному виду. Для этого старший разряд двузначного BCD-числа помещается в регистр ah, умножается на 10 и складывается с раз- рядом единиц двузначного BCD-числа в регистре al. В результате этих дей- ствий и получается соответствующее двоичное число в регистре ах. Далее в программе уже можно применять обычную команду деления div, оперирую- щую двоичными данными. Команду aad можно применять и просто для преобразования неупакованного двузначного BCD-числа в его двоичный эквивалент. Есть еще интересный мо- мент — если посмотреть на коды символов шестнадцатеричных цифр в табли- це ASCII, то видно, что они похожи на BCD-числа. Исключение составляет лишь значение старшей тетрады (для BCD-числа это так называемая зона с нулевым значением) — оно равно 3. Можно сделать вывод, что если предвари- тельно обнулить значение старшей тетрады для кодов двух символов (от 0 до 9), то эту команду вполне можно применять и для преобразования двузнач-
AAM (Ascii Adjust after Multiply) 473 ных десятичных чисел в символьном представлении в их двоичный эквива- лент, что и отражено в названии команды. Для иллюстрации рассмотрим два примера: Пример 1. Разделить десятичное число 18 на 9. Подготовить результат к выво- ду на экран. mov ah.Olh ;ah=O1h mov al,08h ;al=08h => ax=0108h mov bl,09 ;bl=09h aad ;al=12h - двоичный эквивалент •.десятичного числа 18 div bl ;al=02h,ah=00h or al,30h ;al=32h - ASCII-представление числа 2, ;можно выводить на экран Пример 2. Преобразовать десятичное число 16 в символьном виде в эквивалент- ное двоичное число. ;поместим число 16 в символьном виде в регистр ах mov ax,3136h ;ax=3136h and ax.OfOfh ;ax=0106h aad ;al=10h - получили его двоичный эквивалент См. также: уроки 3, 8, приложение 7 и команды ааа, aam, aas, daa, das ААМ (Ascii Adjust after Multiply) ASCII-коррекция после умножения aam Назначение: О корректировка результата умножения двух неупакованных BCD-чисел; О преобразование двоичного числа меньшего 63h (9910) в его неупакованный BCD-эквивалент. Синтаксис: aam Алгоритм работы: О Разделить значение регистра al на 10; О записать частное в регистр ah; остаток — в регистр al.
474 Приложение 2. Описание системы команд микропроцессоров Intel Состояние флагов после выполнения команды: и 07 06 04 02 00 OF SF ZF AF PF CF ? г г ? г ? Применение: Команду aam используют для коррекции результата умножения двух неупако- ванных BCD-чисел. Специальной команды умножения BCD-чисел нет. Поэто- му BCD-числа умножаются поразрядно как обычные двоичные числа коман- дой mul. Максимальное число, которое получается при таком умножении, — это 9х9=8110=5116. Отсюда понятно, что значения, для которых командой aam можно получить их двузначный BCD-эквивалент в регистре ах, находятся в дипазоне от 00h до 51h. Эту команду можно применять и для преобразования двоичного числа из ре- гистра ах (в диапазоне от 0 до 63h) в его десятичный эквивалент (соответ- ственно, из диапазона от 0 до 9910). Пример 1. Умножить десятичное число 8 на 9. Подготовить результат к выво- ду на экран. mov ah,08h ;ah=08h mov al,09h ;al= 09h mul ah ;al=48h - двоичный эквивалент 72 aam ;ah=07h,al=02h or ax,3030h ;ax=3732h - ASCII-представление ;числа 72 Пример 2. Преобразовать двоичное число 60h в эквивалентное десятичное число. •.поместим число 60h в регистр ах mov ax,60h ;ax=60h aam ;ax=0906h - получили десятичный эквивалент числа 60h or ax,3030h символьный эквивалент ах=3936 ;можно выводить на экран См. также: урок 8, приложение 7 и команды ааа, aad, aas, daa, das AAS (Ascii Adjust after Substraction) ASCII-коррекция после вычитания aas Назначение: корректировка результата вычитания двух неупакованных одноразрядных BCD-чисел.
AAS (Ascii Adjust after Substraction) 475 Синтаксис: aas Алгоритм работы: Если (младший полубайт регистра al меньше 9) или (флаг af=l), то выполнить следующие действия: О уменьшить значение младшего полубайта регистра al на 6; О обнулить значение старшего полубайта регистра al; О установить флаги af и cf в 1; иначе установить флаги af и cf в 1. Состояние флагов после выполнения команды: и 07 06 04 02 00 OF SF ZF AF PF CF ? ? ? г ? г Применение: Команду aas используют для коррекции результата вычитания двух неупако- ванных одноразрядных BCD-чисел после команды sub. Операндами в команде sub должны быть правильные одноразрядные BCD-числа. Рассмотрим возмож- ные варианты вычитания одноразрядных BCD-чисел: О 5-9 — для вычитания необходимо сделать заем в старшем разряде. Факт такого заема в микропроцессоре фиксируется установкой флагов cf и af в 1 и вычитанием 1 из содержимого ah. В результате после команды aas в реги- стре al получается правильное значение (модуль результата), которое для нашего примера (с учетом заема из старшего разряда) составляет 6. Одно- временно моделируется заем из старшего разряда, что позволяет произво- дить вычитание длинных чисел. О 8-6 — для вычитания нет необходимости делать заем в старшем разряде. Поэтому производится сброс флагов cf и af в 0, a ah не изменяется. В ре- зультате после команды aas в регистре al получается правильное значение (модуль результата), которое для нашего примера составляет 2. Пример 1. Вычесть десятичное число 8 из 5. Подготовить результат к выводу на экран. mov al,05h mov bl,08h sub al, bl ;al=Ofdh aas ;al=07, cf=af=1 or al,30h ;al=37h - код символа 7 ;вывод результата на экран
476 Приложение 2. Описание системы команд микропроцессоров Intel mov ah,2 mov dl,al int 21h См. также: уроки 3, 8, приложение 7 и команды ааа, aad, aam, daa, das ADC (Addition with Carry) Сложение с переносом adc приемник,источник Назначение: сложение двух операндов с учетом переноса из младшего разряда. Синтаксис: Рис. П2.2. Синтаксическое описание команды adc Алгоритм работы: О Сложить два операнда; О поместить результат в первый операнд: приемник = приемник + источник; О в зависимости от результата установить флаги. Состояние флагов после выполнения команды: И 07 06 04 02 00 OF SF ZF AF PF CF г г г г г r
ADD (ADDition) 477 Применение: Команда adc используется при сложении длинных двоичных чисел. Ее можно использовать как самостоятельно, так и совместно с командой add. При совмест- ном использовании команды adc с командой add сложение младших байтов/ слов/двойных слов осуществляется командой add, а уже старшие байты/слова/ двойные слова складываются командой adc, учитывающей переносы из млад- ших разрядов в старшие. Таким образом команда adc значительно расширяет диапазон значений складываемых чисел. В приложении 7 приведен пример программы сложения двоичных чисел произвольной размерности. .data S11 dd 01fe544fh S12 dd 005044cdh elderREZ db 0 ;для учета переноса из старшего ;разряда результата rez dd 0 . code mov ах,s!1 add ax,s!2 ;сложение младших слов слагаемых mov rez,ах mov ах,sl+2 adc ax,sl2+2 ;сложение старших слов слагаемых плюс cf mov rez+2,ах adc elderREZ,0 ;учесть возможный перенос См. также: урок 8, приложение 7 и команды add, sub, sbb, xadd ADD (ADDition) Сложение add приемник,источник Назначение: сложение двух операндов источник и приемник размерностью байт, слово или двойное слово.
478 Приложение 2. Описание системы команд микропроцессоров Intel Синтаксис: Рис. П2.3. Синтаксическое описание команды add Алгоритм работы: О Сложить операнды источник и приемник; О записать результат сложения в приемник; О установить флаги. Состояние флагов после выполнения команды: И 07 06 04 02 00 OF SF ZF AF PF CF г г г г г г Применение: Команда add используется для сложения двух целочисленных операндов. Ре- зультат сложения помещается по адресу первого операнда. Если результат сло- жения выходит за границы операнда приемник (возникает переполнение), то учесть эту ситуацию следует путем анализа флага cf и последующего возмож- ного применения команды adc. Например, сложим значения в регистре ах и области памяти ch. При сложении следует учесть возможность переполнения: chicle dw 2015 rez dd 0 add ax.chislo ;(ax)=(ax)+ch mov word ptr rez,ax jne dop_sum ;переход, если результат ;не вышел за разрядную сетку
479 AND (logical AND) adc word ptr rez+2,0 расширить результат, для ;учета переноса ;в старший разряд dop_sum: См. также: урок 8, приложение 7 и команды adc, sub, sbb, xadd AND (logical AND) Логическое И and приемник,источник Назначение: операция логического умножения для операндов приемник и источник размерно- стью байт, слово или двойное слово. Синтаксис: Рис. П2.4. Синтаксическое описание команды and Алгоритм работы: О Выполнить операцию логического умножения над операндами источник и приемник: каждый бит результата равен 1, если соответствующие биты опе- рандов равны 1, в остальных случаях бит результата равен 0; О записать результат операции в приемник; О установить флаги.
480 Приложение 2. Описание системы команд микропроцессоров Intel Состояние флагов после выполнения команды: и 07 06 02 00 OF SF ZF PF CF 0 г г г 0 Применение: Команда and используется для логического умножения двух операндов. Резуль- тат операции помещается по адресу первого операнда. Эту команду удобно ис- пользовать для принудительной установки или сброса определенных битов операнда. Например, преобразуем двузначное упакованное BCD-число в его символьный эквивалент: u_BCD s_ch db 25h dw 0 ;упакованное BCD-число ;место для результата xor ax, ax ;очистка ах mov al,u_BCD shl ax, 4 ;ах=0250 mov al,u_BCD ;ах=0225 ;преобразование в символьное представление and ax,3f3fh ;ax=3235h mov s_ch,ax См. также: уроки 9, 12 и команды or, xor, test BOUND (check array BOUNDs) Контроль нахождения индекса массива в границах bound индекс,границы массива Назначение: проверка нахождения значения индекса в границах массива. Синтаксис: Рис. П2.5. Синтаксическое описание команды bound
BOUND (check array BQUNDs) 481 Алгоритм работы: Сравнить значение в регистре индекс с двумя значениями, расположенными последовательно в ячейке памяти, адресуемой операндом границы массива. Диа- пазон значений индекса определяется используемым регистром индекс: О если это 16-разрядный регистр общего назначения, то содержащееся в нем значение проверяется на попадание в диапазон значений, которые находят- ся в двух последовательных словах в памяти по адресу, указываемому вто- рым операндом. Эти два значения являются, соответственно, значениями нижней и верхней границ массива; О если это 32-разрядный регистр общего назначения, то содержащееся в нем значение проверяется на попадание в диапазон значений, которые находят- ся в двух последовательных двойных словах в памяти по адресу, указывае- мому вторым операндом. Эти два значения являются, соответственно, ниж- ней и верхней границами массива Если в результате проверки значение из регистра вышло за пределы указанно- го диапазона, то возбуждается прерывание с номером 5, если нет — программа продолжает выполнение. Состояние флагов после выполнения команды: выполнение команды не влияет на флаги. Применение: Команду bound очень удобно использовать для контроля выхода за нижнюю или верхнюю границы массива. Значения этих границ должны быть предвари- тельно помещены в два последовательных слова (двойных слова) в памяти. Адрес этих слов (двойных слов) указывается вторым операндом. Далее дина- мически в ходе работы программы значение в регистре индекс, указываемом первым операндом, сравнивается со значениями этих двух границ, и если ниж- няя_граница<=(индекс)<=верхняя_граница, то программа продолжает выполнение. В противном случае генерируется исключительная ситуация 5 (int 5). Далее в программе обработки этой ситуации можно выполнить необходимую коррек- тировку и вернуться в программу (см. урок 17). Фрагмент, который можно использовать при обработке одномерного массива с размерностью элементов в слово: .286 ;это обязательная директива, так как bound входит ;в систему команд микропроцессоров, начиная с 1286 .data BoundMas label word Low_Bound dw 0 Upp_Bound dw 20 mas dw 10 dup (?) xor di,di ;очистка индексного регистра
482 Приложение 2. Описание системы команд микропроцессоров Intel cycl: mov ax,mas[di] ; перебор элементов массива add di,2 bound di.BoundMas ;если значение в di не будет попадать в границы, то будет ;вызван обработчик прерывания 5, где можно скорректировать ;значение ip/eip в стеке с тем, чтобы выйти из бесконечного ‘.цикла, например, на метку М2 или выполнить другие действия jmp cycl М2: См. также: урок 17 и команду iret BSF (Bit Scan Forward) Побитовое сканирование вперед bsf результат,источник Назначение: для проверки наличия единичных битов в операнде источник. Синтаксис: bsf г16 |---О—I г16,т16 Ц— г32 |--О—| г32,т32|- Рис. П2.6. Синтаксическое описание команды bsf Алгоритм работы: О Просмотр битов операнда источник, начиная с бита 0 и заканчивая битом 15/31, до тех пор, пока не встретится единичный бит; О если встретился единичный бит, то флаг zf устанавливается в 0 и в регистр первого операнда записывается номер позиции, где встретился единичный бит. Диапазон значений зависит от разрядности второго операнда: для 16-разрядного операнда это 0...15; для 32-разрядного — это 0...31; О если единичных битов нет, то флаг zf устанавливается в 1. Состояние флагов после выполнения команды: 06 ZF г
BSR (Bit Scan Reverse) 483 Применение: Команду bsf используют при работе на битовом уровне для определения пози- ции в операнде крайних справа единичных битов. Например, сдвинем содержимое регистра Ьх вправо таким образом, чтобы ну- левой бит стал единичным: .386 mov bx,0002h ;bx=0000 0010b bsf cx,bx ;cx=0001h jz null shr bx.cl ;bx=0000 0001b null: См. также: уроки 9, 12 и команду bsr BSR (Bit Scan Reverse) Побитовое сканирование назад bsr результат,источник Назначение: проверка наличия единичных битов в операнде источник. Синтаксис: Рис. П2.7. Синтаксическое описание команды bsr Алгоритм работы: О Просмотр битов операнда источник, начиная со старшего бита 15/31 и за- канчивая битом 0, до тех пор пока не встретится единичный бит; О если встретился единичный бит, флаг zf устанавливается в 0 и в регистр первого операнда записывается номер позиции (отсчет осуществляется от- носительно нулевой позиции), где встретился самый старший единичный бит. Диапазон значений зависит от разрядности второго операнда: для 16-разрядного операнда это 0...15; для 32-разрядного — 0...31; О если единичных битов нет, флаг zf устанавливается в 1.
484 Приложение 2. Описание системы команд микропроцессоров Intel Состояние флагов после выполнения команды: 06 ZF г Применение: Команду bs г используют при работе на битовом уровне для определения пози- ции крайних слева единичных битов. Например, сдвинем содержимое регистра Ьх вправо таким образом, чтобы стар- ший единичный бит исходного значения в Ьх переместился в нулевую пози- цию: .386 mov bx,41h bsr cx.bx ;cx=06h jz null shr bx.ax ;bx=0001h null:... См. также: уроки 9, 12 и команду bsf BSWAP (Byte SWAP) Перестановка байтов bswap источник Назначение: О изменение порядка следования байтов; О переход от одной формы адресации к другой. Под формой адресации здесь понимается принцип «младший байт по младше- му адресу» или обратный ему. Существует ряд систем, например использую- щих микропроцессоры Motorola или большие ЭВМ, где применяется обратный принцип размещения многобайтовых значений, чем в микропроцессорах Intel. Поэтому эту команду можно использовать для разработки программ-конверто- ров между подобными платформами и IBM PC. Синтаксис: bswap г32
ВТ (Bit Test) 485 Алгоритм работы: Исходное состояние г32 0 8 16 24 31 Состояние г32 после выполнения команды Рис. П2.8. Схематическое изображение алгоритма работы команды bswap Состояние флагов после выполнения команды: выполнение команды не влияет на флаги. Применение: Команду bswap используют для изменения формы адресации. В качестве опе- ранда может быть указан только 32-разрядный регистр. Эта команда использу- ется в моделях микропроцессоров, начиная с i486: .486 mov ebx,1a2c345fh bswap ebx ;ebx=5f342c1ah См. также: урок 7 и команду xchg ВТ (Bit Test) Проверка битов bt источник,индекс Назначение: извлечение значения заданного бита в флаг cf. Синтаксис: Рис. П2.9. Синтаксическое описание команды bt Алгоритм работы: О Получить бит по указанному номеру позиции индекс в операнде источник; О установить флаг cf согласно значению этого бита.
436 Приложение 2. Описание системы команд микропроцессоров Intel Состояние флагов после выполнения команды: 00 CF г Применение: Команду bt используют для определения значения конкретного бита в операн- де источник. Номер проверяемого бита задается содержимым второго операнда (значением из диапазона 0...31). После выполнения команды флаг cf устанав- ливается в соответствии со значением проверяемого бита: .386 mov ebx,01001100h bt ebx,8 ;проверка состояния бита 8 ;и установка cf в 1 jc ml ;перейти на ml, если ;проверяемый бит равен 1 См. также: уроки 9, 12 и команды btc, btr, bts, test BTC (Bit Test and Complement) Проверка бита с инверсией (дополнением) btc источник,индекс Назначение: извлечение значения заданного бита в флаг cf и изменение его значения в опе- ранде на (обратное. Синтаксис: Рис. П2.10. Синтаксическое описание команды btc Алгоритм работы: О Получить значение бита с номером позиции индекс в операнде источник; О инвертировать значение выбранного бита в операнде источник; О установить флаг cf исходным значением бита.
BTR (Bit Test and Reset) 487 Состояние флагов после выполнения команды: 00 CF г Применение: Команда btc используется для определения и инвертирования значения кон- кретного бита в операнде источник. Номер проверяемого бита задается содер- жимым второго операнда index (значением из диапазона 0...31). После выпол- нения команды флаг cf устанавливается в соответствии с исходным значением бита, то есть тем, которое было до выполнения команды: .386 mov ebx,01001100h ;проверка состояния бита 8 и его обращение: btc ebx,8 ;cf=1 и ebx=01001000h См. также: уроки 9, 12 и команды bt, btr, bts, test BTR (Bit Test and Reset) Проверка бита с его сбросом в 0 btr источник,индекс Назначение: извлечение значения заданного бита в флаг cf и изменение его значения на нулевое. Синтаксис: ---1 btr h-т—j Г16.32 HwT r16,32 h— 4 m16,32 hM-l I8 P Рис. П2.11. Синтаксическое описание команды btr Алгоритм работы: О Получить значение бита с указанным номером позиции в операнде источник; О установить флаг cf значением выбранного бита; О установить значение исходного бита в операнде в 0.
488 Приложение 2. Описание системы команд микропроцессоров Intel Состояние флагов после выполнения команды: 00 CF г Применение: Команда bt г используется для определения значения конкретного бита в опе- ранде источник и его сброса в 0. Номер проверяемого бита задается содержи- мым второго операнда индекс (значением из диапазона 0...31). В результате выполнения команды флаг cf устанавливается в соответствии со значением исходного бита, то есть тем, что было до выполнения операции: .386 mov ebx,01001100h ;проверка состояния бита 8 и его сброс в 0 btr ebx,8 ;cf=1 и ebx=01001000h См. также: уроки 9, 12 и команды bt, btc, bts, test BTS (Bit Test and Set) Проверка бита с его установкой в 1 bts источник,индекс Назначение: извлечение значения заданного бита операнда в флаг cf и установка этого бита в единицу. Синтаксис: Рис. П2.12. Синтаксическое описание команды bts Алгоритм работы: О Получить значение бита с указанным номером позиции в операнде источник; О установить флаг cf значением выбранного бита; О установить значение исходного бита в операнде источник в 1. Состояние флагов после выполнения команды: 00 CF
CALL (CALL) 489 Применение: Команда bts используется для определения значения конкретного бита в опе- ранде источник и установки проверяемого бита в 1. Номер проверяемого бита задается содержимым второго операнда index (значением из диапазона 0...31). После выполнения команды флаг cf устанавливается в соответствии со значе- нием исходного бита, то есть тем, что было до выполнения операции: .386 mov ebx,01001100h проверка состояния бита 0 и его установка в 1 bts ebx,0 ;cf=O ebx=01001001h См. также: уроки 9, 12 и команды bt, btc, btr, test CALL (CALL) Вызов процедуры или задачи call цель Назначение: О передача управления близкой или дальней процедуре с запоминанием в сте- ке адреса точки возврата; О переключение задач. Синтаксис: Рис. П2.13. Синтаксическое описание команды call Алгоритм работы: Определяется типом операнда: О метка ближняя — в стек заносится содержимое указателя команд eip/ip, и в этот же регистр загружается новое значение адреса, соответствующее метке; О метка дальняя — в стек заносится содержимое указателя команд eip/ip и cs. Затем в эти же регистры загружаются новые значения адресов, соответству- ющие дальней метке;
490 Приложение 2. Описание системы команд микропроцессоров Intel О г16,32 или т16,32 — определяют регистр или ячейку памяти, содержащие смещения в текущем сегменте команд, куда передается управление. При пе- редаче управления в стек заносится содержимое указателя команд eip/ip; О указатель на память — определяет ячейку памяти, содержащую указатель размером 4 или 6 байт на вызываемую процедуру. Структура такого указа- теля 2+2 или 2+4 байта. Интерпретация такого указателя зависит от режи- ма работы микропроцессора: © в реальном режиме — в зависимости от размера адреса (use16 или use32) первые 2 байта трактуются как сегментный адрес, вторые 2/4 байта — как смещение целевой метки передачи управления. В стеке запоминается содержимое регистров cs и eip/ip; © в защищенном режиме — интерпретация цели передачи управления за- висит от значения байта AR дескриптора, определяемого селекторной час- тью указателя. Целью здесь является дальний вызов процедуры без изме- нения уровня привилегий, дальний вызов процедуры с изменением уров- ня привилегий или переключение задачи. Состояние флагов после выполнения команды (кроме переключения задачи): выполнение команды не влияет на флаги. При переключении задачи значения флажков изменяются в соответствии с информацией о регистре eflags в сегменте состояния TSS задачи, на которую производится переключение. Применение: Как видно из описания алгоритма, команда call позволяет организовать гиб- кую и многовариантную передачу управления на подпрограмму с сохранением адреса точки возврата. Подробно типовые примеры использования рассмотре- ны на уроках 10 и 14. См. также: уроки 10, 14 и команду ret CBW/CWDE (Convert Byte to Word/Convert Word to Double Word Extended) Преобразование байта в слово/слова в двойное слово cbw cwde Назначение: расширение операнда со знаком.
CLC (CLear Carry flag) 491 Синтаксис: cbw cwde Алгоритм работы команды cbw: При работе команда использует только регистры а1 и ах: О анализ знакового бита регистра а1: о если знаковый бит а1=0, то ah=OOh; о если знаковый бит а1=1, то ah=Offh. Алгоритм работы команды cwde: При работе команда использует только регистры ах и еах: О анализ знакового бита регистра ах: © если знаковый бит ах=0, то установить старшее слово eax=0000h; © если знаковый бит ах=1, то установить старшее слово eax=Offffh. Состояние флагов после выполнения команды: выполнение команды не влияет на флаги. Применение: Данные команды используются для приведения операндов к нужной размернос- ти с учетом знака. Такая необходимость может, в частности, возникнуть при программировании арифметических операций. .386 ;только для cwde, cwd была для 18086 mov ebx,10fecd23h mov ax,-3 ;ax=1111 1111 1111 1101b cwde ;eax=1111 1111 1111 1111 1111 1111 1111 1101b add eax,ebx См. также: урок 8 и команды cbq, cwd CLC (CLear Carry flag) Сброс флага переноса clc Назначение: сброс флага переноса cf. Синтаксис: clc
492 Приложение 2. Описание системы команд микропроцессоров Intel Алгоритм работы: Установка флага cf в ноль. Состояние флагов после выполнения команды: 00 CF 0 Применение: Данная команда используется для сброса флага cf в ноль. Такая необходимость может возникнуть при работе с командами сдвига, арифметическими команда- ми либо действиями по индикации обнаружения ошибок и различных ситуа- ций в программе: clc ;cf=O См. также: уроки 8, 9 и команды cmc, stc CLD (CLear Direction flag) Сброс флага направления cld Назначение: сброс в ноль флага направления df. Синтаксис: cld Алгоритм работы: Установка флага df в ноль. Состояние флагов после выполнения команды: 10 DF 0 Применение: Данная команда используется для сброса флага df в ноль. Такая необходимость может возникнуть при работе с цепочечными командами. Нулевое значение флага df вынуждает микропроцессор при выполнении цепочечных операций производить инкремент регистров si и di: cld ;df=O См. также: урок 11 и команды std, movs, cmps, seas, lods, stos, ins, outs
CMC (CoMplement Carry flag)493 CLI (CLear Interrupt flag) Сброс флага прерывания cli Назначение: сброс флага прерывания if. Синтаксис: cli Алгоритм работы: Установка флага if в ноль. Состояние флагов после выполнения команды: 09 IF 0 Применение: Данная команда используется для сброса флага if в ноль. Такая необходимость может возникнуть при разработке программ обработки прерываний: cli ;if=0 См. также: урок 15 и команды int, iret, sti CMC (CoMplement Carry flag) Инвертирование флага переноса cmc Назначение: изменение значения флага переноса cf на обратное. Синтаксис: cmc Алгоритм работы: Инвертирование значения флага переноса cf. Состояние флагов после выполнения команды: 00 CF г
494 Приложение 2. Описание системы команд микропроцессоров Intel Применение: Данная команда используется для изменения значения флага cf на противопо- ложное. В частности, этот флаг можно использовать для связи с процедурой и по его состоянию судить о результате работы данной процедуры. После выхода из процедуры этот флаг можно проанализировать командой условного перехо- да jc: prod proc cmc prod endp call prod jc ml ;если cf=1, то переход на ml ml: См. также: уроки 8, 9, 15 и команды clc, stc, jc, jne CMP (CoMPare operands) Сравнение операндов cmp операнд!,операнд2 Назначение: сравнение двух операндов. Синтаксис: Рис. П2.14. Синтаксическое описание команды стр
CMPS/CMPSB/CMPSW/CMPSD (CoMPare String Byte/Word/Double word operands) 495 Алгоритм работы: О Выполнить вычитание (операнд1-операнд2); О в зависимости от результата установить флаги, операнд1 и операнд2 не изме- нять (то есть результат не запоминать). Состояние флагов после выполнения команды: И 07 06 04 02 00 OF SF ZF AF PF CF г г г г г г Применение: Данная команда используется для сравнения двух операндов методом вычита- ния, при этом операнды не изменяются. По результатам выполнения команды устанавливаются флаги. Команда стр применяется с командами условного пе- рехода и командой установки байта по значению setcc: len equ 10 cmp ах,len jne ml ;переход если (ax)oien jmp m2 ;переход если (ax)=len См. также: уроки 8, 10, 11, 12 и команды cmps, cmpxchg, sub, jcc, setcc CMPS/CMPSB/CMPSW/CMPSD (CoMPare String Byte/Word/Double word operands) Сравнение строк байтов/слов/двойных слов cmps источник, приемник empsb empsw empsd Назначение: сравнение двух последовательностей (цепочек) элементов в памяти.
496 Приложение 2. Описание системы команд микропроцессоров Intel Синтаксис: Рис. П2.15. Синтаксическое описание команд сравнения цепочек Алгоритм работы: О Выполнить вычитание элементов (источник-приемник), адреса элементов предварительно должны быть загружены: адрес источника в пару регистров ds:esi/si; адрес приемника в пару регистров es:edi/di; О в зависимости от состояния флага df изменить значение регистров esi/si и edi/di: если df=O, то увеличить содержимое этих регистров на длину элемента последовательности; если df=1, то уменьшить содержимое этих регистров на длину элемента последовательности; О в зависимости от результата вычитания установить флаги: если очередные элементы цепочек не равны, то cf=1, zf=0; если очередные элементы цепочек или цепочки в целом равны, то cf=0, zf=1; О при наличии префикса выполнить определяемые им действия (см. команды гере/герпе). Состояние флагов после выполнения команды: 11 07 06 04 02 00 OF SF ZF AF PF CF г г г r r r Применение: Команды без префиксов осуществляют простое сравнение двух элементов в памяти. Размеры сравниваемых элементов зависят от применяемой команды. Команда cmps может работать с элементами размером в байт, слово, двойное
CMPS/CMPSB/CMPSW/CMPSD (CoMPare String Byte/Word/Double word operands) 497 слово. В качестве операндов в команде указываются идентификаторы последо- вательностей этих элементов в памяти. Реально эти идентификаторы использу- ются лишь для получения типов элементов последовательностей, а их адреса должны быть предварительно загружены в указанные выше пары регистров. Транслятор, обработав команду cmps и выяснив тип операндов, генерирует одну из машинных команд: cmpsb, cmpsw или cmpsd. Машинного аналога для ко- манды cmps нет. Для адресации приемника обязательно должен использоваться регистр es, а для адресации источника можно делать замену сегмента с исполь- зованием соответствующего префикса. Для того чтобы эти команды можно было использовать для сравнения после- довательности элементов, имеющих размерность байт, слово, двойное слово, не- обходимо использовать один из префиксов гере или герпе. Префикс гере застав- ляет циклически выполняться команды сравнения до тех пор, пока содержимое регистра есх/сх не станет равным нулю или пока не совпадут очередные срав- ниваемые элементы цепочек (флаг zf=1). Префикс герпе заставляет циклически производить сравнение до тех пор, пока не будет достигнут конец цепочки (есх/сх=0) либо не встретятся различающиеся элементы цепочек (флаг zf=0): .data obl1 db "Строка для сравнения" obl1 db "Строка ля сравнения" a_obl1 dd оЬП a_obl2 dd оЬ12 .code cld ;просмотр цепочки в направлении ;возрастания адресов mov сх,20 ;длина цепочки Ids si,a_obl1 ;адрес источника в пару ds:si les di,a_obl2 ;адрес назначения в пару ds:si гере cmpsb сравнивать пока равны jnz ml ;если не конец цепочки, то ;встретились разные элементы ...;действия, если цепочки совпали ml: ;действия, если цепочки не совпали См. также: уроки 10, И и команды ins, lods, movs, outs, seas, stos, repe, repz, repne, repnz
498 Приложение 2. Описание системы команд микропроцессоров Intel CMPXCHG (CoMPare and eXCHanGe) Сравнение и обмен cmpxchg приемник,источник(аккумулятор) Назначение: сравнение значений и обмен ими между источником и приемником. Синтаксис: Рис. П2.16. Синтаксическое описание команды cmpxchg Алгоритм работы: О Выполнить сравнение элементов источник и приемник; О если источник и приемник не равны, то: о установить zf=0; о переслать содержимое операнда приемник в источник (регистр al/ax/eax). О если источник и приемник равны, то: О установить zf=1; О переслать содержимое операнда источник (регистр al/ax/eax) по месту операнда приемник. Состояние флагов после выполнения команды: и 07 06 04 02 00 OF SF ZF AF PF CF г г г г г г Применение: Команды сравнивают два операнда. Один из сравниваемых операндов находит- ся в аккумуляторе (регистре al/ax/eax), другой может находиться в памяти или регистре общего назначения. Если значения равны, то производится заме- на содержимого операнда приемник содержимым источника, находящимся в регистре-аккумуляторе. Если значения не равны, то производится замена со- держимого операнда источник, находящимся в регистре-аккумуляторе, содер- жимым операнда назначения. Определить тот факт, была ли произведена смена
CWD (Convert Word to Double word) 499 значения в аккумуляторе (то есть были ли не равны сравниваемые операнды), можно по значению флага zf: .486 mov ax,114eh mov bx,8e70h cmpxchg bx,ax jz ml :переход если zf=1, то есть •«операнды равны и ах не изменился •«действия, если операнды не равны ml: См. также: уроки 7, 10 и команды cmp, xchg CWD (Convert Word to Double word) Преобразование слова в двойное слово cwd Назначение: расширение слова со знаком до размера двойного слова со знаком. Синтаксис: cwd Алгоритм работы: Копирование значения старшего бита регистра ах во все биты регистра dx. Состояние флагов после выполнения команды: выполнение команды не влияет на флаги. Применение: Команда cwd используется для расширения значения знакового бита в регистре ах на биты регистра dx. Данную операцию, в частности, можно использовать для подготовки к операции деления, для которой размер делимого должен быть в два раза больше размера делителя, либо для приведения операндов к одной размерности в командах умножения, сложения, вычитания: mov ах,25 mov bx,4 cwd div bx См. также: урок 8 и команды cbw, cdq, cwde, div, idiv, mul, imul, add, adc, sub, sbb
500 Приложение 2. Описание системы команд микропроцессоров Intel CDQ (Convert Double word to Quad word) Преобразование двойного слова в учетверенное слово cdq Назначение: расширение двойного слова со знаком до размера учетверенного слова (64 бита) со знаком. Синтаксис: cdq Алгоритм работы: Копирование значения старшего бита регистра еах на все биты регистра edx. Состояние флагов после выполнения команды: выполнение команды не влияет на флаги. Применение: Команду cdq можно использовать для распространения значения знакового бита в регистре еах на все биты регистра edx. Данную операцию, в частности, можно использовать для подготовки к операции деления, для которой размер делимого должен быть в два раза больше размера делителя: .386 delimoe dd delitel dd mov eax,delimoe cdq idiv delitel ;частное в еах, остаток в edx См. также: урок 8 и команды cbw, cwd, cwde, div, idiv DAA (Decimal Adjust for Addition) Десятичная коррекция после сложения daa Назначение: коррекция упакованного результата сложения двух BCD-чисел в упакованном формате. Синтаксис: daa
DAA (Decimal Adjust for Addition) 501 Алгоритм работы: Команда работает только с регистром al и анализирует наличие следующих ситуаций: О Ситуация 1. В результате предыдущей команды сложения флаг af=1 или значение младшей тетрады регистра а1>9. Напомним, что флаг af устанав- ливается в 1 в случае переноса двоичной единицы из бита 3 младшей тет- рады в старшую тетраду регистра al (если значение превысило Ofh). Нали- чие одного из этих двух признаков говорит о том, что значение младшей тетрады превысило 9h. О Ситуация 2. В результате предыдущей команды сложения флаг cf=1 или значение регистра al>9f h. Напомним, что флаг cf устанавливается в 1 в слу- чае переноса двоичной единицы в старший бит операнда (если значение превысило Offh в случае регистра al). Наличие одного из этих двух призна- ков говорит о том, что значение в регистре al превысило 9fh. Если имеет место одна из этих двух ситуаций, то регистр al корректируется следующим образом: О для ситуации 1 содержимое регистра al увеличивается на 6; О для ситуации 2 содержимое регистра al увеличивается на 60h; О если имеют место обе ситуации, то корректировка начинается с младшей тетрады. Состояние флагов после выполнения команды (в случае, если были переносы): И 07 06 04 02 00 OF SF ZF AF PF CF г г г 1 г 1 Состояние флагов после выполнения команды (в случае, если переносов не было): И 07 06 04 02 00 OF SF ZF AF PF CF г г г 0 г 0 Применение: Эту команду следует применять после сложения двух упакованных BCD-чисел с целью корректировки получающегося двоичного результата сложения в пра- вильное двузначное десятичное число. После команды daa следует анализиро- вать состояние флага cf. Если он равен 1, то это говорит о том, что был пере- нос единицы в старший разряд и это нужно учесть для сложения старших десятичных цифр BCD-числа: mov al,69h ;69h - упакованное BCD-число mov bl,74h ;74h - упакованное BCD-число adc al, bl ;al=Oddh daa ;cf=1, al=43h
502 Приложение 2. Описание системы команд микропроцессоров Intel ;если перенос, то переход на ту ветвь программы, ;где он будет учтен: jc ml См. также: урок 8, приложение 7 и команды ааа, aad, aam, aas, das DAS (Decimal Adjust for Subtraction) Десятичная коррекция после вычитания das Назначение: коррекция упакованного результата вычитания двух BCD-чисел в упакован- ном формате. Синтаксис: das Алгоритм работы: Команда das работает только с регистром al и анализирует наличие следую- щих ситуаций: О Ситуация 1. В результате предыдущей команды сложения флаг af =1 или значение младшей тетрады регистра а1>9. Напомним, что для случая вычи- тания флаг af устанавливается в 1 в случае заема двоичной единицы из старшей тетрады в младшую тетраду регистра al. Наличие одного из этих двух признаков говорит о том, что значение младшей тетрады превысило 9h и его нужно корректировать. О Ситуация 2. В результате предыдущей команды сложения флаг cf =1 или зна- чение регистра al>9fh. Напомним, что для случая вычитания флаг cf устанав- ливается в 1 в случае заема двоичной единицы. Наличие одного из этих двух признаков говорит о том, что значение в регистре al превысило 9fh. Если имеет место одна из этих ситуаций, то регистр al корректируется следую- щим образом: О для ситуации 1 содержимое регистра al уменьшается на 6; О для ситуации 2 содержимое регистра al уменьшается на 60h; О если имеют место обе ситуации, то корректировка начинается с младшей тетрады. Состояние флагов после выполнения команды (в случае, если были переносы): И 07 06 04 02 00 OF SF ZF AF PF CF г г г 1 г 1
DEC (DECrement operand by 1) 503 Состояние флагов после выполнения команды (в случае, если переносов не было): И 07 06 04 02 00 OF SF ZF AF PF CF г г г 0 г 0 Применение: Команду das следует применять после вычитания двух упакованных BCD-чи- сел с целью корректировки получающегося двоичного результата вычитания в правильное двузначное десятичное число. После команды das следует анализи- ровать состояние флага cf. Если он равен 1, то это говорит о том, что был заем единицы в старший разряд, и это нужно учесть в дальнейших действиях. Если у вычитаемого нет больше старших разрядов, то результат следует трактовать как отрицательное двоичное дополнение. Для определения его абсолютного значения нужно вычесть 100 из результата в al. Если у вычитаемого еще есть старшие разряды, то факт заема нужно просто учесть уменьшением младшего из этих оставшихся старших разрядов на единицу: mov al,44h mov bl,27h sub al,bl ;al=1dh das ;al=17h, cf=O jc ml ;был ли заем? В нашем случае - нет. См. также: урок 8, приложение 7 и команды ааа, aad, aam, aas, daa DEC (DECrement operand by 1) Уменьшение операнда на единицу dec операнд Назначение: уменьшение значения операнда в памяти или регистре на 1. Синтаксис: dec r8,16,32 Н- т8,16,32Н Рис. П2.17. Синтаксическое описание команды dec Алгоритм работы: Команда вычитает 1 из операнда.
504 Приложение 2. Описание системы команд микропроцессоров Intel Состояние флагов после выполнения команды: И 07 06 04 02 OF SF ZF AF PF г г г г г Применение: Команда dec используется для уменьшения значения байта, слова, двойного слова в памяти или регистре на единицу. При этом заметьте, что команда не воздействует на флаг cf: mov al,9 dec al ; al=8 См. также: урок 8 и команды inc, sub DIV (DIVide unsigned) Деление беззнаковое div делитель Назначение: выполнение операции деления двух двоичных беззнаковых значений. Синтаксис: — div -Н г8,16,32 L- Чт8,16,32Н Рис. П2.18. Синтаксическое описание команды div Алгоритм работы: Для команды необходимо задание двух операндов — делимого и делителя. Де- лимое задается неявно, и размер его зависит от размера делителя, который ука- зывается в команде: О если делитель размером в байт, то делимое должно быть расположено в ре- гистре ах. После операции частное помещается в al, а остаток — в ah; О если делитель размером в слово, то делимое должно быть расположено в паре регистров dx:ах, причем младшая часть делимого находится в ах. Пос- ле операции частное помещается в ах, а остаток — в dx; О если делитель размером в двойное слово, то делимое должно быть располо- жено в паре регистров edx:eax, причем младшая часть делимого находится в еах. После операции частное помещается в еах, а остаток — в edx.
ENTER (setup parameter block for ENTERing procedure) 505 Состояние флагов после выполнения команды: и 07 06 04 02 00 OF SF ZF AF PF CF ? ? ? ? ? ? Применение: Команда выполняет целочисленное деление операндов с выдачей результата де- ления в виде частного и остатка от деления. При выполнении операции деления возможно возникновение исключительной ситуации 0 — ошибка деления. Эта ситуация возникает в одном из двух случаев: делитель равен 0 или частное слишком велико для его размещения в регистре еах/ах/а1: mov ах,10234 mov bl,154 div bl ;аЬ=остаток, а!=частное См. также: урок 8, приложение 7 и команду idiv ENTER (setup parameter block for ENTERing procedure) Установка кадра стека для параметров процедуры enter loC-Size,lex lev Назначение: установка границы в стеке для локальных переменных процедуры. Синтаксис: — enter h-fO Рис. П2.19. Синтаксическое описание команды enter Алгоритм работы: О Поместить текущее значение регистра ebp/bp в стек; О сохранить текущее значение esp/sp в промежуточной переменной fp (имя переменной выбрано случайно); О если лексический уровень вложенности (операнд lex_lev) не равен нулю, то (lex_lev-1) раз делать следующее: • в зависимости от установленного режима адресации use16 или use32 выпол нить вычитание (Ьр-2) или (ebp-4) $ записать результат обратно в ebp/bp;
506 Приложение 2. Описание системы команд микропроцессоров Intel • сохранить значение ebp/bp в стеке; • сохранить в стеке значение промежуточной переменной f р; О записать значение промежуточной переменной fp в регистр ebp/bp; О уменьшить значение регистра esp/sp на величину, заданную первым операн- дом, минус размер области локальных переменных loc_size: esp/sp=(esp/sp)-loc_size. Состояние флагов после выполнения команды: выполнение команды не влияет на флаги Применение: Команда enter специально введена в систему команд микропроцессора для поддержки блочно-структурированных языков высокого уровня типа Pascal или С. В этих языках программа разбивается на блоки. В блоках можно опи- сать свои собственные (локальные) идентификаторы, которые не могут быть использованы вне этого блока. К примеру, на рисунке ниже в виде блоков изображена структура некоторой программы. Процедура А Е описание переменных процедуры А Процедура В L описание переменных процедуры В Процедура С [Т описание переменных процедуры С Процедура D описание переменных процедуры D Рис. П2.20. Изображение структуры некоторой программы в виде блоков В правом верхнем углу каждого блока (процедуры) стоит номер лексического уровня вложенности этого блока относительно других блоков программы. Большинство блочно-структурированных языков в качестве основного метода распределения памяти для переменных в блоках используют автоматическое распределение памяти. Это означает, что при входе в блок (при вызове проце- дуры и т. п.) в некотором месте оперативной памяти (или в стеке) выделяется область памяти для переменных этого блока (ее можно назвать областью ини- циализации). После выхода из этого блока связь программы с этой областью
ENTER (setup parameter block for ENTERing procedure) 507 теряется, то есть эти переменные становятся недоступными. Но если, как в нашем примере, в этой процедуре есть вложенные блоки (процедуры), то для некоторого внутреннего блока (например, С) могут быть доступны области инициализации (переменные) блоков, объемлющих данный блок. В нашем примере для блока С доступны также переменные блоков В и А, но не D. Воз- никает вопрос: как же программа, находясь в конкретной точке своего выпол- нения, может отслеживать то, какие области инициализации ей доступны? Это делается с помощью структуры данных, называемой дисплеем. Дисплей содер- жит указатели на самую последнюю область текущего блока и на области ини- циализации всех блоков, объемлющих данный блок в программе. Например, если в программе А была вызвана сначала процедура В, а затем С, то дисплей содержит указатели на области инициализации А, В и С (см. рисунок ниже). с в А Области Дисплей инициализации Если после этого вызвать процедуру D (в то время как В и С еще не заверше- ны), то картина изменится. После того как некоторый блок (процедура) завершает свою работу, ее область инициализации удаляется из памяти (стека) и одновременно соответствующим образом корректируется дисплей. Большинство языков высокого уровня хранят локальные данные блоков в сте- ке. Эти переменные называют еще автоматическими или динамическими. Па- мять для них резервируется путем уменьшения значения регистра-указателя стека esp/sp на величину, равную длине области, занимаемой этими динамичес- кими переменными. Доступ к этим переменным осуществляется посредством регистра ebp/bp. Если один блок вложен в другой, то для его динамических (локальных) переменных также выделяется место (кадр) в стеке, но в этот кадр помещается указатель на кадр стека для включающего его блока. Команды enter и leave как раз и позволяют поддержать в языке ассемблера принципы работы с переменными блоков, как в блочно-структурированных языках. Дис- плей организуется с помощью второго операнда команды enter и стека.
508 Приложение 2. Описание системы команд микропроцессоров Intel Например, в начале работы главной процедуры А и после вызова процедуры В кадр стека будет выглядеть так: После вызова В Стек Стек Соответственно, после вызова процедур С и D стек будет выглядеть, как пока- зано ниже. После вызова D 0000 esp 0000 После вызова С (ebp) Переменные процедуры В_______ Значение(ebp)p Значение(ebp)g Значение(еЬр)д Значение(ebp)g Переменные процедуры В Значение(еЬр)в Значение(еЬр)д значение (еЬр)д Переменные процедуры А Значение(еЬр)д Старое значение ebp Дисплей Переменные процедуры D_______ Значение (ebp)p Значение(еЬр)д Значение (ebp)A Переменные процедуры В_______ Значение (ebp)p Значение(ebp)B Значение (ebp)A Значение (ebp)g Переменные процедуры В Значение(ebp)g Значение(ebp)A Значение(еЬр)д Переменные процедуры А Значение (ebp)A Старое значение ebp Дисплей D Стек Стек
HIT (HaLT) 509 Таким образом, видно, что, используя дисплей, мы, фактически, имеем адреса областей инициализации, доступных по признаку вложенности объемлющих блоков. Обратный процесс завершения работы с блоками и удаления соответст- вующих областей инициализации поддерживается командой leave: .286 prod ргос ;зарезервировать в стеке место для локальных переменных ; prod 16 байт [лексический уровень вложенности О enter 16,0 leave ret prod endp См. также: урок 14 и команды leave, ret HLT (HaLT) Останов hit Назначение: останов микропроцессора до прерывания или перезагрузки. Синтаксис: hit Алгоритм работы: Перевод микропроцессора в состояние останова. Состояние флагов после выполнения команды: выполнение команды не влияет на флаги. Применение: В результате выполнения команды микропроцессор переходит в состояние ос- танова. Из этого состояния его можно вывести сигналами на входах RESET, NMI, INTR. Если для возобновления работы микропроцессора используется прерыва- ние, то сохраненное значение пары cs:eip/ip указывает на команду, следую- щую за hit. Для иллюстрации применения данной команды рассмотрим еще один способ переключения микропроцессора из защищенного в реальный режим и его воз- врата обратно в реальный режим (см. урок 16). Как известно, в микропроцес- соре не предусмотрено специальных средств для подобного переключения. Сброс микропроцессора можно инициировать, если вывести байт со значением Of eh в порт клавиатуры 64h. После этого микропроцесор переходит в реальный
510 Приложение 2. Описание системы команд микропроцессоров Intel режим и управление получает программа BIOS, которая анализирует байт от- ключения в CMOS-памяти по адресу Ofh. Для нас интерес представляют два значения этого байта — 5h и Oah: О 5h — сброс микропроцессора вызывает инициализацию программируемого контроллера прерываний на значение базового вектора 08h (см. уроки 15 и 17). Далее управление передается по адресу, который находится в ячейке области данных BIOS 0040:0067; О Oah — сброс микропроцессора инициирует непосредственно передачу управ- ления по адресу в ячейке области данных BIOS 0040:0067 (то есть без пере- программирования контроллера прерываний). Таким образом, если вы не используете прерываний, то достаточно установить байт Ofh в CMOS-памяти в Oah. Предварительно, конечно, вы должны инициа- лизировать ячейку области данных BIOS 0040:0067 значением адреса, по кото- рому необходимо передать управление после сброса. Для программирования CMOS-памяти используются номера портов 070h и 071 h. Вначале в порт 070h заносится нужный номер ячейки CMOS-памяти, а затем в порт 071 h — новое значение этой ячейки: ;работаем в реальном режиме, готовимся к переходу ;в защищенный режим: push es mov ax,40h mov es.ax mov word ptr es:[67h],offset ret_real ; ret_real - метка в программе, с которой должно ;начаться выполнение программы после сброса mov es:[69h],cs mov al,Ofh :будем обращаться к ячейке Ofh в CMOS out 70h,al jmp $+2 ;чуть задержимся, чтобы аппаратура ;отработала •.сброс без перепрограммирования контроллера mov al,Oah out 71h,al ;переходим в защищенный режим установкой ;бита 0 сгО в 1 (см. урок 16) ;работаем в защищенном режиме ;готовимся перейти обратно в реальный режим mov al.Olfch out 64h,al ;сброс микропроцессора hit ;останов до физического ;окончания процесса сброса ret.real: ... ;метка, на которую будет передано управление после сброса См. также: уроки 15, 16, 17
IDIV (Integer Divide) 511 IDIV (Integer Divide) Деление целочисленное co знаком idiv делитель Назначение: операция деления двух двоичных значений со знаком. Синтаксис: idiv Ч т8,16,32 Н Рис. П2.23. Синтаксическое описание команды idiv Алгоритм работы: Для команды необходимо задание двух операндов — делимого и делителя. Де- лимое задается неявно, и размер его зависит от размера делителя, местонахож- дение которого указывается в команде: О если делитель размером в байт, то делимое должно быть расположено в ре- гистре ах. После операции частное помещается в al, а остаток — в ah; О если делитель размером в слово, то делимое должно быть расположено в паре регистров dx:ax, причем младшая часть делимого находится в ах. Пос- ле операции частное помещается в ах, а остаток — в dx; О если делитель размером в двойное слово, то делимое должно быть располо- жено в паре регистров edx:еах, причем младшая часть делимого находится в еах. После операции частное помещается в еах, а остаток — в edx. Остаток всегда имеет знак делимого. Знак частного зависит от состояния зна- ковых битов (старших разрядов) делимого и делителя. Состояние флагов после выполнения команды: и 07 06 04 02 00 OF SF ZF AF PF CF ? ? ? ? ? ? Применение: Команда выполняет целочисленное деление операндов с учетом их знаковых разрядов. Результатом деления являются частное и остаток от деления. При выполнении операции деления возможно возникновение исключительной си- туации 0 — ошибка деления. Эта ситуация возникает в одном из двух случаев:
512 Приложение 2. Описание системы команд микропроцессоров Intel делитель равен 0 или частное слишком велико для его размещения в регистре eax/ax/al: ;деление слов mov ах,1045 ;делимое mov bx,587 ;делитель cwd расширение делимого dx:ax idiv bx ;частное в ах, остаток в dx См. также: урок 8, приложение 7 и команду div IMUL (Integer MULtiply) Умножение целочисленное со знаком imul множитель_1 imul множ_1,множ_2 imul рез-т,множ_1,множ_2 Назначение: операция умножения двух целочисленных двоичных значений со знаком. Синтаксис: Рис. П2.24. Синтаксическое описание команды imul Алгоритм работы: Алгоритм работы команды зависит от используемой формы команды. Форма команды с одним операндом требует явного указания местоположения только одного сомножителя, который может быть расположен в ячейке памяти или регистре. Местоположение второго сомножителя фиксировано и зависит от размера первого сомножителя: О если операнд, указанный в команде, — байт, то второй сомножитель распо- лагается в al;
IMUL (Integer Multiply) 513 О если операнд, указанный в команде, — слово, то второй сомножитель распо- лагается в ах; О если операнд, указанный в команде, — двойное слово, то второй сомножи- тель располагается в еах. Результат умножения для команды с одним операндом помещается также в строго определенное место, определяемое размером сомножителей: О при умножении байтов результат помещается в ах; О при умножении слов результат помещается в пару dx:ax; О при умножении двойных слов результат помещается в пару edx:eax. Команды с двумя и тремя операндами однозначно определяют расположение результата и сомножителей следующим образом: О в команде с двумя операндами первый операнд определяет местоположение первого сомножителя. На его место впоследствии будет записан результат. Второй операнд определяет местоположение второго сомножителя; О в команде с тремя операндами первый операнд определяет местоположение результата, второй операнд — местоположение первого сомножителя, тре- тий операнд может быть непосредственно заданным значением размером в байт, слово или двойное слово. Состояние флагов после выполнения команды: И 07 06 04 02 00 OF SF ZF AF PF CF г ? ? ? ? г Команда imul устанавливает в ноль флаги of и cf, если размер результата соот- ветствует регистру назначения. Если эти флаги отличны от нуля, то это озна- чает, что результат слишком велик для отведенных ему регистром назначения рамок, и необходимо указать больший по размеру регистр для успешного за- вершения данной операции умножения. Конкретно условиями сброса флагов of и cf в ноль являются следующие условия: О для однооперандной формы команды imul — регистры ax/dx/edx являются знаковыми расширениями регистров al/ax/eax; О для двухоперандной формы команды imul - для размещения результата умножения достаточно размерности указанных регистров назначения г16/г32; О то же для трехоперандной команды умножения. Применение: Команда выполняет целочисленное умножение операндов с учетом их знако- вых разрядов. Для выполнения этой операции необходимо наличие двух со- множителей. Размещение и задание их местоположения в команде зависят от формы применяемой команды умножения, которая, в свою очередь, определя- ется моделью микропроцессора. Так, для микропроцессора 18086 возможна только однооперандная форма команды, для последующих моделей микропро-
514 Приложение 2. Описание системы команд микропроцессоров Intel цессоров дополнительно можно использовать двух- и трехоперандные формы этой команды: .486 mov bx,186 imul eax,bx,8 ;если результату не хватило размерности операнда!, ;то перейдем на ml, где скорректируем ситуацию: jc ml См. также: урок 8, приложение 7 и команду mul IN (INput operand from port) Ввод операнда из порта in аккумулятор,ном порта Назначение: ввод значения из порта ввода-вывода. Синтаксис: Рис. П2.25. Синтаксическое описание команды in Алгоритм работы: Передает байт, слово, двойное слово из порта ввода-вывода в один из регист- ров al/ax/eax. Состояние флагов после выполнения команды: выполнение команды не влияет на флаги. Применение: Команда применяется для прямого управления оборудованием компьютера по- средством портов. Номер порта задается вторым операндом в виде непосред- ственного значения или значения в регистре dx. Непосредственным значением можно задать порт с номером в диапазоне 0-255. При использовании порта с большим номером используется регистр dx. Размер данных определяется раз- мерностью первого операнда и может быть байтом, словом, двойным словом. В качестве примера применения рассмотрим фрагмент обработчика прерыва- ния от клавиатуры с номером 9. Это прерывание вызывается всякий раз при нажатии клавиши на клавиатуре. Обработчик этого прерывания должен про- читать скан-код клавиши, подтвердить микропроцессору клавиатуры факт
INC (INCrement operand by 1) 515 приема скан-кода, преобразовать код в соответствии состоянием клавишей пере- ключателей и поместить преобразованный код в буфер клавиатуры, находящий- ся в области BIOS. Чтение и подтверждение приема скан-кода могут выглядеть, к примеру, так: in al, 60h ; читаем скан-код push ax ; сохраним его на время in al,61h ;читаем порт 61h or al,80h устанавливаем старший бит ;байта из порта 61h в 1 out pop 61h,al ax ;подтверждаем факт приема i out 61h,al восстановили байт в порту скан-кода 61h См. также: урок 7 и команды out, ins, outs INC (INCrement operand by 1) Увеличить операнд на 1 inc операнд Назначение: увеличение значения операнда в памяти или регистре на 1. Синтаксис: Рис. П2.26. Синтаксическое описание команды inc Алгоритм работы: Команда увеличивает операнд на единицу. Состояние флагов после выполнения команды: 11 07 06 04 02 OF SF ZF AF PF г г г г г Применение: Команда используется для увеличения значения байта, слова, двойного слова в памяти или регистре на единицу. При этом команда не воздействует на флаг cf: inc ах увеличить значение в ах на 1 См. также: урок 8 и команды dec, add, adc
516 Приложение 2. Описание системы команд микропроцессоров Intel INS/INSB/INSW/INSD (Input String Byte/Word/Double word operands) Ввод строк байтов/слов/двойных слов из порта ins приемник,порт insb insw insd Назначение: ввод из порта в память последовательности байт, слов, двойных слов. Синтаксис: Рис. П2.27. Синтаксическое описание команд ввода цепочки из порта ins Алгоритм работы: О Передать данные из порта ввода-вывода, номер которого загружен в регистр dx, в память по адресу es:edi/di; О в зависимости от состояния флага df изменить значение регистров edi/di: • если df=0, то увеличить содержимое этих регистров на длину структур- ного элемента последовательности; • если df=1, то уменьшить содержимое этих регистров на длину структур- ного элемента последовательности; О при наличии префикса выполнить определяемые им действия (см. коман- ду гер). Состояние флагов после выполнения команды: выполнение команды не влияет на флаги. Применение: Команда вводит данные из порта ввода-вывода, номер которого загружен в ре- гистр dx, в память по адресу es:edi/di. Сегментная составляющая адреса долж- на быть обязательно в регистре es. Замена сегментного регистра недопустима. Непосредственное задание порта в команде также недопустимо — для этого используется регистр dx. Размеры вводимых элементов зависят от применяе-
INT (INTerrupt) 517 мой команды. Команда ins может работать с элементами размером в байт, слово, двойное слово. В качестве операндов в команде указывается символическое имя ячейки памяти, в которую вводятся элементы из порта ввода-вывода. Реально это символическое имя используется лишь для получения типа элемента после- довательности, а его адрес должен быть предварительно загружен в пару регистров es:edi/di. Транслятор, обработав команду ins и выяснив тип опе- ранда, генерирует одну из машинных команд: insb, insw или insd. Машинного аналога для команды ins нет. Для того чтобы эти команды можно было использовать для ввода последова- тельности элементов, имеющих размерность байт, слово, двойное слово, необ- ходимо использовать префикс гер. Префикс гер заставляет циклически выпол- няться команду ввода до тех пор, пока содержимое регистра есх/сх не станет равным нулю: .286 ;ввести 10 байт из порта 300h (номер порта взят условно) ;в цепочку байт в памяти по адресу str_10 db 10 dup(O) adr_str dd str_10 les di,adr_str mov dx,300h rep insb См. также: уроки 2, 11 и команды cmps, lods, movs, outs, seas, stos, rep INT (INTerrupt) Вызов подпрограммы обслуживания прерывания int номер прерывания Назначение: вызов подпрограммы обслуживания прерывания с номером прерывания, задан- ным операндом команды. Синтаксис: — int Рис, П2.28. Синтаксическое описание команды int
518 Приложение 2. Описание системы команд микропроцессоров Intel Алгоритм работы: О Записать в стек регистр флагов eflags/flags и адрес возврата. При записи адреса возврата вначале записывается содержимое сегментного регистра cs, затем содержимое указателя команд eip/ip; О сбросить в ноль флаги if и tf; О передать управление на программу обработки прерывания с указанным но- мером. Механизм передачи управления зависит от режима работы микро- процессора (см. уроки 15 и 17). Состояние флагов после выполнения команды: 09 08 IF TF 0 0 Применение: Как видно из синтаксиса, существует две формы этой команды: О int 3 — имеет свой индивидуальный код операции Occh и занимает один байт. Это обстоятельство делает ее очень удобной для использования в раз- личных программных отладчиках для установки точек прерывания путем подмены первого байта любой команды. Микропроцессор, встречая в после- довательности команд команду с кодом операции Occh, вызывает программу обработки прерывания с номером вектора 3, которая служит для связи с программным отладчиком. О Вторая форма команды занимает два байта, имеет код операции Ocdh и по- зволяет инициировать вызов подпрограммы обработки прерывания с номе- ром вектора в диапазоне 0-255. Особенности передачи управления, как было отмечено, зависят от режима работы микропроцессора: ;вызов обработчика аппаратного прерывания 08h из программы: int 08h См. также: уроки 15, 17 и команды into, iret INTO (INTerrupt if Overflow) Прерывание, если переполнение into Назначение: инициирование прерывания с номером 4, если установлен флаг of.
INTO (INTerrupt if Overflow) 519 Синтаксис: into Алгоритм работы: Проанализировать состояние флага of: О если of=0, то никаких действий производить не нужно — передать управле- ние на следующую команду; О если of=1, то дальнейшие действия — как при команде int, то есть: • записать в стек регистр флагов eflags/flags и адрес возврата. При запи- си адреса возврата вначале записывается содержимое сегментного регист- ра cs, затем — содержимое указателя команд eip/ip; • сбросить в ноль флаги if и tf; • передать управление на программу обработки прерывания с данным но- мером. Механизм передачи зависит от режима работы микропроцессора (см. уроки 15 и 17). Состояние флагов после выполнения команды: 09 08 IF TF г г Применение: Свойство этой команды инициировать вызов подпрограммы обработки преры- вания с номером вектора 4 определяет варианты ее применения. Если предыду- щая команда в программе может в результате своей работы установить флаг переполнения of (к примеру, арифметические команды), то для обнаружения и обработки такой ситуации можно использовать команду into. Особенности пе- редачи управления и обработки (корректировки) результата зависят от режи- ма работы микропроцессора: .486 mov bx,186 imul eax,bx,8 ;если результату не хватило размерности операнда!, ;то of установится в 1 ;исправим ситуацию в обработчике прерывания 3 into См. также: уроки 8, 15, 17 и команды int, iret, imul
520 Приложение 2. Описание системы команд микропроцессоров Intel IRET/IRETD (Interrupt RETurn) Возврат из прерывания iret iretd Назначение: используется в той точке программы обработки прерывания, откуда необходи- мо вернуть управление прерванной программе. Синтаксис: iret iretd Алгоритм работы: Работа команды зависит от режима работы микропроцесссора: О в реальном режиме команда iret последовательно извлекает из стека и за- тем восстанавливает в микропроцессоре содержимое следующих регистров: eip/ip, cs, eflags/flags. Далее прерванная программа продолжается с точки прерывания; О в защищенном режиме действия команды зависят от состояния флага NT (вложенной задачи) в регистре флагов: если NT=O, то производятся действия по возврату управления прерванной программе, при этом характер этих действий зависит от соотношения уровней привилегированности прерванной программы и программы об- работки прерывания; в случае NT=1 производятся действия по переключению задач. Состояние флагов после выполнения команды: И 10 09 08 07 OF DF IF TF SF 06 04 02 00 ZF AF PF CF г г г г г г г г г Применение: Команду iret необходимо применять для восстановления сохраненных коман- дой int регистров флагов, указателя команд и сегментного регистра кода. Чис- ло этих команд в программе обработки прерывания должно соответствовать количеству точек выхода из нее. Команда iretd используется в старших моделях микропроцессоров для извле- чения из стека и восстановления 32-битных регистров:
Jcc (Jump if condition) JCXZ/JECXZ (Jump if CX=Zero/ Jump if ECX=Zero) 521 my_int1C proc программа обработки прерывания ICh iret endp См. также: уроки 15, 17 и команды int, into Jcc (Jump if condition) JCXZ/JECXZ (Jump if CX=Zero/ Jump if ECX=Zero) Переход, если выполнено условие Переход, если СХ/ЕСХ равен нулю jcc метка jcxz метка jecxz метка Назначение: переход внутри текущего сегмента команд в зависимости от некоторого усло- вия. Синтаксис: Рис, П2.29. Синтаксическое описание команды jcc Алгоритм работы команд (кроме jcxz/jecxz): Проверка состояния флагов в зависимости от кода операции (оно отражает проверяемое условие): О если проверяемое условие истинно, то перейти к ячейке, обозначенной опе- рандом; О если проверяемое условие ложно, то передать управление следующей ко- манде. Алгоритм работы команды jcxz/jecxz: Проверка условия равенства нулю содержимого регистра есх/сх: О если проверяемое условие истинно, то есть содержимое есх/сх равно 0, то перейти к ячейке, обозначенной операндом метка;
522 Приложение 2. Описание системы команд микропроцессоров Intel О если проверяемое условие ложно, то есть содержимое есх/сх не равно 0, то передать управление следующей за jcxz/jecxz команде программы. Состояние флагов после выполнения команды: выполнение команды не влияет на флаги. Применение (кроме jcxz/jecxz): Команды условного перехода удобно применять для проверки различных усло- вий, возникающих в ходе выполнения программы. Как известно, многие коман- ды формируют признаки результатов своей работы в регистре eflags/flags. Это обстоятельство и используется командами условного перехода для работы. Ниже приведен перечень команд условного перехода, анализируемых ими фла- гов и соответствующих им логических условий перехода. Команда Состояние проверяемых флагов Условие перехода JA CF = 0 и ZF = 0 Если выше JAE CF = 0 Если выше или равно JB CF = 1 Если ниже JBE CF = 1 или ZF = 1 Если ниже или равно JC CF = 1 Если перенос JE ZF = 1 Если равно JZ ZF = 1 Если 0 JG ZF = 0 и SF = OF Если больше JGE SF = OF Если больше или равно JL SF <> OF Если меньше JLE ZF=1 или SF <> OF Если меньше или равно JNA CF = 1 и ZF = 1 Если не выше JNAE CF = 1 Если не выше или равно JNB CF = 0 Если не ниже JNBE CF=0 и ZF=0 Если не ниже или равно JNC CF = 0 Если нет переноса JNE ZF = 0 Если не равно JNG ZF = 1 или SF <> OF Если не больше JNGE SF <> OF Если не больше или равно JNL SF = OF Если не меньше JNLE ZF=0 и SF=OF Если не меньше или равно JNO OF=0 Если нет переполнения JNP PF = 0 Если количество единичных битов результа- та нечетно (нечетный паритет)
Jcc (Jump if condition) JCXZ/JECXZ (Jump if CX=Zero/ Jump if ECX=Zero) 523 Команда Состояние проверяемых флагов Условие перехода JNS SF = 0 Если знак плюс (знаковый (старший) бит результата равен 0) JNZ ZF = 0 Если нет нуля JO OF = 1 Если переполнение JP PF = 1 Если количество единичных битов результа- та четно (четный паритет) JPE PF = 1 То же, что и JP, то есть четный паритет JPO PF = 0 То же, что и JNP JS SF = 1 Если знак минус (знаковый (старший) бит результата равен 1) JZ ZF = 1 Если ноль Логические условия «больше» и «меньше» относятся к сравнениям целочис- ленных значений со знаком, а «выше» и «ниже» — к сравнениям целочислен- ных значений без знака. Если внимательно посмотреть, то у многих команд мож- но заметить одинаковые значения флагов для перехода. Это объясняется наличием нескольких ситуаций, которые могут вызвать одинаковое состояние флагов. В этом случае с целью удобства ассемблер допускает несколько различных мнемони- ческих обозначений одной и той же машинной команды условного перехода. Эти команды ассемблера по действию абсолютно равнозначны, так как это одна и та же машинная команда. Изначально в микропроцессоре 18086 команды условного пере- хода могли осуществлять только короткие переходы в пределах -128...+127 байт, считая от следующей команды. Начиная с микропроцессора 1386 эти команды уже могли выполнять любые переходы в пределах текущего сегмента команд. Это стало возможным за счет введения в систему команд микропроцессора дополнительных машинных команд. Для реализации межсегментных переходов необходимо ком- бинировать команды условного перехода и команду безусловного перехода jmp. При этом можно воспользоваться тем, что практически все команды условного перехода парные, то есть имеют команды, проверяющие обратные условия. Применение jcxz/jecxz: Команда Состояние флагов в eflags/flags Условие перехода JCXZ Не влияет Если регистр СХ=0 JECXZ Не влияет Если регистр ЕСХ=0 Команду jcxz/jecxz удобно использовать со всеми командами, использующими регистр есх/сх для своей работы. Это команды организации цикла и цепочеч- ные команды. Очень важно отметить то, что команда jcxz/jecxz, в отличие от других ко- манд перехода, может выполнять только близкие переходы в пределах -128...+127 байт, считая от следующей команды. Поэтому для нее особенно акту-
524 Приложение 2. Описание системы команд микропроцессоров Intel альна проблема передачи управления далее, чем в указанном диапазоне. Для это- го можно привлечь команду безусловного перехода jmp. Например, команду jcxz/ jecxz можно использовать для предварительной проверки счетчика цикла в реги- стре сх для обхода цикла, если его счетчик нулевой: jcxz ml ;обойти цикл, если сх=0 cycl: ;некоторый цикл loop cycl ml: См. также: уроки 10, 11 и команду jmp jmp (Jump) Переход безусловный jmp метка Назначение: используется в программе для организации безусловного перехода как внутри текущего сегмента команд, так и за его пределы. При определенных условиях в защищенном режиме работы команда jmp может использоваться для переклю- чения задач. Синтаксис: jmp Ц а8,16,32 к- Н m16,32,48b -| r16,32 b Puc. П2.30. Синтаксическое описание команды jmp Алгоритм работы: Команда jmp в зависимости от типа своего операнда изменяет содержимое либо только одного регистра eip, либо обоих регистров cs и eip: О если операнд в команде jmp — метка в текущем сегменте команд (а8, 16, 32), то ассемблер формирует машинную команду, операнд которой является значением со знаком, являющимся смещением перехода относительно сле- дующей за jmp команды. При этом виде перехода изменяется только регистр eip/ip; О если операнд в команде jmp — символический идентификатор ячейки памя- ти (ш16, 32, 48), то ассемблер предполагает, что в ней находится адрес, по которому необходимо передать управление. Этот адрес может быть трех ви-
LAHF (Load AH register from register Flags) 525 дов: • значением абсолютного смещения метки перехода относительно начала сегмента кода. Размер этого смещения может быть 16 или 32 бит, в за- висимости от режима адресации; • дальним указателем на метку перехода в реальном и защищенном режи- мах, содержащим два компонента адреса — сегментный и смещение. Раз- меры этих компонентов также зависят от установленного режима адреса- ции (use16 или use32). Если текущим режимом является use16, то адрес сегмента и смещение занимают по 16 бит, причем смещение располагает- ся в младшем слове двойного слова, отводимого под этот полный адрес метки перехода. Если текущим режимом является use32, то адрес сегмен- та и смещение занимают, соответственно, 16 и 32 бит — размещение этих компонентов аналогично вышерассмотренному: в младшем двойном слове находится смещение, в старшем — адрес сегмента; • адресом в одном из 16 или 32-разрядных регистров — этот адрес пред- ставляет собой абсолютное смещение метки, на которую необходимо пе- редать управление, относительно начала сегмента команд. Для понимания различий механизмов перехода в реальном и защищенном ре- жимах нужно помнить следующее. В реальном режиме микропроцессор просто изменяет cs и eip/ip в соответствии с содержимым указателя в памяти. В за- щищенном режиме микропроцессор предварительно анализирует байт прав доступа AR в дескрипторе, номер которого определяется по содержимому сег- ментной части указателя. В зависимости от состояния байта AR микропроцес- сор выполняет либо переход, либо переключение задач. Состояние флагов после выполнения команды: выполнение команды не влияет на флаги, за исключением случая переключения задач. Применение: Команду jmp применяют для осуществления ближних и дальних безусловных переходов без сохранения контекста точки перехода. См. также: урок 10 и команду jcc LAHF (Load АН register from register Flags) Загрузка регистра АН флагами из регистра eFlags/Flags lahf Назначение: извлечение содержимого младшего байта регистра eflags/f lags, в котором со- держатся пять флагов cf, pf, af, zf и sf.
526 Приложение 2. Описание системы команд микропроцессоров Intel Синтаксис: lahf Алгоритм работы: Команда загружает регистр ah содержимым младшего байта регистра eflags/ flags. Состояние флагов после выполнения команды: выполнение команды не влияет на флаги. Применение: Из-за того, что регистр флагов непосредственно недоступен, команду lahf мож- но применять для анализа и последующего изменения командой sahf состоя- ния некоторых флагов регистра eflags/flags: ;сбросить в ноль флаг cf lahf and ah,11111110b sahf См. также: команду sahf LDS/LES/LFS/LGS/LSS (Load pointer into ds/es/fs/gs/ss segment register) Загрузка сегментного регистра ds/es/fs/gs/ss указателем из памяти Ids приемник,источник les приемник,источник Its приемник,источник Igs приемник,источник Iss приемник,источник М6>О-|7пзГЬ г32 Назначение: получение полного указателя в виде сегментной составляющей и смещения. Синтаксис: [9S Ч~гЗП0{т48> Iss Рис. П2.31. Синтаксическое описание команд загрузки сегментного регистра указателем Алгоритм работы: Алгоритм работы команды зависит от действующего режима адресации (use16 или use32): О если use16, то загрузить первые два байта из ячейки памяти источник в 16-разрядный регистр, указанный операндом приемник. Следующие два бай-
LEA (Load Effective Address) 527 та в области источник должны содержать сегментную составляющую некоторо- го адреса; они загружаются в регистр ds/es/fs/gs/ss; О если use32, то загрузить первые четыре байта из ячейки памяти источник в 32- разрядный регистр, указанный операндом приемник. Следующие два байта в области источник должны содержать сегментную составляющую или селектор некоторого адреса; они загружаются в регистр ds/es/fs/gs/ss. Состояние флагов после выполнения команды: выполнение команды не влияет на флаги. Применение: Таким образом, с помощью данных команд в паре регистров ds/es/fs/gs/ss и приемник оказывается полный адрес некоторой ячейки памяти. Это обстоя- тельство можно использовать, к примеру, при работе с цепочечными команда- ми, где существуют жесткие соглашения на размещение адресов обрабатывае- мых строк. Помните, что любая загрузка сегментного регистра приводит к обновлению соответствующего теневого регистра (см. урок 16). См. также опи- сание команды cmps с примером использования. См. также: уроки 5, 7, 11, команду lea и операторы ассемблера seg и offset LEA (Load Effective Address) Загрузка эффективного адреса lea приемник,источник Назначение: получение эффективного адреса (смещения) источника. Синтаксис: г32 Иэ{тет]- Рис. П2.32. Синтаксическое описание команды lea Алгоритм работы: Алгоритм работы команды зависит от действующего режима адресации (use16 или use32): О если use16, то в регистр приемник загружается 16-битное значение смещения операнда источник;
528 Приложение 2. Описание системы команд микропроцессоров Intel О если use32, то в регистр приемник загружается 32-битное значение смещения операнда источник. Состояние флагов после выполнения команды: выполнение команды не влияет на флаги. Применение: Данная команда является альтернативой оператору ассемблера offset. В отли- чие от offset, команда lea допускает индексацию операнда, что позволяет бо- лее гибко организовать адресацию операндов: [загрузить в регистр Ьх адрес пятого элемента массива mas .data mas db 10 dup (0) .code mov di,4 lea bx,mas[di] ; или lea bx,mas[4] ; или lea bx,mas+4 См. также: уроки 5, 7, 11, команды lea, Ids, les, Iss, Igs, Ifs и операторы ассемблера seg, offset LEAVE (LEAVE from procedure) Выход из процедуры leave Назначение: удаление из стека области локальных (динамических) переменных, выделен- ных командой enter. Синтаксис: leave Алгоритм работы: Команда выполняет обратные команде enter действия: О содержимое ebp/bp копируется в esp/sp, тем самым восстанавливается зна- чение esp/sp, которое было до вызова данной процедуры. С другой стороны, восстановление старого значения esp/sp означает освобождение простран-
LGDT (Load Global Descriptor Table) 529 ства в стеке, отведенного для завершающейся процедуры (локальные перемен- ные процедуры уничтожаются); О из стека восстанавливается содержимое ebp/bp, которое было до входа в про- цедуру. После этого действия значение esp/sp также становится таким, каким оно было до входа в процедуру. В результате этих двух действий также восстанавливается кадр стека, если он был, вызывающей программы. Состояние флагов после выполнения команды: выполнение команды не влияет на флаги. Применение: Команда leave не имеет операндов и выполняет обратные команде enter дей- ствия. Эта команда должна находиться непосредственно перед командой ret, которая в зависимости от соглашений конкретного языка по вызову процедур удаляет или не удаляет аргументы из стека (см. урок 14): .286 prod proc enter 16,0 leave ret prod endp См. также: урок 14 и команды enter, ret LGDT (Load Global Descriptor Table) Загрузка регистра глобальной дескрипторной таблицы Igdt источник Назначение: загрузка регистра gdtr значениями базового адреса и размера глобальной дес- крипторной таблицы GDT. Синтаксис: Igdt m48 Алгоритм работы: Команда выполняет загрузку 16 бит размера и 32 бит значения базового адреса начала таблицы GDT в памяти в системный регистр gdtr. Эта загрузка произ- водится в соответствии с форматом этого регистра (см. урок 16).
530 Приложение 2. Описание системы команд микропроцессоров Intel Состояние флагов после выполнения команды: выполнение команды не влияет на флаги. Применение: Команду Igdt применяют при подготовке к переходу в защищенный режим для загрузки системного регистра gdt г. В качестве операнда в команде указывается адрес области в формате 16+32. Младшее слово области — размер GDT, двой- ное слово по старшему адресу — значение базового адреса начала этой табли- цы. Данные два компонента должны быть сформированы в памяти заранее: .286 [структура для описания псевдодескриптора gdtr point STRUC lim dw 0 adr dd 0 ENDS .data point_gdt point <gdt_size,O> .code [загружаем gdtr xor eax,eax mov ax,gdt_seg shl eax,4 mov point_gdt.adr,eax Igdt point_gdt См. также: уроки 16, 17 и команду sgdt LIDT (Load Interrupt Descriptor Table) Загрузка регистра дескрипторной таблицы прерываний lidt источник Назначение: загрузка регистра idtr значениями базового адреса и размера глобальной де- скрипторной таблицы IDT.
LIDT (Load Interrupt Descriptor Table) 531 Синтаксис: lidt m48 Алгоритм работы: Команда lidt аналогична Igdt, но для дескрипторной таблицы прерываний IDT (см. урок 17). Состояние флагов после выполнения команды: выполнение команды не влияет на флаги. Применение: Команду lidt применяют при подготовке к переходу в защищенный режим для загрузки системного регистра idtr. В качестве операнда в команде указывается адрес области в формате 16+32. Младшее слово области — размер IDT, двой- ное слово по старшему адресу — значение базового адреса начала этой табли- цы. Два данных компонента должны быть сформированы в памяти заранее: .386 структура для описания псевдодескриптора gdtr и idtr point STRUC lim dw 0 adr dd 0 ENDS .data point_idt point <idt_size,0> .code загружаем idtr xor eax,eax mov ax,IDT_SEG shl eax,4 mov point_idt.adr,eax lidt point_idt См. также: урок 17 и команду sidt
532 Приложение 2. Описание системы команд микропроцессоров Intel LODS/LODSB/LODSW/LODSD (LOad String Byte/Word/ Double word operands) Загрузка строки байтов/слов/двойных слов lods источник lodsb lodsw lodsd Назначение: загрузка элемента из последовательности (цепочки) в регистр-аккумулятор al/ax/eax. Синтаксис: Рис. П2.33. Синтаксическое описание команд загрузки байт/слов/двойных слов в al/ax/eax Алгоритм работы: О Загрузить элемент из ячейки памяти, адресуемой парой ds:esi/si, в регистр al/ax/eax. Размер элемента определяется неявно (для команды lods) или явно в соответствии с применяемой командой (для команд lodsb, lodsw, lodsd); О изменить значение регистра si на величину, равную длине элемента цепоч- ки. Знак этой величины зависит от состояния флага df: df=O — значение положительное, то есть просмотр от начала цепочки к ее концу; df=1 — значение отрицательное, то есть просмотр от конца цепочки к ее началу. Состояние флагов после выполнения команды: выполнение команды не влияет на флаги. Применение: Команды извлекают элемент из ячейки памяти в один из регистров. Перед ко- мандой lods можно указать префикс повторения гер, но в этом нет особого
LOOP (LOOP control by register ex) 533 смысла, так как обычно эту команду используют в некотором цикле для про- смотра некоторой цепочки с элементами фиксированного размера: str db cld lea si,str lodsb загрузить первый байт из str в al См. также: урок 11 и команды ins, cmps, movs, outs, seas, stos, repe, repz, repne, repnz LOOP (LOOP control by register ex) Управление циклом по сх loop метка Назначение: организация цикла со счетчиком в регистре сх. Синтаксис: loop метка Алгоритм работы: О Выполнить декремент содержимого регистра есх/сх; О анализ регистра есх/сх: • если есх/сх=0, передать управление следующей за loop команде; • если есх/сх=1, передать управление команде, метка которой указана в качестве операнда loop. Состояние флагов после выполнения команды: выполнение команды не влияет на состояние флагов. Применение: Команду loop применяют для организации цикла со счетчиком. Количество повторений цикла задается значением в регистре есх/сх перед входом в после- довательность команд, составляющих тело цикла. Помните о двух важных мо- ментах: О для предотвращения выполнения цикла при нулевом есх/сх используйте команду jecxz/jcxz. Если этого не сделать, то при изначально нулевом есх/сх цикл повторится (4 294 967 295/65 536) раз;
534 Приложение 2. Описание системы команд микропроцессоров Intel О смещение метки, являющейся операндом loop, не должно выходить из диапа- зона -128...+127 байт. Это смещение, как и в командах условного перехода, является относительным от значения счетчика адреса следующей за loop ко- манды: mov сх, 10 jcxz ml cycl: ;тело цикла loop cycl ml: См. также: урок 10 и команды jecxz/jcxz, loope/loopz, loopne/loopnz LOOPE/LOOPZ (LOOP control by register ex not equal 0 and ZF=1) LOOPNE/LOOPNZ (LOOP control by register ex not equal 0 and ZF=O) Управление циклом по сх с учетом значения флага ZF loope/loopz метка loopne/loopnz метка Назначение: организация цикла со счетчиком в регистре сх с учетом флага zf. Синтаксис: loope/loopz метка loopne/loopnz метка Алгоритм работы команд: О Выполнить декремент содержимого регистра есх/сх; О проанализировать регистр есх/сх: • если есх/сх=0, передать управление следующей за loopxx команде; • если есх/сх=1, передать управление команде, метка которой указана в качестве операнда loopxx;
MOV (MOVe operand) 535 О анализ флага zf: • если zf=0, для команд loope/loopz это означает выход из цикла, для команд loopne/loopnz — переход к началу цикла; • если zf=1, для команд loope/loopz это означает переход к началу цикла, для команд loopne/loopnz - выход из цикла. Состояние флагов после выполнения команды: выполнение команды не влияет на флаги. Применение: Команды loopxx удобно использовать вместе с командами, которые в результате своей работы меняют значение флага zf. Типичный пример — команда сравне- ния стр: ; найти первый пробел в строке символов strdb "Найти первый пробел" str_size=$-str cld mov cx,str_size lea si,str cycl: lodsb cmp al, loopne cycl jcxz ml ; переход, если пробелов нет dec si ; в si - адрес пробела в строке str ml См. также: уроки 8, 10, 11 и команду loop MOV (MOVe operand) Пересылка операнда mov приемник,источник Назначение: пересылка данных между регистрами или регистрами и памятью.
536 Приложение 2. Описание системы команд микропроцессоров Intel Синтаксис: Рис. П2.34. Синтаксическое описание команды mov Алгоритм работы: Копирование второго операнда в первый операнд. Состояние флагов после выполнения команды: выполнение команды не влияет на флаги. Применение: Команда mov применяется для различного рода пересылок данных, при этом, несмотря на всю простоту этого действия, необходимо помнить о некоторых ограничениях и особенностях выполнения3данной операции: О направление пересылки в команде mov всегда справа налево, то есть из вто- рого операнда в первый; О значение источника не изменяется; О оба операнда не могут быть из памяти (при необходимости можно исполь- зовать цепочечную команду movs); О лишь один из операндов может быть сегментным регистром; О желательно использовать в качестве одного из операндов регистр al/ax/eax, так как в этом случае TASM генерирует более быструю форму команды mov; mov al,5 mov bl,al mov bx,ds См. также: урок 10 и команды movs, lods, stos MOV (MOVe operand to/from system registers) Пересылка операнда в системные регистры (или из них) mov приемник,источник
MOVS/MOVSB/MOVSW/MOVSD (MOVe String Byte/Word/Double word) 537 Назначение: пересылка данных между регистрами или регистрами и памятью. Синтаксис: Рис. П2.35. Синтаксическое описание команды mov Алгоритм работы: Копирование второго операнда в первый. Состояние флагов после выполнения команды: 11 07 06 04 02 00 OF SF ZF AF PF CF ? ? ? ? ? ? Применение: Команда mov применяется для обмена данными между системными регистрами. Это одна из немногих возможностей доступа к содержимому этих регистров. Данную команду можно использовать только на нулевом уровне привилегий либо в реальном режиме работы микропроцессора: .286 переключение микропроцессора в защищенный режим: mov еах.сгО bts еах,О mov сгО.еах / См. также: уроки 16, 17 и команды mov, bts MOVS/MOVSB/MOVSW/MOVSD (MOVe String Byte/Word/Double word) Пересылка строк байтов/слов/двойных слов movs приемник,источник movsb movsw moved
538 Приложение 2. Описание системы команд микропроцессоров Intel Назначение: пересылка элементов двух последовательностей (цепочек) в памяти. Синтаксис: L| es,gs,fshQ^ L| es,gs,fs|^ L| es,gs,fs|-or Рис. П2.36. Синтаксическое описание команд пересылки цепочки Алгоритм работы: О Выполнить копирование байта, слова или двойного слова из операнда источника в операнд приемник, при этом адреса элементов предварительно должны быть загружены: • адрес источника — в пару регистров ds:esi/si (ds по умолчанию, допус- кается замена сегмента); • адрес приемника — в пару регистров es: edi/di (замена сегмента не допус- кается); О в зависимости от состояния флага df изменить значение регистров esi/si и edi/di: • если df=0, то увеличить содержимое этих регистров на длину структур- ного элемента последовательности; • если df=1, то уменьшить содержимое этих регистров на длину структур- ного элемента последовательности; О если есть префикс повторения, то выполнить определяемые им действия (см. команду гер). Состояние флагов после выполнения команды: выполнение команды не влияет на флаги. Применение: Команды пересылают элемент из одной ячейки памяти в другую. Размеры пе- ресылаемых элементов зависят от применяемой команды. Команда movs может работать с элементами размером в байт, слово, двойное слово. В качестве опе- рандов в команде указываются идентификаторы последовательностей этих элементов в памяти. Реально эти идентификаторы используются лишь для по- лучения типов элементов последовательностей, а их адреса должны быть пред- варительно загружены в указанные выше пары регистров. Транслятор, обрабо- тав команду movs и выяснив тип операндов, генерирует одну из машинных команд: movsb, movsw или movsd. Машинного аналога для команды movs нет. Для адресации операнда приемник обязательно должен использоваться регистр es.
MOVSX (MOVe and Sign extension) 539 Для того чтобы эти команды можно было использовать для пересылки последо- вательности элементов, имеющих размерность байт, слово, двойное слово, необ- ходимо использовать префикс гер. Префикс гер заставляет циклически выпол- няться команды пересылки до тех пор, пока содержимое регистра есх/сх не станет равным нулю: strl db "str1 копируется в str2" Ien_str1=$-str1 a_str1 dd strl str2 db len_str1 dup (" ”) a_str2 dd str2 mov cx,len_str1 Ids si,str1 les di,str2 cld rep movsb См. также: урок 11 и команды cmps, ins, lods, outs, seas, stos, rep, repe, repz, repne, repnz MOVSX (MOVe and Sign extension) Пересылка co знаковым расширением movsx приемник,источник Назначение: преобразование элементов со знаком меньшей размерности в эквивалентные им элементы со знаком большей размерности. Синтаксис: Рис. П2.37. Синтаксическое описание команды movsx Алгоритм работы: О Считать содержимое источника; О записать содержимое операнда источника в операнд приемник, начиная с младших разрядов источника;
540 Приложение 2. Описание системы команд микропроцессоров Intel О распространить значение знакового разряда источника на свободные старшие разряды операнда приемника. Состояние флагов после выполнения команды: выполнение команды не влияет на флаги. Применение: Команду movsx обычно используют для получения эквивалентного, но больше- го по размеру операнда со знаком. Это может понадобиться для приведения размера операнда к нужному значению с целью обеспечения работы нижесле- дующих команд программы: mov al,Offh movsx bx.al ;bx=Offffh См. также: урок 8 и команды mov, movs, movzx, cbw, cwd, cdq MOVZX (MOVe and Zero extension) Пересылка с нулевым расширением movzx приемник,источник Назначение: преобразование элементов без знака меньшей размерности в эквивалентные им элементы без знака большей размерности. Синтаксис: Рис. П2.38. Синтаксическое описание команды movzx Алгоритм работы: О Считать содержимое источника', О записать содержимое операнда источника в операнд приемник, начиная с его младших разрядов; О распространить двоичный нуль на свободные старшие разряды операнда назначения.
MUL (Multiply) 541 Состояние флагов после выполнения команды: выполнение команды не влияет на флаги. Применение: Команду movzx обычно используют для получения эквивалентного, но больше- го по размеру операнда без учета знака. Она может быть использована для со- гласования операндов различной размерности. Но не следует думать, что все эти разнотипные пересылки делает одна машинная команда. На самом деле, существует несколько машинных команд, каждая из которых работает со свои- ми размерами операндов. Генерацию же нужной команды обеспечивает транс- лятор на основе анализа исходного текста программы: .data si db ? .code mov al.Offh movzx bx.al ;bx=OOffh ;или из памяти: movzx eax,byte ptr si См. также: урок 8 и команды mov, movs, movsx, cbw, cwd, cdq MUL (MULtiply) Умножение целочисленное без учета знака mul множитель 1 Назначение: операция умножения двух целых чисел без учета знака. Синтаксис: mul |г8,16,32|-[ |m8,16,32h Рис. П2.39. Синтаксическое описание команды mul Алгоритм работы: Команда выполняет умножение двух операндов без учета знаков. Алгоритм за- висит от формата операнда команды и требует явного указания местоположе- ния только одного сомножителя, который может быть расположен в памяти
542 Приложение 2. Описание системы команд микропроцессоров Intel или в регистре. Местоположение второго сомножителя фиксировано и зависит от размера первого сомножителя: О если операнд, указанный в команде, — байт, то второй сомножитель должен располагаться в al; О если операнд, указанный в команде, — слово, то второй сомножитель дол- жен располагаться в ах; О если операнд, указанный в команде, — двойное слово, то второй сомножи- тель должен располагаться в еах. Результат умножения также помещается в фиксированное место, определяемое размером сомножителей: О при умножении байтов результат помещается в ах; О при умножении слов результат помещается в пару dx:ax; О при умножении двойных слов результат помещается в пару edx:eax. Состояние флагов после выполнения команды (если старшая половина результата нулевая): 11 07 06 04 02 00 OF SF ZF AF PF CF 0 ? ? ? ? 0 Состояние флагов после выполнения команды (если старшая половина результата ненулевая): И 07 06 04 02 00 OF SF ZF AF PF CF 1 ? ? Применение: ? ? 1 Команда mul выполняет целочисленное умножение операндов без учета их зна- ковых разрядов. Для этой операции необходимо наличие двух операндов-со- множителей, размещение одного из которых фиксировано, а другого — задает- ся операндом в команде. Контролировать размер результата удобно используя флаги cf и of. mn_1 db 15 mn_2 db 25 mov al,mn_1 mul mn_2 См. также: урок 8 и команду imul
NEG (NEGate operand) 543 NEG (NEGate operand) Изменить знак операнда neg источник Назначение: изменение знака (получение двоичного дополнения) источника. Синтаксис: — neg L-{78,16,32 k- L|m8,16,32 H Рис. П2.40. Синтаксическое описание команды neg Алгоритм работы: О Выполнить вычитание (0-источник) и поместить результат на место источ- ника; О если источнику то его значение не меняется. Состояние флагов после выполнения команды (если результат нулевой): И 07 06 04 02 00 OF SF ZF AF PF CF г г г г г 0 Состояние флагов после выполнения команды (если результат ненулевой): и 07 06 04 02 00 OF SF ZF AF PF CF г г г г г 1 Применение: Команда используется для формирования двоичного дополнения операнда в памяти или регистре. Операция двоичного дополнения предполагает инверти- рование всех разрядов операнда с последующим сложением операнда с двоич- ной единицей. Если операнд отрицательный, то операция neg над ним означает получение его модуля: mov al,2 neg al;al=Ofeh - число -2 в дополнительном коде См. также: уроки 6, 8 и команду not
544 Приложение 2. Описание системы команд микропроцессоров Intel NOP (No Operation) Нетоперации nop Назначение: пустая команда. Синтаксис: пор Алгоритм работы: Не производит никаких действий. Состояние флагов после выполнения команды: выполнение команды не влияет на флаги. Применение: Команда пор, занимая один байт, может использоваться для резервирования места в сегменте кода или организации программной задержки. В качестве ил- люстрации можно обратиться к примеру, приведенному в описании команды hit. В этом примере команду пор можно использовать вместо jmp $+2. Назначе- ние jmp $+2 в этом фрагменте — задержка для синхронизации работы микро- процессора и аппаратуры компьютера. NOT (NOT operand) Инвертирование операнда not источник Назначение: инвертирование всех битов операнда источник. Синтаксис: Рис. П2.41. Синтаксическое описание команды not Алгоритм работы: Инвертировать все биты операнда источника: из 1 в 0, из 0 в 1. Состояние флагов после выполнения команды: выполнение команды не влияет на флаги.
OR (logical OR) 545 Применение: Команду not можно использовать для изменения байта, выполняющего роль некоторого флага, с целью отслеживания некоторых логических условий в про- грамме. Но такой способ не оптимален, эту ситуацию мы обсуждали в книге на уроках 9 и 12: flag db Offh ;значение флага - истина cycl: cmp flag,О je ml ml: not flag установить флаг в истину См. также: уроки 9, 12 и команду neg OR (logical OR) Логическое включающее ИЛИ or приемник,маска Назначение: операция логического ИЛИ над битами операнда приемника. Синтаксис: Рис. П2.42. Синтаксическое описание команды or Алгоритм работы: О Выполнить операцию логического ИЛИ над битами операнда приемника, ис- пользуя в качестве маски второй операнд — маска. При этом бит результата
546 Приложение 2. Описание системы команд микропроцессоров Intel равен 0, если соответствующие биты операндов маска и приемника равны 0. Бит равен 1 в противном случае; О записать результат операции в источник (операнд маска остается неизмен- ным); О установить флаги. Состояние флагов после выполнения команды: И 07 06 04 02 00 OF SF ZF AF PF CF 0 г г ? г 0 Применение: Команду or можно использовать для работы с операндами на уровне битов. Типичное использование команды — установка определенных разрядов перво- го операнда в единицу: mov al,01h or bl,al установить нулевой бит в 1 См. также: урок 9 и команды and, xor, not OUT (OUT operand to port) Вывод операнда в порт out ном порта,аккумулятор Назначение: вывод значения в порт ввода-вывода. Синтаксис: Рис. П2.43. Синтаксическое описание команды out Алгоритм работы: Передать байт, слово, двойное слово из регистра al/ax/eax в порт, номер кото- рого определяется первым операндом. Состояние флагов после выполнения команды выполнение команды не влияет на флаги.
OUTS/OUTSB/OUTSW/OUTSD (OUTput Byte/Word/Double word String to port) 547 Применение: Команда применяется для прямого управления оборудованием компьютера по- средством портов. Номер порта задается первым операндом в виде непосред- ственного значения или значения в регистре dx. Непосредственным значением можно задать порт с номером в диапазоне 0...255. Для указания порта с боль- шим номером используется регистр dx. Размер данных определяется размернос- тью второго операнда и может быть байтом, словом или двойным словом. out 64h,al См. также: уроки 2, 7, 16, 17 и команды in, ins, outs OUTS/OUTSB/OUTSW/OUTSD (OUTput Byte/Word/Double word String to port) Вывод строки байтов/слов/двойных слов в порт outs порт,источник outsb outsw outsd Назначение: вывод в порт из памяти последовательности байт, слов, двойных слов. Синтаксис: Рис. П2.44. Синтаксическое описание команд вывода цепочки в порт Алгоритм работы: О Передать данные в порт ввода-вывода, номер которого загружен в регистр dx, из ячейки памяти по адресу ds:esi/si; О в зависимости от состояния флага df изменить значение регистров esi/si: • если df=0, то увеличить содержимое этих регистров на длину структур- ного элемента последовательности; • если df=1, то уменьшить содержимое этих регистров на длину структур- ного элемента последовательности;
548 Приложение 2. Описание системы команд микропроцессоров Intel О при наличии префикса выполнить определяемые им действия (см. коман- ду гер). Состояние флагов после выполнения команды: выполнение команды не влияет на флаги. Применение: Команда выводит данные в порт ввода-вывода, номер которого загружен в ре- гистр dx, из ячейки памяти по адресу ds:esi/si (допускается замена сегмента). Недопустимо задание номера порта в команде в виде непосредственного опе- ранда — для этого используется регистр dx. Размеры вводимых элементов за- висят от применяемой команды. Команда outs может работать с элементами размером в байт, слово или двойное слово. В качестве операнда в команде ука- зывается символическое имя ячейки памяти, из которой элемент выводится в порт ввода-вывода. Реально символическое имя используется лишь для полу- чения типа элемента последовательности, а ее адрес должен быть предвари- тельно загружен в пару регистров ds:esi/si. Транслятор, обработав команду outs и выяснив тип операндов, генерирует одну из машинных команд outsb, outsw или outsd. Машинного аналога для команды outs нет. Для того чтобы эти команды можно было использовать для вывода в порт пос- ледовательности элементов, имеющих размерность байт, слово или двойное слово, необходимо использовать префикс гер. Он заставляет циклически вы- полняться команду вывода в порт до тех пор, пока содержимое регистра есх/сх не станет равным нулю: .286 ;вывести последовательность 10 байт в порт 300h ;(номер порта взят условно) str_10 db 10 dup(O) adr_str dd str_10 Ids si,adr_str mov dx,300h rep outsb См. также: уроки 2, 7, 11 и команды cmps, lods, movs, ins, seas, stos, rep POP (POP operand from the stack) Извлечение операнда из стека pop приемник Назначение: извлечение слова или двойного слова из стека.
POPA (POP All general registers from the stack) 549 Синтаксис: Рис. П2.45. Синтаксическое описание команды pop Алгоритм работы: Алгоритм работы команды зависит от установленного атрибута размера адре- са — use16 или use32: О загрузить в приемник содержимое вершины стека (адресуется парой ss:esp/sp); О увеличить содержимое esp/sp на 4 (2 байта) для use32 (соответственно, для use16). Состояние флагов после выполнения команды: выполнение команды не влияет на флаги. Применение: Команда применяется для восстановления содержимого вершины стека в ре- гистр, ячейку памяти или сегментный регистр. Заметим, что недопустимо вос- становление значения в сегментный регистр cs: my_proc proc near push ax push bx ;тело процедуры, в которой изменяется содержимое регистров ах и Ьх pop bx pop ах ret endp См. также: уроки 7, 10, 14, 15, 16, 17 и команды рора, popad, popf, popfd, push, pusha, pushad, pushf, pushfd POPA (POP AU general registers from the stack) Извлечение всех регистров общего назначения из стека рора Назначение: извлечение из стека регистров общего назначения di, si, bp, sp, bx, dx, ex, ax.
550 Приложение 2. Описание системы команд микропроцессоров Intel Синтаксис: рора Алгоритм работы: О Извлечь из стека последовательно значения и загрузить ими регистры общего назначения di, si, bp, sp, bx, dx, сх, ax. Содержимое di восстанавливается пер- вым. Содержимое sp извлекается, но не восстанавливается; О увеличить значение указателя стека esp/sp на 16. Состояние флагов после выполнения команды: выполнение команды не влияет на флаги. Применение: Команда рора по принципу работы является обратной команде pusha и использу- ется для восстановления содержимого всех регистров общего назначения значе- ниями из стека. Эту команду можно использовать в процедурах и программах обработки прерываний для восстановления регистров общего назначения пре- рванной программы: .386 my.proc proc near pusha ;тело процедуры, в которой изменяется ; содержимое регистров общего назначения рора ret endp См. также: уроки 7, 10, 14, 15,16, 17 и команды pop, popad, popf, popfd, push, pusha, pushad, pushf, pushfd POPAD (POP All general Double word registers from the stack) Извлечение всех 32-разрядных регистров общего назначения из стека popad Назначение: извлечение из стека регистров общего назначения edi, esi, ebp, esp, ebx, edx, ecx, eax. Синтаксис: popad
PQPF (POP Flags register from the stack) 551 Алгоритм работы: О Извлечь из стека последовательно значения и загрузить ими 32-разрядные регистры общего назначения edi, esi, ebp, esp, ebx, edx, ecx, еах. Содержимое edi восстанавливается первым. Содержимое esp извлекается, но не восста- навливается; О увеличить значение указателя стека esp на 32. Состояние флагов после выполнения команды: выполнение команды не влияет на флаги. Применение: Команда popad по принципу работы является обратной команде pushad и ис- пользуется для восстановления всех 32-разрядных регистров общего назначе- ния. Эту команду можно использовать в процедурах и программах обработки прерываний для восстановления регистров общего назначения прерванной про- граммы: .386 my_proc proc near pushad ;тело процедуры, в которой изменяется ;содержимое регистров общего назначения popad ret endp См. также: уроки 7, 10, 14, 15, 16, 17 и команды pop, рора, popf, popfd, push, pusha, pushad, pushf, pushfd POPF (POP Flags register from the stack) Извлечение регистра флагов из стека popf Назначение: извлечение из стека слова и восстановление его в регистр флагов flags. Синтаксис: popf Алгоритм работы: О Извлечь из вершины стека слово и поместить его в регистр flags; О увеличить значение указателя стека esp на 2.
552 Приложение 2. Описание системы команд микропроцессоров Intel Состояние флагов после выполнения команды: выполнение команды не влияет на флаги. Применение: Команда popf по принципу работы является обратной команде pushf и ис- пользуется для восстановления из стека содержимого регистра флагов eflags. Возможным вариантом использования этой команды являются программы об- работки прерываний или другие случаи, в которых необходимо сохранять не- который локальный контекст процесса вычисления. Из-за того, что регистр eflags/flags непосредственно недоступен, команда popf является одной из не- многих возможностей влияния на его содержимое: установить значение регистра flags в 03h mov ax,3h push ax popf См. также: уроки 7,10, 14,15,16,17 и команды pop, рора, popad, popfd, push, pusha, pushad, pushf, pushfd POPFD (POP eFlags Double word register from the stack) Извлечение расширенного регистра флагов из стека popfd Назначение: извлечение из стека двойного слова и восстановление его в регистр флагов eflags. Синтаксис: popfd Алгоритм работы: О Извлечь из вершины стека двойное слово и поместить его в регистр eflags; О увеличить значение указателя стека esp на 4. Состояние флагов после выполнения команды: состояние флагов изменяется в соответствии с содержимым двойного слова, извлекаемого из стека. Применение: Команда popfd по принципу работы является обратной командой команде pushfd и используется для восстановления из стека содержимого регистра фла-
PUSH (PUSH operand onto stack)553 гов eflags. Необходимо отметить, что команда popfd не влияет на состояние фла- гов vm и rf: .386 установить значение регистра eflags в 03h mov eax,3h push еах popfd еах установить новое значение eflags См. также: уроки 7, 10, 14, 15,16,17 и команды pop, рора, popad, popf, push, pusha, pushad, pushf, pushfd PUSH (PUSH operand onto stack) Размещение операнда в стеке push источник Назначение: размещение содержимого операнда источник в стеке. Синтаксис: Рис. П2.46. Синтаксическое описание команды push Алгоритм работы: . О Уменьшить значение указателя стека esp/sp на 4/2 (в зависимости от значе- ния атрибута размера адреса — use16 или use32); О записать источник в вершину стека (адресуемую парой ss:esp/sp). Состояние флагов после выполнения команды: выполнение команды не влияет на флаги. Применение: Команда push используется совместно с командой pop для записи значений в стек и извлечения их из стека. Размер записываемых значений — слово или двойное слово. Также в стек можно записывать непосредственные значения. Заметьте, что в отличие от команды pop в стек можно включать значение сег- ментного регистра cs. Другой интересный момент связан с регистром sp. Ко-
554 Приложение 2. Описание системы команд микропроцессоров Intel манда push esp/sp записывает в стек значение esp/sp по состоянию до выдачи этой команды. В микропроцессоре i8086 по этой команде записывалось скор- ректированное значение sp. При записи в стек 8-битовых непосредственных значений для них все равно выделяется слово или двойное слово (в зависимос- ти от use16 или use32): my-ргос proc near push ах push bx ;тело процедуры, в которой изменяется содержимое регистров ах и Ьх pop bx pop ах ret endp См. также: уроки 7, 10, 14, 15,16, 17 и команды pop, рора, popad, popf, popfd, pusha, pushad, pushf, pushfd PUSHA (PUSH All general registers onto stack) Размещение всех регистров общего назначения в стеке pusha Назначение: размещение в стеке регистров общего назначения в следующей последователь- ности: ах, сх, dx, bx, sp, bp, si, di. Синтаксис: pusha Алгоритм работы: О Уменьшить значение указателя стека esp/sp на 32/16 (в зависимости от зна- чения атрибута размера адреса — use16 или use32); О включить в стек последовательно значения регистров общего назначения ах, сх, dx, bx, sp, bp, si, di. Содержимое di при этом будет на вершине стека. В стек помещается содержимое sp по состоянию до выполнения команды. Состояние флагов после выполнения команды: выполнение команды не влияет на флаги. Применение: Команда pusha используется совместно с командой рора для сохранения и вос- становления всех регистров общего назначения. Эти команды удобно использо- вать при работе с процедурами, программами обработки прерываний, а также в
PUSHAD (PUSH All general Double word registers onto stack) 555 других случаях для сохранения и восстановления регистров общего назначения как части контекста некоторого вычислительного процесса: my_proc proc near pusha ;тело процедуры, в которой изменяется ;содержимое регистров общего назначения рора ret endp См. также: уроки 7, 10, 14, 15,16, 17 и команды pop, popad, popf, popfd, push, рора, pushad, pushf, pushfd PUSHAD (PUSH All general Double word registers onto stack) Размещение всех регистров общего назначения в стеке pushad Назначение: размещение в стеке регистров общего назначения в следующей последователь- ности: еах, есх, edx, ebx, esp, ebp, esi, edi. Синтаксис: pushad Алгоритм работы: О Уменьшить значение указателя стека esp на 32; О включить в стек последовательно значения регистров общего назначения еах, есх, edx, ebx, esp, ebp, esi, edi. Содержимое edi при этом будет на вершине стека. Содержимое esp включается по состоянию на момент, предшествовавший выполнению данной команды. Состояние флагов после выполнения команды: выполнение команды не влияет на флаги. Применение: Команда pushad используется совместно с командой popad для сохранения и восстановления всех регистров общего назначения. Эти команды используются аналогично командам рора и pusha: .386 my.proc proc near pushad
556 Приложение 2. Описание системы команд микропроцессоров Intel ;тело процедуры, в которой изменяется ;содержимое регистров общего назначения popad ret endp См. также: уроки 7, 10, 14, 15, 16, 17 и команды pop, рора, popf, popfd, push, pusha, popad, pushf, pushfd PUSHF (PUSH Flags register onto stack) Размещение регистра флагов в стеке pushf Назначение: размещение в вершине стека (ss:sp) содержимого регистра флагов flags. Синтаксис: pushf Алгоритм работы: О Уменьшить значение указателя стека sp на 2; О поместить в вершину стека содержимое регистра flags. Состояние флагов после выполнения команды: выполнение команды не влияет на флаги. Применение: Команда pushf может использоваться для получения содержимого регистра флагов. Как известно, прямой доступ к регистру флагов невозможен, поэтому данная команда является одной из немногих команд, позволяющих получить доступ к регистру флагов как к содержимому обычного регистра. Обратное действие, то есть восстановление — возможно, измененного слова — в регистр флагов осуществляется командой popf. Эта команда может использоваться в программах обработки прерываний и в других случаях, когда необходимо со- хранить локальный контекст процесса вычисления: •«извлечь значение регистра flags и изменить ;значение флага cf на обратное pushf pop ах хог ax.Olh
PUSHFD (PUSH eFlags Double word register onto stack) 557 push ax popf См. также: уроки 7,10, 14,15,16,17 и команды pop, рора, popad, popfd, push, pusha, pushad, popf, pushfd PUSHFD (PUSH eFlags Double word register onto stack) Размещение расширенного регистра флагов в стеке pushfd Назначение: размещение в стеке содержимого регистра флагов eflags. Синтаксис: pushfd Алгоритм работы: О уменьшить значение указателя стека esp на 4; О записать в вершину стека двойное слово, представляющее собой содержи- мое регистра eflags. Состояние флагов после выполнения команды: выполнение команды не влияет на флаги. Применение: Команды pushfd и popfd используются аналогично командам pushf и popf. Ко- манда pushfd применяется для получения содержимого регистра флагов. Как известно, прямой доступ к регистру флагов невозможен, поэтому данная ко- манда является одной из немногих команд, позволяющих получить доступ к регистру флагов как к содержимому обычного регистра. Обратное действие, то есть восстановление — возможно измененного слова — в регистр флагов осу- ществляется командой popfd. Эта команда может использоваться в программах обработки прерываний или в других случаях, когда необходимо сохранить ло- кальный контекст процесса вычисления: .386 ;извлечь значение регистра eflags и изменить ; значение флага cf на обратное pushfd pop еах xor eax.Olh push еах popfd См. также: уроки 7, 10, 14, 15,16,17 и команды pop, рора, popad, popf, popfd, push, pusha, pushad, pushf
558 Приложение 2. Описание системы команд микропроцессоров Intel RCL (Rotate operand through Carry flag Left) Циклический сдвиг операнда влево через флаг переноса rd операнд, количество сдвигов Назначение: операция циклического сдвига операнда влево через флаг переноса cf. Синтаксис: гс! -I—| Г8,16,32 "1-г Нт8,16,32Н Рис. П2.47. Синтаксическое описание команды rd Алгоритм работы: О Сдвиг всех битов операнда влево на один разряд, при этом старший бит опе- ранда становится значением флага переноса cf; О одновременно старое значение флага переноса cf вдвигается в операнд справа и становится значением младшего бита операнда; О указанные выше два действия повторяются такое количество раз, которое равное значению второго операнда команды rcl. Состояние флагов после выполнения команды: И 00 OF CF ?г г Здесь обозначение ?г означает то, что анализ состояния флага имеет смысл при определенном сочетании операндов. В случае команды rcl флаг of представля- ет интерес, если сдвиг осуществляется на один разряд (см. ниже описание при- менения команды rcl). Применение: Команда rcl используется для циклического сдвига разрядов операнда влево. Особенность этого сдвига в том, что он происходит с некоторой задержкой, так как очередной сдвигаемый бит оказывается на некоторое время вне операнда. В это время можно произвести его извлечение и (или) подмену. Другой важ- ный момент заключается в том, что для счетчика сдвига микропроцессор ис- пользует только пять младших разрядов операнда количество.сдвигов. Таким образом, значение, большее 31, микропроцессором не допускается (аппаратно это ограничение реализуется тем, что игнорируются значения всех битов счет- чика, кроме первых пяти). Обратите внимание на еще один интересный эф-
RCR (Rotate operand through Carry flag Right) 559 фект, связанный с поведением флага of. В операциях сдвига на один разряд по изменению этого флага можно судить о факте изменения знакового (старшего) разряда операнда: О of=1, если текущее значение флага cf и выдвигаемого бита операнда слева различны; О of=0, если текущее значение флага cf и выдвигаемого бита операнда слева совпадают: ;сдвиг операнда, занимающего два двойных слова :на четыре разряда влево ch_l dd ... ;младшая часть 64-битного операнда ch-2 dd ... ;старшая часть 64-битного операнда mov сх,4 ;счетчик сдвигов в сх mov eax,ch_l mov edx,ch_h ml: clc ;очистка флага cf rcl eax,1 ;старший бит еах в cf rcl edx,1 ;cf в младший бит edx, ;старший бит edx в cf loop ml См. также: урок 9 и команды rcr, rol, ror, sal, sar, shl, shr RCR (Rotate operand through Carry flag Right) Циклический сдвиг операнда вправо через флаг переноса гсг операнд,количествО-Сдвигов Назначение: операция циклического сдвига операнда вправо через флаг переноса cf. Синтаксис: Рис. П2.48. Синтаксическое описание команды гсг Алгоритм работы: О Сдвиг всех битов операнда вправо на один разряд; при этом младший бит операнда становится значением флага переноса cf; О одновременно старое значение флага переноса cf вдвигается в операнд слева и становится значением старшего бита операнда;
560 Приложение 2. Описание системы команд микропроцессоров Intel О указанные выше два действия повторяются количество раз, равное значению второго операнда команды гсг. Состояние флагов после выполнения команды: И 00 OF CF ?г г Здесь обозначение ?г означает то, что анализ состояния флага имеет смысл при определенном сочетании операндов. В случае команды гсг флаг of представля- ет интерес, если сдвиг осуществляется на один разряд (см. ниже описание при- менения команды гсг). Применение: Команда гсг используется для циклического сдвига разрядов операнда вправо. Особенность этого сдвига в том, что он происходит с некоторой задержкой, так как очередной сдвигаемый бит оказывается на некоторое время вне операнда. В это время можно произвести его извлечение и (или) подмену. Другой важ- ный момент заключается в том, что для счетчика сдвига микропроцессор ис- пользует только пять младших разрядов операнда количество_сдвигов. Таким образом, значение, большее 31, не допускается (аппаратно это ограничение ре- ализуется тем, что игнорируются значения битов счетчика старше пятого). Об- ратите внимание на еще один интересный эффект, связанный с поведением флага of, — его значение имеет смысл только в операциях сдвига на один раз- ряд и обусловлено тем, что по изменению этого флага можно судить о факте изменения знакового разряда операнда: О of=1, если текущие (то есть до операции сдвига) значения флага cf и стар- шего, левого бита операнда различны; О of=0, если текущие (то есть до операции сдвига) значения флага cf и стар- шего, левого бита операнда слева совпадают: ;подсчет числа единичных битов в операнде operand dw mov ex, 16 ;размер операнда хог al, al ;счетчик единичных битов cycl: rcr operand,1 jc $+4 ;переход, если очередной ;выдвинутый бит равен 1 jmp $+4 ;переход, если очередной ;выдвинутый бит равен 0 inc al увеличить счетчик единичных битов loop cycl См. также: урок 9 и команды rcl, rol, ror, sal, sar, shl, shr
REP/REPE/REPZ/REPNE/REPNZ (REPeat string operation) 561 REP/REPE/REPZ/REPNE/REPNZ (REPeat string operation) Повторить цепочечную операцию rep гере repz repne repnz Назначение: указание условного и безусловного повторения следующей за данной командой цепочечной операции. Синтаксис: гер гере repz repne repnz Алгоритм работы: Алгоритм работы зависит от конкретного префикса. Префиксы гер, гере и repz на самом деле имеют одинаковый код операции, их действия зависят от той цепочечной команды, которую они предваряют: О гер используется перед следующими цепочечными командами и их кратки- ми эквивалентами: movs, stos, ins, outs. Действие rep: 1) анализ содержимого сх: если сх<>0, то выполнить цепочечную команду, следующую за данным префиксом, и перейти к шагу 2; если сх=0, то передать управление команде, следующей за данной цепо- чечной командой (выйти из цикла по гер); 2) уменьшить значение сх=сх-1 и вернуться к шагу 1; О гере и repz используются перед следующими цепочечными командами и их краткими эквивалентами: cmps, seas. Действия гере и repz: 1) анализ содержимого сх и флага zf: если сх<>0 или zf<>0, то выполнить цепочечную команду, следующую за данным префиксом, и перейти к шагу 2;
562 Приложение 2. Описание системы команд микропроцессоров Intel если сх=0 или zf=0, то передать управление команде, следующей за данной цепочечной командой (выйти из цикла по гер); 2) уменьшить значение сх=сх-1 и вернуться к шагу 1; О герпе и repnz также имеют один код операции и имеют смысл при использо- вании перед следующими цепочечными командами и их краткими эквива- лентами: cmps, seas. Действия герпе и repnz: 1) анализ содержимого сх и флага zf: если сх<>0 или zf=0, то выполнить цепочечную команду, следующую за данным префиксом, и перейти к шагу 2; если сх=0 или zf<>0, то передать управление команде, следующей за дан- ной цепочечной командой (выйти из цикла по гер); 2) уменьшить значение сх=сх-1 и вернуться к шагу 1. Состояние флагов после выполнения команды: 06 ZF г Применение: Команды гер, гере, repz, герпе и repnz в силу специфики своей работы называ- ются префиксами. Они имеют смысл только при использовании цепочечных операций, заставляя их циклически выполняться и тем самым без организации внешнего цикла обрабатывать последовательности элементов фиксированной длины. Большинство применяемых префиксов являются условными, то есть они прекращают работу цепочечной команды при выполнении определенных условий. См. также: урок 11 и команды cmps, ins, outs, movs, seas, stos RET/RETF (RETurn/RETurn Far from procedure) Возврат ближний (дальний) из процедуры ret ret число Назначение: возврат управления из процедуры вызывающей программе. Синтаксис: ret ret 116
RET/RETF (RETurn/RETurn Far from procedure) 563 Алгоритм работы: Работа команды зависит от типа процедуры: О для процедур ближнего типа — восстановить из стека содержимое eip/ip; О для процедур дальнего типа — последовательно восстановить из стека содер- жимое eip/ip и сегментного регистра cs; О если команда ret имеет операнд, то увеличить содержимое esp/sp на величи- ну операнда число; при этом учитывается атрибут режима адресации — use 16 или use32: • если use16, то sp=(sp+4Mcno), то есть указатель стека сдвигается на число байт, количеством равное значению число; • если use32, то sp=(sp+2*4Mcno), то есть указатель стека сдвигается на чис- ло слов, количеством равное значению число. Состояние флагов после выполнения команды: выполнение команды не влияет на флаги. Применение: Команду ret необходимо применять для возврата управления вызывающей про- грамме из процедуры, управление которой было передано по команде call. На самом деле, микропроцессор имеет три варианта команды возврата ret — это ret, ее синоним retn, а также команда retf. Они отличаются типами процедур, в которых используются. Команды ret и retn служат для возврата из процедур ближнего типа. Команда retf — команда возврата для процедур дальнего типа. Какая конкретно команда будет использоваться, определяется компилятором; программисту лучше использовать команду ret и доверить транслятору самому сгенерировать ее ближний или дальний вариант. Количество команд ret в про- цедуре должно соответствовать количеству точек выхода из нее. Некоторые языки высокого уровня, к примеру Pascal, требуют, чтобы вызывае- мая процедура очищала стек от переданных ей параметров. Для этого команда ret содержит необязательный параметр число, который в зависимости от уста- новленного атрибута размера адреса означает количество байт или слов, удаля- емых из стека по окончании работы процедуры. my_proc proc ret 6 endp См. также: уроки 10, 14 и команду call
564 Приложение 2. Описание системы команд микропроцессоров Intel ROL (Rotate operand Left) Циклический сдвиг операнда влево rol операнд,количество.сдвигов Назначение: операция циклического сдвига операнда влево. Синтаксис: Рис. П2.49. Синтаксическое описание команды rol Алгоритм работы: О Сдвиг всех битов операнда влево на один разряд, при этом старший бит опе- ранда вдвигается в операнд справа и становится значением младшего бита операнда; О одновременно выдвигаемый бит становится значением флага переноса cf; О указанные выше два действия повторяются количество раз, равное значению второго операнда. Состояние флагов после выполнения команды: И 00 OF CF ?г г Применение: Команда rol используется для циклического сдвига разрядов операнда влево. Отличие этого сдвига от rcl в том, что очередной сдвигаемый бит одновремен- но вдвигается в операнд справа и становится значением флага cf. Так же как и для других сдвигов, значение второго операнда (счетчика сдвига) ограничено диапазоном 0...31. Это объясняется тем, что микропроцессор использует только пять младших разрядов операнда количество_сдвигов. Аналогично другим ко- мандам сдвига, сохраняется эффект, связанный с поведением флага of, значе- ние которого имеет смысл только в операциях сдвига на один разряд: О если of=1, то текущее значение флага cf и выдвигаемого слева бита операн- да различны; О если of=0, если текущее значение флага cf и выдвигаемого слева бита опе- ранда совпадают.
RQR (Rotate operand Right) 565 Этот эффект, как вы помните, обусловлен тем, что флаг of устанавливается в единицу всякий раз при изменении знакового разряда операнда: ;поменять местами половинки регистра еах: mov еах,OffffOOOOh mov cl,16 rol еах,cl ;eax=0000ffffh См. также: урок 9 и команды rcr, rcl, ror, sal, sar, shl, shr ROR (Rotate operand Right) Циклический сдвиг операнда вправо ror операнд,количество.сдвигов Назначение: операция циклического сдвига операнда вправо. Синтаксис: Рис. 112,50. Синтаксическое описание команды ror Алгоритм работы: О Сдвиг всех битов операнда вправо на один разряд, при этом младший бит операнда вдвигается в операнд слева и становится значением старшего бита операнда; О одновременно этот младший бит операнда становится значением флага пе- реноса cf; О старое значение флага переноса cf вдвигается в операнд слева и становится значением старшего бита операнда; О указанные выше два действия повторяются количество раз, равное значе- нию второго операнда. Состояние флагов после выполнения команды: И 00 OF CF ?г г
566 Приложение 2. Описание системы команд микропроцессоров Intel Применение: Команда тог используется для циклического сдвига разрядов операнда вправо. Отличие этого сдвига от гсг в том, что очередной сдвигаемый бит одновремен- но вдвигается в операнд слева и становится значением флага cf. Так же как и для других сдвигов, значение второго операнда (счетчика сдвига) ограничено диапазоном 0...31. Это объясняется тем, что микропроцессор использует только пять младших разрядов операнда количество_сдвигов. Аналогично другим ко- мандам сдвига, сохраняется эффект, связанный с поведением флага of, значе- ние которого имеет смысл только в операциях сдвига на один разряд: О если of=1, то текущее значение флага cf и вдвигаемого слева бита операнда различны; О если of=0, если текущее значение флага cf и вдвигаемого слева бита операн- да совпадают; Этот эффект, как вы помните, обусловлен тем, что флаг of устанавливается в единицу всякий раз при изменении знакового разряда операнда: ;поместить четыре младших бита ах ;на место старших битов: гог ах, 4 См. также: уроки 9 и команды гсг, rcl, rol, sal, sar, shl, shr SAHF (Store AH register into register Flags) Загрузка регистра флагов eFlags/Flags из регистра АН sahf Назначение: запись содержимого регистра ah в младший байт регистра eflags/flags, в кото- ром содержатся пять флагов cf, pf, af, zf и sf. Синтаксис: sahf Алгоритм работы: Команда загружает младший байт регистра eflags/flags содержимым регистра ah. В битах 7, 6, 4, 2 и 0 регистра ah должны, соответственно, содержаться но- вые значения флагов sf, zf, af, pf и cf. Состояние флагов после выполнения команды: 07 06 04 02 00 SF ZF AF PF CF г г г г г
SAL (Shift Arithmetic operand Left) 567 Применение: Эта команда используется совместно с командой lahf. Из-за того, что регистр флагов непосредственно недоступен, сочетание этих команд можно применять для анализа — и возможно, изменения — состояния некоторых флагов в регист- ре eflags/flags. Содержимое старшей части регистра флагов не изменяется: ;сбросить в ноль флаг cf lahf and ah,11111110b sahf См. также: уроки 2, 7 и команду lahf SAL (Shift Arithmetic operand Left) Сдвиг арифметический операнда влево sal операнд,количество-сдвигов Назначение: арифметический сдвиг операнда влево. Синтаксис: Рис. П2.51. Синтаксическое описание команды sal Алгоритм работы: О сдвиг всех битов операнда влево на один разряд, при этом выдвигаемый слева бит становится значением флага переноса cf; О одновременно справа в операнд вдвигается нулевой бит; О указанные выше два действия повторяются количество раз, равное значе- нию второго операнда. Состояние флагов после выполнения команды: И 00 OF CF ?г г Применение: Команда sal используется для сдвига разрядов операнда влево. Так же, как и для других сдвигов, значение второго операнда (счетчика сдвига) ограничено диапазоном 0...31. Это объясняется тем, что микропроцессор использует только
568 Приложение 2. Описание системы команд микропроцессоров Intel пять младших разрядов количество_сдвигов. Аналогично другим командам сдвига, сохраняется эффект, связанный с поведением флага of, значение которого имеет смысл только в операциях сдвига на один разряд: О если of=1, то текущее значение флага cf и выдвигаемого слева бита операнда различны; О если of=0, то текущее значение флага cf и выдвигаемого слева бита операнда совпадают. Этот эффект, как вы помните, обусловлен тем, что флаг cf устанавливается в единицу всякий раз при изменении знакового разряда операнда. Команду sal удобно использовать для умножения целочисленных операндов без знака на степени 2. Кстати сказать, это самый быстрый способ такого умно- жения: ;умножить содержимое ах на 16 (2 в степени 4): mov ах,17 sal ах,4 См. также: уроки 8, 9 и команды rcr, rcl, гог, rol, sar, shl, shr SAR (Shift Arithmetic operand Right) Сдвиг арифметический операнда вправо sar операнд,количество.сдвигов Назначение: арифметический сдвиг операнда вправо. Синтаксис: Рис. П2.52. Синтаксическое описание команды sar Алгоритм работы: О Сдвиг всех битов операнда вправо на один разряд, при этом выдвигаемый справа бит становится значением флага переноса cf; О Обратите внимание: одновременно слева в операнд вдвигается не нулевой бит, а значение старшего бита операнда, то есть по мере сдвига вправо осво- бождающиеся места заполняются значением знакового разряда. По этой причине этот тип сдвига и называется арифметическим; О указанные выше два действия повторяются количество раз, равное значе- нию второго операнда.
SBB (SuBtract with Borrow) 569 Состояние флагов после выполнения команды: И 00 OF CF ?г г Применение: Команда sar используется для арифметического сдвига разрядов операнда вправо. Так же как и для других сдвигов, значение второго операнда (счетчика сдвига) ограничено диапазоном 0...31. Это объясняется тем, что микропроцес- сор использует только пять младших разрядов операнда количество_сдвигов. В отличие от других команд сдвига флаг of всегда сбрасывается в ноль в опе- рациях сдвига на один разряд. Команду sar можно использовать для деления целочисленных операндов со знаком на степени 2: mov ах, 88 sar ах, 2 ;(ах) разделить на 2 ;во второй степени, то есть на 4 См. также: урок 8, 9 и команды rcr, rcl, ror, rol, sal, shl, shr SBB (SuBtract with Borrow) Вычитание сзаемом sbb операнда, операнд 2 Назначение: целочисленное вычитание с учетом результата предыдущего вычитания коман- дами sbb и sub (по состоянию флага переноса cf). Синтаксис: Рис. П2.53. Синтаксическое описание команды sbb
570 Приложение 2. Описание системы команд микропроцессоров Intel Алгоритм работы: О Выполнить сложение операнд_2=операнд_2+(сТ); О выполнить вычитание операнд_1=операнд_2-операнд_1. Состояние флагов после выполнения команды: 11 07 06 04 02 OF SF ZF AF PF 00 CF г г г г г г Применение: Команда sbb используется для выполнения вычитания старших частей значений многобайтных операндов с учетом возможного предыдущего заема при вычита- нии младших частей значений этих операндов: ; выполнить вычитание 64-битных значений: vichj-vich_2 vich_1 dd 2 dup (0) vich_2 dd 2 dup (0) rezdd 2 dup (0) ; ввести значения в поля vich_1 и vich_2: ; младший байт по младшему адресу mov sub mov eax,vich_1 eax,vich_2 ;вычесть младшие половинки чисел rez,еах ;младшая часть результата mov sbb еах,vich_1+4 eax,vich_2+4 ;вычесть старшие половинки чисел mov rez+4,eax ;старшая часть результата См. также: урок 8, приложение 7 и команды sub SCAS/SCASB/SCASW/SCASD (SCAn String Byte/Word/ Double word) Сканирование строки байтов/слов/двойныхслов seas приемник scasb scasw scasd Назначение: поиск значения в последовательности (цепочке) элементов в памяти.
SCAS/SCASB/SCASW/SCASD (SCAn String Byte/Word/Double word) 571 Синтаксис: seas m8, 16, 32 scasb scasw scasd Алгоритм работы: О Выполнить вычитание (элемент цепочки-(еах/ах/а!)). Элемент цепочки лока- лизуется парой es: edi/di. Замена сегмента es не допускается; О по результату вычитания установить флаги; О изменить значение регистра edi/di на величину, равную длине элемента це- почки. Знак этой величины зависит от состояния флага df: • df=O — величина положительная, то есть просмотр от начала цепочки к ее концу; • df=1 — величина отрицательная, то есть просмотр от конца цепочки к ее началу. Состояние флагов после выполнения команды: и 07 06 04 02 00 OF SF ZF AF PF CF г г г г г г Применение: Команды сканирования сравнивают значение в регистре еах/ах/а! с ячейкой памяти, локализуемой парой регистров es:edi/di. Размер сравниваемого эле- мента зависит от применяемой команды. Команда seas может работать с эле- ментами размером в байт, слово или двойное слово. В качестве операнда в ко- манде указывается идентификатор последовательности элементов в памяти. Реально этот идентификатор используется лишь для получения типа элемен- тов последовательности, а ее адрес должен быть предварительно загружен в указанную выше пару регистров. Транслятор, обработав команду seas и выяс- нив тип операндов, генерирует одну из машинных команд scasb, scasw или scasd. Машинного аналога для команды seas нет. Для адресации операнда ис- точник обязательно должен использоваться регистр es. Для того чтобы эту команду можно было использовать для поиска значения в последовательности элементов, имеющих размерность байт, слово или двойное слово, необходимо использовать один из префиксов гере или герпе. Эти пре- фиксы не только заставляют циклически выполняться команду поиска пока есх/сх<>0, но и отслеживают состояние флага zf (см. команды гер/гере/герпе):
572 Приложение 2. Описание системы команд микропроцессоров Intel сосчитать число пробелов в строке str .data strdb len_str=$-str .code mov ax,©data mov ds, ax mov es, ax lea di, str mov cx,len_str ;длину строки - в сх mov al,' ‘ mov bx,0 ; счетчик для подсчета ; пробелов в строке cld cycl: гере scasb jcxz exit ; переход на exit, если цепочка ; просмотрена полностью inc bx jmp cycl exit: См. также: урок 11 и команды cmps, ins, lods, movs, outs, stos, rep, repe, repz, repne, repnz SETcc (byte SET on condition) Установка байта по условию setcc операнд Назначение: установка операнда логическим значением в зависимости от истинности усло- вия, заданного модификатором кода операции сс. Синтаксис: setcc т| гв |-~ Рис. П2.54. Синтаксическое описание команды setcc
SETcc (byte SET on condition) 573 Алгоритм работы: Команда проверяет истинность условия, заданного в коде операции, то есть, фак- тически, состояние определенных флагов (табл. П2.1). Таблица П2.1. Команды установки байтов Команда Проверяемые флаги Логическое условие SETA/SETNBE CF = 0 и ZF = 0 (Выше)/(не ниже или равно) SETAE/SETNB CF = 0 (Выше или равно)/(не ниже) SETB/SETNAE CF = 1 (Ниже)/(не выше или равно) SETBE/SETNA CF = 1 или ZF = 1 (Ниже или равно)/(не выше) SETC CF = 1 Перенос SETE/SETZ ZF = 1 Ноль SETG/SETNLE ZF = 0 или SF - OF (Болыпе)/(не меньше или равно) SETGE/SETNL SF = OF (Больше или равно)/(не меньше) SETL/SETNGE SF <> OF Если SF <> OF SETLE/SETNG ZF=1 или SF <> OF (Меньше или равно)/(не больше) SETNC CF = 0 Нет переноса SETNE/SETNZ ZF = 0 Не равно нулю SETNO OF=0 Нет переполнения SETNP/SETPO PF - 0 (Неравенство)/(нет контроля четности) SETNS SF = 0 Нет знака, число положительное SETO OF = 1 Переполнение SETP/SETPE PF= 1 Контроль четности/равенство SETS SF = 1 Если знак минус, число отрицательное Если проверяемое условие (или содержимое соответствующих флагов на мо- мент выдачи команды setcc) истинно, то установить значение операнда в 01 h, если условие ложно — то в 00h. Состояние флагов после выполнения команды: выполнение команды не влияет на флаги. Применение: Эти команды можно использовать после любой команды, изменяющей флаги, при необходимости анализа результата изменений. Если проанализировать ус- ловия для команд условного перехода, то обнаружится их полное соответствие
574 Приложение 2. Описание системы команд микропроцессоров Intel с условиями, обрабатываемыми командой setcc, за исключением, конечно, ко- манд jcxz и jecxz: ;подсчитать число единичных битов в регистре ах mov сх,16 ml: rol ах,1 setc Ы add bh.bl clc loop ml См. также: урок 10 и команду jcc SGDT (Store Global Descriptor Table) Сохранение регистра глобальной дескрипторной таблицы sgdt источник Назначение: извлечение содержимого системного регистра gdtr, содержащего значения ба- зового адреса и размера глобальной дескрипторной таблицы GDT. Синтаксис: sgdt m48 Алгоритм работы: Команда выполняет чтение содержимого системного регистра gdtr в область памяти размером 48 бит. Структурно эти 48 бит представляют 16 бит размера и 32 бита значения базового адреса начала таблицы GDT в памяти. Состояние флагов после выполнения команды: выполнение команды не влияет на флаги. Применение: Команду sgdt применяют при работе системных программ с уровнем привиле- гий 0, в частности, при написании различных драйверов: .286 ;структура для описания псевдодескриптора gdtr point STRUC lim dw 0 adr dd 0 ENDS .data point_gdt point <gdt_size,O>
SIDT (Store Interrupt Descriptor Table) 575 .code ;читаем содержимое gdtr sgdt point.gdt См. также: уроки 16,17 и команду Igdt SIDT (Store Interrupt Descriptor Table) Сохранение регистра дескрипторной таблицы прерываний sidt приемник Назначение: извлечение содержимого системного регистра idtr, содержащего значения базо- вого адреса и размера дескрипторной таблицы прерываний IDT. Синтаксис: sidt m48 Алгоритм работы: Команда sidt выполняет чтение содержимого системного регистра idtr в область памяти размером 48 бит. Структурно эти 48 бит представляют 16 бит размера и 32 бита значения базового адреса начала таблицы IDT в памяти. Состояние флагов после выполнения команды: выполнение команды не влияет на состояние флагов Применение: Команду sidt применяют при работе системных программ с уровнем привилегий О, в частности, при написании различных драйверов. В качестве операнда в ко- манде указывается адрес области в формате 16+32. Младшее слово области — размер IDT, двойное слово по старшему адресу — значение базового адреса на- чала этой таблицы: .286 структура для описания псевдодескрипторов gdtr и idtr point STRUC limdw О adrdd О ENDS .data
576 Приложение 2. Описание системы команд микропроцессоров Intel point_idt point <idt_size,0> .code ;читаем содержимое idtr sidt point_idt См. также: урок 17 и команду lidt SHL (SHift logical Left) Сдвиглогический операнда влево shl операнд,количество.сдвигов Назначение: логический сдвиг операнда влево. Синтаксис: shl г8,16,32 П] 4ZEP Рис. П2.55. Синтаксическое описание команды shl Алгоритм работы: О Сдвиг всех битов операнда влево на один разряд, при этом выдвигаемый слева бит становится значением флага переноса cf; О одновременно слева в операнд вдвигается нулевой бит; О указанные выше два действия повторяются количество раз, равное значе- нию второго операнда. Состояние флагов после выполнения команды: 11 00 OF CF ?г г Применение: Команда shl используется для сдвига разрядов операнда влево. Ее машинный код идентичен коду sal, поэтому вся информация, приведенная для sal, отно- сится и к команде shl. Команда shl используется для сдвига разрядов операнда влево. Так же как и для других сдвигов, значение второго операнда (счетчика
SHLD (SHift Left Double word) 577 сдвига) ограничено диапазоном 0...31. Это объясняется тем, что микропроцессор использует только пять младших разрядов операнда количество_сдвигов. Анало- гично другим командам сдвига, сохраняется эффект, связанный с поведением флага of, значение которого имеет смысл только в операциях сдвига на один раз- ряд: О если of=1, то текущее значение флага cf и выдвигаемого слева бита операнда различны; О если of=0, то текущее значение флага cf и выдвигаемого слева бита операнда совпадают. Этот эффект, как вы помните, обусловлен тем, что флаг of устанавливается в единицу всякий раз при изменении знакового разряда операнда. Команду shl удобно использовать для умножения целочисленных операндов без знака на степени 2. Кстати сказать, это самый быстрый способ умножения: ;умножить содержимое ах на 16 (2 в степени 4) mov ах,17 shl ах,4 См. также: урок 9 и команды rcr, rcl, ror, rol, sar, sal, shr SHLD (SHift Left Double word) Сдвигдвойного слова влево shld приемник,источник,количество сдвигов Назначение: логический сдвиг двойного слова влево. Синтаксис: Рис. П2.56. Синтаксическое описание команды shld Алгоритм работы: О Сдвинуть операнд приемник влево на количество битов, определяемое опе- рандом количествО-СДвигов', О одновременно сдвинуть операнд источник влево на количество битов, опре- деляемое операндом количество_сдвигов. Важно заметить, что операнд источ-
578 Приложение 2. Описание системы команд микропроцессоров Intel ник только обеспечивает вдвигаемые в операнд приемник биты, сам он при этом не изменяется; О выдвигаемые во время сдвига влево из операнда источник биты вдвигаются в операнд приемник с его правого края. Состояние флагов после выполнения команды: и 07 06 04 02 00 OF SF ZF AF PF CF ? г г ? г г Применение: Команда shld используется для манипуляции битовыми строками длиной до 64 бит. Эту команду удобно использовать для быстрой вставки (или извлече- ния) битовой строки в большую битовую строку; при этом, что очень важно, не разрушается контекст (битовое окружение) этих подстрок: .386 ;извлечь старшую половину еах в Ьх без разрушения еах mov cl, 16 shld ebx,еах,cl push bx shl ebx,cl shld eax,ebx,cl ;восстановим eax pop bx См. также: урок 9 и команды rcr, rcl, ror, rol, sar, sal, shr, shrd SHR (SHift logical Right) Сдвиглогический операнда вправо shr операнд,кол-во сдвигов Назначение: логический сдвиг операнда вправо. Синтаксис: shr г8,16,32 ГН Рис. П2.57. Синтаксическое описание команды shr
SHRD (SHift Right Double word) 579 Алгоритм работы: О Сдвиг всех битов операнда вправо на один разряд; при этом выдвигаемый справа бит становится значением флага переноса cf; О одновременно слева в операнд вдвигается нулевой бит; О указанные выше два действия повторяются количество раз, равное значе- нию второго операнда. Состояние флагов после выполнения команды: 11 07 06 04 02 OF SF ZF AF PF ?r г г ? r 00 CF г Применение: Команда shr используется для логического сдвига разрядов операнда вправо. Так же как и для других сдвигов, значение второго операнда (счетчика сдвига) ограничено диапазоном 0...31. Это объясняется тем, что микропроцессор ис- пользует только пять младших разрядов операнда количество_сдвигов. В отли- чие от других команд сдвига, флаг of всегда сбрасывается в ноль в операциях сдвига на один разряд. Команду shr можно использовать для деления целочисленных операндов без знака на степени 2: mov cl,4 shr еах,cl ;(еах) разделить на 2 в степени 4 См. также: урок 9 и команды rcr, rcl, ror, rol, sal, shl, sar SHRD (SHift Right Double word) Сдвигдвойного слова вправо shrd приемник,источник,количество сдвигов Назначение: логический сдвиг двойного слова вправо. Синтаксис: Рис. П2.58. Синтаксическое описание команды shrd
580 Приложение 2. Описание системы команд микропроцессоров Intel Алгоритм работы: О Сдвинуть операнд приемник вправо на количество битов, определяемое опе- рандом количествО-Сдвигов; О одновременно сдвинуть операнд источник вправо на количество битов, опре- деляемое операндом количество_сдвигов. Важно заметить, что операнд ис- точник только обеспечивает вдвигаемые в операнд приемник биты, сам он при этом не изменяется; О выдвигаемые вправо во время сдвига из операнда источник биты вдвигаются в операнд приемник с его левого конца. Состояние флагов после выполнения команды: И 07 06 04 02 00 OF SF ZF AF PF CF ? г г ? г г Применение: Команда shrd используется для манипуляции битовыми строками длиной до 64 бит. Эту команду удобно использовать для быстрой вставки (или извлече- ния) битовой строки в большую битовую строку, при этом, что очень важно, не разрушается контекст (битовое окружение) этих подстрок: .386 ;разделить операнд размером 64 бит на степень 2 ор_1 dd ... ;младшая часть операнда op_h dd ... ;старшая часть операнда mov eax,op_h shld ор_1,еах,4 ;разделить операнд на 4 ;так как старшая часть операнда реально еще не сдвинулась, ;то нужно привести ее в соответствие с результатом shr op_h,4 ; См. также: урок 9 и команды rcr, rcl, ror, rol, sar, sal, shr, shld STC (Set Carry Flag) Установка флага переноса stc Назначение: установка флага переноса cf в 1.
STD (SeT Direction Flag) 581 Синтаксис: stc Алгоритм работы: Установить флаг cf в 1. Состояние флагов после выполнения команды: 00 CF 1 Применение: Данная команда используется для установки флага cf в 1. Такая необходимость может возникнуть при работе с командами сдвига, арифметическими командами или действиями по индикации ошибок в программах: stc ; cf=1 См. также: уроки 2, 8, 9 и команды cmc, clc STD (SeT Direction Flag) Установка флага направления std Назначение: установка флага направления df в 1. Синтаксис: std Алгоритм работы: Установить флаг df в 1. Состояние флагов после выполнения команды: 10 DF 1
582 Приложение 2. Описание системы команд микропроцессоров Intel Применение: Данная команда используется для установки флага df в 1. Такая необходимость может возникнуть при работе с цепочечными командами. Единичное состояние флага df вынуждает микропроцессор производить декремент регистров si и di при выполнении цепочечных операций: std ;df=1 ; смотрите материал урока 11 См. также: уроки 2, 11 и команду cld STI (SeT Interrupt flag) Установка флага прерывания sti Назначение: установка флага прерывания if в 1. Синтаксис: sti Алгоритм работы: Установить флаг if в 1. Состояние флагов после выполнения команды: 09 IF 1 Применение: Данная команда используется для установки флага if в единицу. Такая необ- ходимость может возникнуть при разработке программ обработки прерываний: sti ;if=1 См. также: урок 2, 15, 17 и команду cli
STOS/STQSB/STOSW/STOSD (Store String Byte/Word/Double word operands) 583 STOS/STOSB/STOSW/STOSD (Store String Byte/Word/Double word operands) Сохранение строки байтов/слов/двойных слов stos приемник stosb stosw stosd Назначение: сохранение элемента из регистра-аккумулятора al/ax/eax в последовательнос- ти (цепочке). Синтаксис: ______ —|m16|- -stosb |- |-д - stosw - stosd Рис. П2.59. Синтаксическое описание команд сохранения регистра al/ax/eax в элементе цепочки Алгоритм работы: О Записать элемент из регистра al/ax/eax в ячейку памяти, адресуемую парой es:di/edi. Размер элемента определяется неявно (для команды stos) или конкретной применяемой командой (для команд stosb, stosw, stosd); О изменить значение регистра di на величину, равную длине элемента цепоч- ки. Знак этого изменения зависит от состояния флага df: df=0 — увеличить, что означает просмотр от начала цепочки к ее концу; df=1 — уменьшить, что означает просмотр от конца цепочки к ее началу. Состояние флагов после выполнения команды: выполнение команды не влияет на флаги. Применение: Команды сохраняют элемент из регистров al/ax/eax в ячейке памяти. Перед командой stos можно указать префикс повторения гер, в этом случае появляет-
584 Приложение 2. Описание системы команд микропроцессоров Intel ся возможность работы с блоками памяти, заполняя их значениями в соответ- ствии с содержимым регистра есх/сх: [заполнить некоторую область памяти пробелами str db ’’Какая-то строка” len_str=$-str mov ах,©data mov ds, ах mov es,ax cld mov al,' ” lea di,str mov cx,len_str rep stosb [заполняем пробелами строку str [пример совместной работы stosb и lodsb: [копировать одну строку в другую до первого пробела strl db "Какая-то строка” len_str1=$-str str2 db len_str1 dup (” ’’) mov ax,©data mov ds,ax mov es.ax cld mov cx,len_str1 lea si,str1 lea di,str2 ml: lodsb cmp al, ’ ’’ jc exit [выход, если пробел stosb loop ml exit: См. также: урок 11 и команды ins, cmps, movs, outs, seas, lods, rep, repe, repz repne, repnz
SUB (SUBtract) 585 SUB (SUBtract) Вычитание sub операнд 1,операнд 2 Назначение: целочисленное вычитание. Синтаксис: Рис. П2.60. Синтаксическое описание команды sub Алгоритм работы: О Выполнить вычитание операнд_1=операнд_2-операнд_1; О установить флаги. Состояние флагов после выполнения команды: 11 07 06 04 02 OF SF ZF AF PF г г г г r 00 CF r Применение: Команда sub используется для выполнения вычитания целочисленных операн- дов или для вычитания младших частей значений многобайтных операндов: выполнить вычитание 64-битных значений: vich_1-vich_2 vich_1 dd 2 dup (0) vich_2 dd 2 dup (0) rez dd 2 dup (0) ;ввести значения в поля vich_1 и vich_2:
586 Приложение 2. Описание системы команд микропроцессоров Intel ; младший байт по младшему адресу mov eax,vich_1 sub eax,vich_2 ;вычесть младшие половинки чисел mov rez,еах ;младшая часть результата mov eax,vich_1+4 sbb eax,vich_2+4 ;вычесть старшие половинки чисел mov rez+4,eax ;старшая часть результата См. также: урок 8, приложение 7 и команду sbb TEST (TEST operand) Логическое И test приемник,источник Назначение: операция логического сравнения операндов приемник и источник размерностью байт, слово или двойное слово. Синтаксис: Рис. П2.61. Синтаксическое описание команды test Алгоритм работы: О Выполнить операцию логического умножения над операндами приемник и источник: бит результата равен 1, если соответствующие биты операндов равны 1, в остальных случаях бит результата равен 0; О установить флаги.
XADD (eXchange and ADD) 587 Состояние флагов после выполнения команды: И 07 06 02 00 OF SF ZF PF CF 0 г г г 0 Применение: Команда test используется для логического умножения двух операндов. Ре- зультат операции, в отличие от команды and, никуда не записывается, устанав- ливаются только флаги. Эту команду удобно использовать для получения ин- формации о состоянии заданных битов операнда приемник. Для анализа результата используется флаг zf, который равен 1, если результат логического умножения равен нулю: test al,01h jnz ml ;переход, если нулевой бит al равен 1 См. также: урок 9 и команды or, xor, and, bt XADD (exchange and ADD) Обмен и сложение xadd приемник,источник Назначение: суммирование и обмен двух значений. Синтаксис: Рис. П2.62. Синтаксическое описание команды xadd Алгоритм работы: О Копировать содержимое операнда приемник в операнд источник; О выполнить сложение (приемник + источник); О поместить сумму в операнд приемник.
588 Приложение 2. Описоние системы команд микропроцессоров Intel Состояние флагов после выполнения команды: И 07 06 04 02 00 OF SF ZF AF PF CF г г г г г г Применение: Команда xadd используется для выполнения операции обмена и сложения двух операндов: mov al,08h mov bl,01h xadd al,bl ;al=09h, bl=08h См. также: уроки 7, 8 и команды add, xchg XCHG (eXCHanGe) Обмен xchg операнд 1,операнд 2 Назначение: обмен двух значений между регистрами или между регистрами и памятью. Синтаксис: Рис. П2.63. Синтаксическое описание команды xchg Алгоритм работы: Обмен содержимого операнд_1 и операнд_2. Состояние флагов после выполнения команды: выполнение команды не влияет на флаги.
XLAT/XLATB (transLATe Byte from table) 589 Применение: Команду xchg можно использовать для выполнения операции обмена двух опе- рандов с целью изменения порядка следования байт, слов, двойных слов или их временного сохранения в регистре или памяти. Альтернативой является ис- пользование для этой цели стека: ;поменять порядок следования байт в слове chi label byte dw 0f85ch mov al,ch1 xchg ch1+1,al mov ch1,al См. также: урок 7 и команды bswap, cmpxchg, xadd XLAT/XLATB (transLATe Byte from table) Преобразование байта xlat адрес_таблицы_байтов xlatb Назначение: подмена байта в регистре al байтом из последовательности (таблицы) байтов в памяти. Синтаксис: xlat m8 xlatb Алгоритм работы: О Вычислить адрес, равный ds:bx+(al); О выполнить замену байта в регистре al байтом из памяти по вычисленному адресу. Несмотря на наличие операнда адрес_таблицы_байтов в команде xlat, адрес пос- ледовательности байтов, из которой будет осуществляться выборка байта для подмены в регистре al, должен быть предварительно загружен в пару ds: bx(ebx). Команда xlat допускает замену сегмента. Состояние флагов после выполнения команды: выполнение команды не влияет на флаги.
590 Приложение 2. Описание системы команд микропроцессоров Intel Применение: Команду xlat можно использовать для выполнения различного рода перекодиро- вок символов. Для формирования адреса таблицы в регистрах bx(ebx) можно использовать команду lea или оператор ассемблера offset в команде mov: table db "abcdef" int db 0 ;значение индекса mov al,3 lea bx,table xlat ;(al)=’c' См. также: урок 7 и команду lea XOR (logical exclusive OR) Логическое исключающее ИЛИ xor приемник,источник Назначение: операция логического исключающего ИЛИ над двумя операндами размернос- тью байт, слово или двойное слово. Синтаксис: Рис. П2.64. Синтаксическое описание команды хог Алгоритм работы: О Выполнить операцию логического исключающего ИЛИ над операндами: бит результата равен 1, если значения соответствующих битов операндов различны, в остальных случаях бит результата равен 0;
XOR (logical exclusive OR) 591 О записать результат сложения в приемник; О установить флаги. Состояние флагов после выполнения команды: и 07 06 04 02 00 OF SF ZF AF PF CF 0 г г ? г 0 Применение: Команда хог используется для выполнения операции логического исключаю- щего ИЛИ двух операндов. Результат операции помещается в первый операнд. Эту операцию удобно использовать для инвертирования или сравнения опре- деленных битов операндов: ;изменить значение бита О регистра al на обратное xor al,01h См. также: урок 9 и команды and, or, not
ПРИЛОЖЕНИЕ Таблицы кодов символов
В этом приложении приведены фрагменты таблицы ASCII-кодов, которые включают ее младшую (постоянную) половину и старшую (изменяемую). Так что при разборе примеров программ из данной книги вам не придется искать информацию о кодировке символов в других источниках. Таблица П3.1. Таблица кодов ASCII (коды 32-127) Десятеричный код Шестнадцатеричный код Символ 32 20 Пробел 33 21 ! 34 22 < 35 23 # 36 24 $ 37 25 % 38 26 & 39 27 « 40 28 ( 41 29 ) 42 2а * 43 2Ь + 44 2с • 45 2d - 46 2е 47 2f / 48 30 0 49 31 1 50 32 2 51 33 3 52 34 4 53 35 5 54 36 6 55 37 7 56 38 8 57 39 9 58 За • 59 ЗЬ 60 Зс < продолжение&
594 Приложение 3. Таблицы кодов символов Таблица П3.1. (Продолжение) Десятеричный код Шестнадцатеричный код Символ 61 3d = 62 Зе > 63 3f ? 64 40 @ 65 41 А 66 42 В 67 43 С 68 44 D 69 45 Е 70 46 F 71 47 G 72 48 Н 73 49 I 74 4а J 75 4b К 76 4с L 77 4d М 78 4е N 79 4f О 80 50 Р 81 51 Q 82 52 R 83 53 S 84 54 т 85 55 и 86 56 V 87 57 W 88 58 X 89 59 У 90 5а Z 91 5Ь [ 92 5с \ 93 5d ] 94 5е А 95 5f Подчеркивание 96 60 «
Символы ASCII 595 Десятеричный код Шестнадцатеричный код Символ 97 61 а 98 62 b 99 63 с 100 64 d 101 65 е 102 66 f 103 67 g 104 68 h 105 69 i 106 6а j 107 6b k 108 6с 1 109 6d m 110 бе n 111 6f 0 112 70 P ИЗ 71 q 114 72 r 115 73 s 116 74 t 117 75 u 118 76 V 119 77 w 120 78 X 121 79 У 122 7а z 123 7b { 124 7с 1 125 7d } 126 7е 127 7f Забой Как мы уже отметили, расположение символов в младшей половине кодовой таблицы неизменно — это стандарт. Что же касается старшей половины, то вначале она в основном использовалась для размещения различных значков и символов псевдографики (из которых, например, построены панели оболочки Norton Commander). Как только компьютеры на базе микропроцессоров Intel стали использоваться за пределами англоязычных стран, встала проблема визу-
596 Приложение 3. Таблицы кодов символов ализации символов неанглоязычных алфавитов. Стали искать различные «дыры» в таблице ASCII. Младшую половину таблицы трогать, естественно, было нельзя. Поэтому модификации подвергалась старшая половина. У нас в стране решать эту проблему пытались с помощью нескольких систем коди- ровок. Наиболее живучей оказалась альтернативная кодировка. У нее есть специальное международное название — «кодовая страница 866 MS-DOS». В табл. П3.2 приведен фрагмент этой таблицы с кодами символов русского ал- фавита. Фирма Microsoft при локализации Windows использовала кодировки, отличаю- щиеся от кодировок MS-DOS. В России была использована кодовая страница ANSI 1251. В табл. ПЗ.З приведен фрагмент этой таблицы с кодами символов русского алфавита. Таблица П3.2. Фрагмент таблицы кодов ASCII (866 — MS-DOS) Десятеричный код Шестнадцатеричный код Символ 128 80 А 129 81 Б 130 82 В 131 83 Г 132 84 Д 133 85 Е 134 86 Ж 135 87 3 136 88 И 137 89 Й 138 8А К 139 8В Л 140 8С М 141 8D Н 142 8Е О 143 8F п 144 90 р 145 91 с 146 92 т 147 93 У 148 94 ф 149 95 X 150 96 ц 151 97 ч 152 98 ш
ASCII 866 597 Десятеричный код Шестнадцатеричный код Символ 153 99 Щ 154 9А Ъ 155 9В Ы 156 9С Ь 157 9D Э 158 9Е Ю 159 9F Я 160 АО а 161 А1 б 162 А2 в 163 АЗ г 164 А4 Д 165 А5 е 166 А6 ж 167 А7 3 168 А8 и 169 А9 й 170 АА к 171 АВ л 172 АС м 173 AD н 174 АЕ 0 175 AF п Здесь располагаются символы псевдографики 224 Е0 р 225 Е1 с 226 Е2 т 227 ЕЗ У 228 Е4 Ф 229 Е5 X 230 Е6 ц 231 Е7 ч 232 Е8 ш 233 Е9 щ 234 ЕА ъ 235 ЕВ ы 236 ЕС ь 237 ED э 238 ЕЕ ю 239 EF я
598 Приложение 3. Таблицы кодов символов Таблица ПЗЗ. Фрагмент таблицы кодов ANS11251 — Microsoft Windows Десятеричный код Шестнадцатеричный код Символ 192 СО A 193 С1 Б 194 С2 В 195 СЗ Г 196 С4 Д 197 С5 E 198 С6 Ж 199 С7 3 200 С8 И 201 С9 Й 202 СА К 203 СВ Л 204 сс м 205 CD н 206 СЕ о 207 CF п 208 D0 р 209 Dl с 210 D2 т 211 D3 У 212 D4 ф 213 D5 X 214 D6 ц 215 D7 ч 216 D8 ш 217 D9 щ 218 DA ъ 219 DB ы 220 DC ь 221 DD э 222 DE ю 223 DF я 224 E0 а 225 El б 226 E2 в 227 E3 г
ANSI 1251 599 Десятеричный код Шестнадцатеричный код Символ 228 Е4 д 229 Е5 е 230 Е6 ж 231 Е7 3 232 Е8 и 233 Е9 й 234 ЕА к 235 ЕВ л 236 ЕС м 237 ED н 238 ЕЕ 0 239 EF п 240 F0 р 241 F1 с 242 F2 т 243 F3 У 244 F4 ф 245 F5 X 246 F6 ц 247 F7 ч 248 F8 ш 249 F9 щ 250 FA ъ 251 FB ы 252 FC ь 253 FD э 254 FE ю 255 FF я Конечно же, этими вариантами кодовых таблиц проблема кодировок (есть, на- пример, еще КОИ-8, кодировка для UNIX) отнюдь не исчерпывается. И вы всегда должны быть готовы к тому, что вам придется столкнуться с какими-то отклонениями от этих соглашений. Особенно остро проблема систем кодиро- вок стоит при работе с Интернетом. Но это, как говорится, «уже совсем другая история...».
ПРИЛОЖЕНИЕ Функции прерываний 10h (BIOS) и 21h (DOS)
В этом приложении приведено описание некоторых используемых в книге функций прерываний 10h и 21h. Вспомним основную последовательность дей- ствий при использовании этих функций: 1. Поместить номер функции в регистр ah. 2. Поместить передаваемые функции параметры в определенные регистры (они приведены при описании каждой функции). 3. Вызвать прерывание 10h (BIOS) или 21h (DOS) командами int 10h или int 21h. 4. Извлечь результаты работы функций из определенных регистров. Какие именно регистры и что они содержат после возврата управления из функ- ции программе пользователя, указывается при описании каждой функции. Прерывание BIOS 10h предназначено для управления монитором компьютера. Это управление осуществляется с помощью набора программ (функций). Ка- кая именно функция должна быть вызвана, указывается числом в регистре ah. В табл. П4.1 приведено описание доступа к некоторым функциям прерывания 10h BIOS, которые были использованы в данной книге. Таблица П4.1. Некоторые функции BIOS управления видеосистемой (int 10h) Назначение Номер функции Вход Выход Установить положение курсора ah=02h Страница (0, 1, ...) бЬ=строка с11=колонка Определить положение курсора ah=03h ЬЬ=экранная страница (0, 1, ...) бЬ=строка dl=кoлoнкa сЬ=первая строка курсора с1=последняя строка курсора ЬЬ=номер страницы продолжение &
602 Приложение 4. Функции прерываний 1 Oh (BIOS) и 21 h (DOS) Таблица П4.1. (продолжение) Назначение Номер функции Вход Выход Прокрутить окно вверх ah=06h а1=количество строк для прокрутки (0 — очистить экран) Ы1=новый атрибут для символов новой строки границы окна: сЬ=верхняя строка с1=левая колонка <1Ь=нижняя строка с11=правая колонка Прокрутить окно вниз ah=07h а1=количество строк для прокрутки (0 — очистить экран) bh=новый атрибут для символов новой строки границы окна: сЬ=верхняя строка с1=левая колонка бЬ=нижняя строка сП=правая колонка Прочитать ah=08h Ы1=экранная страница аЬ=атрибут символа символ и его атрибут в текущем месте нахождения курсора (0, 1, ...) а1=знак Вставить символ с его атрибутом в текущее место нахождения курсора ah=09h ЬЬ=экранная страница (0, 1, ...) аЬ=атрибут символа а1=А8СП-код символа сх=число повторений символа Вставить символ в текущее место нахождения курсора ah=Oah Ы1=экранная страница (0, 1, ...) а1=А8СП-код символа Ы=цвет в графическом режиме сх=число повторений символа Записать символ в режиме телетайпа ah=Oeh а1=А8СП~код символа Ы=цвет в графическом режиме Получить параметры текущего видеорежима ah=Ofh а1ввидеорежим аЬ=число колонок на экране Ы1вэкранная страница (0, 1, ...)
603 Прерывание DOS 21h предназначено для предоставления программисту раз- личных услуг со стороны операционной системы. Этими услугами является набор функций. Какая именно функция должна быть вызвана, указывается числом в регистре ah. В табл. П4.2 приведено описание некоторых функций прерывания 21h DOS, которые были использованы в данной книге. Таблица П4.2. Некоторые функции DOS (int 21h) Назначение Номер функции Вход Выход Ввод символа с ожиданием и эхосопровождением ah=01h а1= ASCII-код символа Вывод символа ah=02h dl= ASCII-код символа Вывод символа на принтер ah=05h dl= ASCII-код символа Ввод символа с ожиданием и без эхосопровождения ah=07h ah=08h а1= ASCII-код символа (функция 08h при вводе проверяет, не нажато ли CTRL-BREAK) Вывод строки на экран ah=09h ds:dx=aflpec строки с символом «$» на конце Ввод строки с клавиатуры ah=Oah ds:dx=aflpec буфера с форматом: 1 байт — размер буфера для ввода (формирует пользователь); 2 байт — число фактически введенных символов (заполняет система по окончанию ввода — нажатию клавиши Enter (Odh)). Символ Odh не учитывается во втором байте буфера; 3 байт и далее — введенная строка с символом Odh на конце Введенная строка в буфере Проверка состояния буфера клавиатуры ah=Obh а1=0 — буфер пуст al=Offh — в буфере есть символы
Директивы приложение управления листингом
Директивы управления листингом делятся на следующие группы: О общие директивы управления листингом; О директивы вывода в листинг включаемых файлов; О директивы вывода блоков условного ассемблирования; О директивы вывода в листинг макрокоманд; О директивы вывода в листинг информации о перекрестных ссылках; О директивы изменения формата листинга. При рассмотрении директив обращайте внимание на то, что их формат разли- чается для режимов работы транслятора MASM и IDEAL: директивам режима MASM предшествует точка, директивам режима IDEAL предшествует знак «%». В остальном синтаксис директив простой. Большинство директив не име- ют операндов. Директивы, как и команды, задаются в отдельной строке в том месте программы, с которого должно начаться их действие. Общие директивы управления листингом Директивы этой группы предназначены для управления видом файла листин- га. Все директивы являются парными — это означает, что если одна директива что-то разрешает, то другая, наоборот, запрещает. Рассмотрим назначение этих пар директив. o/oLIST и %NOLIST (.LIST и .XLIST) Директивы . LIST или %LIST определяют необходимость вывода в файл листин- га всех строк исходного кода. Эти директивы подразумеваются по умолчанию. Для запрета вывода в файл листинга всех строк исходного кода необходимо использовать директивы . XLIST или %NOLIST. В тексте программы их можно применять произвольное количество раз, при этом очередная директива отме- няет действие предыдущей. %CTLS и %NOCTLS Если предыдущие директивы влияют на полноту представления исходного кода в целом, то директивы %CTLS и %NOCTLS управляют выводом в файл листин- га самих директив управления листингом.
606 Приложение 5. Директивы управления листингом %SYMS и %NOSYMS Эти директивы определяют, включать (%SYMS) или не включать (%NOSYMS) в файл листинга таблицу идентификаторов. Директивы вывода текста включаемых файлов %INCL и %NOINCL Эти директивы позволяют регулировать включение в файл листинга текста включаемых файлов (по директиве INCLUDE). По умолчанию включаемые фай- лы записываются в файл листинга. Директива %NOINCL запрещает вывод в файл листинга всех последующих включаемых файлов, пока вывод снова не будет разрешен директивой %INCL. Директивы вывода блоков условного ассемблирования %CONDS и %NOCONDS (-LFCOND и .SFCONDS) Для исследования исходного текста программы, содержащего директивы ус- ловной компиляции, удобно использовать директивы, регулирующие включе- ние блоков условной компиляции в листинг программы. Директива %CONDS (. LFCOND) заставляет TASM выводить в файл листинга все операторы условных блоков. При этом в файл листинга выводятся все блоки, в том числе с условием false. Директива %NOCONDS (.SECONDS) запрещает вывод в файл листинга блоков условного ассемблирования с условием false. Директива .TFCOND переключает режимы вывода %CONDS (.LFCOND) и %NOCONDS (. SFCONDS). Эту директиву можно использовать как отдельно, так и совместно с директивами . LFCOND и .SFCONDS. Первая директива .TFCOND, которую обнаружи- вает TASM, разрешает вывод в листинг всех блоков условного ассемблирова- ния. Следующая директива .TFCOND будет запрещать вывод этих блоков. С ди- рективой . TFCOND можно использовать параметр командной строки транслятора TASM /X: согласно ему блоки условного ассемблирования будут сначала выво- диться в листинг, но первая же директива .TFCOND запретит их вывод.
Директивы изменения формата листинга «07 Директивы вывода макрорасширений «/«MACS (.LALL) и %NOMACS (.SALL) Аналогично директивам вывода блоков условной компиляции при отладке про- граммы удобно регулировать полноту информации о применяемых макрокоман- дах. По умолчанию транслятор включает макрорасширения в файл листинга. Можно запретить вывод макрорасширений в файл листинга, что удобно на неко- торых стадиях отладки. Директива %MACS (. LALL) разрешает вывод в листинг всех макрорасширений. Ди- ректива %NOMACS (.SALL) запрещает вывод всех операторов макрорасширения в файл листинга. В режиме MASM можно использовать директиву . XALL, позволяющую выводить в листинг только те макрорасширения, которые генерируют код или данные. Директивы вывода листинга перекрестных ссылок Приведенные выше директивы %SYMS и %NOSYMS регулировали вывод в листинг таблицы идентификаторов, в которой приводится информация о метках, груп- пах и сегментах, но там не сообщается, где они определены и где используются. Информация в таблице перекрестных ссылок исправляет этот недостаток. Она облегчает поиск меток и полезна для отладки программы. В приложении 1 приведена опция командной строки TASM /с для получения таблицы перекрестных ссылок. Но действие этой опции распространяется на весь исходный файл, что может быть не совсем удобным. Поэтому TASM до- полнительно предоставляет директивы для создания таблиц перекрестных ссы- лок только для отдельных частей исходного кода. Директивы %CREF (.CREF) и %NOCREF (.XCREF) соответственно разрешают и запрещают сбор информации о перекрестных ссылках, начиная с точки, где они были определены. При этом директивы %NOCREF (.XCREF) позволяют выборочно запрещать сбор информации о перекрестных ссылках для определенных идентификаторов в программе. Эти директивы имеют следующий синтаксис: %NOCREF (.XCREF) [идентификатор, ...] Если в директиве %NOCREF (.XCREF) не указать идентификатор, то вывод пере- крестных ссылок запрещается полностью, если указать некоторые идентифика- торы, то информация не будет собираться только для этих идентификаторов. Директивы изменения формата листинга Директивы этой группы позволяют управлять форматом файла листинга.
608 Приложение 5. Директивы управления листингом .PAGE Директива . PAGE задает высоту и ширину страницы файла листинга и начинает его новую страницу. Она имеет следующий синтаксис: PAGE [число__строк} [,число_столбцов} PAGE + Здесь: О число-Строк задает число строк, выводимых на странице листинга; О число__столбцов находится в диапазоне 5Э...255 и задает число столбцов на странице. Если опустить один из этих параметров, то текущая установка данного пара- метра останется без изменений. Для изменения только числа столбцов необхо- димо указать перед этим параметром запятую. С помощью директивы PAGE можно разбивать листинг на разделы, в пределах которых нумерация начинается с нуля. Так, при указании после директивы PAGE символа «+» начинается новая страница, номер раздела увеличивается, а номер страницы снова устанавливается в 1. Если использовать директиву PAGE без аргументов, то листинг возобновляется с новой страницы без изменения номера раздела. %PAGESIZE (.PAGESIZE) Директива %PAGESIZE работает так же, как и директива . PAGE, но в отличие от последней она не начинает новую страницу, а лишь определяет ее параметры: %РAGESIZE [число_строк][,число_столбцов] %NEWPAGE Директива %NEWPAGE работает аналогично директиве .PAGE без аргументов. Строки исходного текста после директивы %NEWPAGE будут начинаться с новой страницы. %BIN Директива %BIN устанавливает длину поля объектного кода в файле листинга. Ее синтаксис: %BIN размер Здесь: размер — некоторая константа. По умолчанию поле объектного кода занимает в файле листинга до 20 позиций.
Директивы изменения формата листинга 609 %DEPTH Директива %DEPTH устанавливает размер поля глубины в файле листинга. Ее синтаксис: %DEPTH размер Здесь: размер — задает количество столбцов в поле глубины листинга. Напомню, что данное поле показывает уровень вложенности включаемых файлов (INCLUDE) и макрорасширений. Если указать в качестве размера значение О, то поле уровня вложенности не выводится. По умолчанию это поле имеет зна- чение 1. % LINENUM Директива %LINENUM позволяет задать размер поля занимаемого номерами строк в файле листинга: %LINENUM размер По умолчанию под номер строки отводится четыре столбца. %TRUNC и %NOTRUNC Директивы %TRUNC и %NOTRUNC предназначены для усечения длинных полей лис- тинга. Их синтаксис: %TRUNC и %NOTRUNC Если некоторая строка исходного кода получается слишком длинной, то она автоматически усекается. Если возникает необходимость увидеть всю генери- руемую строку, то можно использовать директиву %NOTRUNC, действие которой будет заключаться в том, что слишком длинная строка будет переноситься на следующую строку. Для включения режима усечения нужно использовать ди- рективу %TRUNC. Такие переключения можно осуществлять неограниченное ко- личество раз. %PCNT Директива %PCNT задает размер поля «сегмент:смещение» в файле листинга. Ее синтаксис: %PCNT размер
610 Приложение 5. Директивы управления листингом Здесь: размер — число столбцов, которое необходимо отвести для смещения в теку- щем ассемблируемом сегменте. По умолчанию TASM устанавливает размер, равный 4 для обычных 16-битовых сегментов (атрибут размера адреса use16) и 8 — для 32-битовых сегментов (атрибут размера адреса use32). Директива %PCNT позволяет переопределить эти используемые по умолчанию значения. %TITLE Директива %Т1Т1_Е задает заголовок файла листинга. Ее синтаксис: %Т1Т1_Е «текст» Здесь: текст — строка, которая будет выводиться в верхней части каждой страницы после имени исходного файла и перед заголовком, заданным по директиве %SUBTTL. В отличие от других директив, %Т1Т1_Е можно использовать в програм- ме только один раз. %SUBTTL Директива %SUBTTL задает подзаголовок файла листинга. Ее синтаксис: %SUBTTL «текст» Подзаголовок представляет собой текст, который выводится в верхней части каждой страницы после имени исходного файла и после заголовка, заданного директивой %Т1Т1_Е. Директиву %SUBTTL можно указывать в программе столько раз, сколько необходимо. Каждая директива изменяет подзаголовок, который будет выводиться на следующей странице листинга. %TABSIZE Директива %TABSIZE задает позицию табуляции в файле листинга. Ее синтак- сис: %TABSIZE размер Здесь: размер — число столбцов между двумя позициями табуляции в файле листинга (по умолчанию 8 столбцов).
Директивы изменения формата листинга 611 %ТЕХТ Директива %ТЕХТ используется для задания длины поля исходного текста в файле листинга. Ее синтаксис: %ТЕХТ размер Здесь: размер - число столбцов, используемых для вывода исходных строк. Если раз- мер строки превышает длину этого поля, то строка будет либо усекаться, либо переноситься на следующую строку, в зависимости от директив 96TRUNC или %NOTRUNC.
ПРИЛОЖЕНИЕ Значения полей инициализации
В этом приложении для каждой директивы определения данных, обсуждав- шихся на уроке 5, перечислены возможные типы и диапазоны значений, кото- рые можно описывать или задавать с ее помощью. При работе с этими дирек- тивами и с дампами памяти не забывайте о принципе «младший байт по младшему адресу», в соответствии с которым при сохранении данных в памяти младшая часть значения всегда записывается перед старшей частью. Все дирек- тивы позволяют задавать строковые значения, но в памяти они могут выгля- деть не так, как вы их описали в директиве. Поэтому старайтесь использовать для определения строк директиву db. Задаваемые таким образом строки долж- ны заключаться в кавычки. Эти кавычки могут быть одинарными (’ ’) или двойными (“ ”). Если задать внутри строки два таких ограничителя подряд, то это будет означать, что данная кавычка должна быть частью строки. DB (Define Byte) — определить байт Директивой db можно задавать следующие значения: О выражение или константу, принимающую значение из диапазона: для чисел со знаком —128...+127; для чисел без знака 0...255; О 8-битовое относительное выражение, использующее операции HIGH и LOW; О символьную строку из одного или более символов. Строка заключается в кавычки. В этом случае определяется столько байт, сколько символов в строке. DW (Define Word) — определить слово Директивой dw можно задавать следующие значения: О выражение или константу, принимающую значение из диапазона: для чисел со знаком —32 768...32 767; для чисел без знака 0...65 535;
614 Приложение 6. Значения полей инициализации О выражение, занимающее 16 или менее бит, в качестве которого может выс- тупать смещение в 16-битовом сегменте или адрес сегмента; О 1- или 2-байтовая строка, заключенная в кавычки. DD (Define Double word) — определить двойное слово Директивой dd можно задавать следующие значения: О выражение или константу, принимающую значение из диапазона: О для i8086: для чисел со знаком -32 768...+32 767; для чисел без знака 0...65 535; О для i386 и выше: для чисел со знаком -2 147 483 648...+2 147 483 647; для чисел без знака 0...4 294 967 295; О относительное или адресное выражение, состоящее из 16-битового адреса сег- мента и 16-битового смещения; О строку длиной до 4 символов, заключенную в кавычки. DQ (Define Quarter word) — определить учетверенное слово Директивой dq можно задавать следующие значения: О выражение или константу, принимающую значение из диапазона: О для МП i8086: для чисел со знаком -32 768...+32 767; для чисел без знака 0...65 535; О для МП i386 и выше: для чисел со знаком -2 147 483 648... +2 147 483 647; для чисел без знака 0...4 294 967 295; О относительное или адресное выражение, состоящее из 32 или менее бит (для i80386) или 16 или менее бит (для младших моделей микропроцессо- ров Intel); О константу со знаком из диапазона -263...263 - 1;
DT (Define Ten Bytes) — определить 10 байт 615 О константу без знака из диапазона 0...264 - 1; О строку длиной до 8 байт, заключенную в кавычки. DF (Define Far word) — определить указатель дальнего слова DP (Define Pointer) /1 определить указатель 48 бит Директивами df и dp можно задавать следующие значения: О выражение или константу, принимающую значение из диапазона: О для 18086: для чисел со знаком -32 768...+32 767; для чисел без знака 0...65 535; О для 1386 и выше: для чисел со знаком -2 147 483 648...+2 147 483 647; для чисел без знака 0...4 294 967 295; О относительное или адресное выражение, состоящее из 32 или менее бит (для i80386) или 16 или менее бит (для младших моделей микропроцессо- ров Intel); О адресное выражение, состоящее из 16-битового сегмента и 32-битового сме- щения; О константу со знаком из диапазона -247...247 - 1; О константу без знака из диапазона 0...248 - 1; О строку длиной до 6 байт, заключенную в кавычки. DT (Define Ten Bytes) — определить 10 байт Директивой dt можно задавать следующие значения: О выражение или константу, принимающую значение из диапазона: О для 18086: для чисел со знаком -32 768...+32 767; для чисел без знака 0...65 535; О для 1386 и выше: для чисел со знаком -2 147 483 648... +2 147 483 647; для чисел без знака 0...4 294 967 295;
616 Приложение 6. Значения полей инициализации О относительное или адресное выражение, состоящее из 32 или менее бит (для i80386) или 16 или менее бит (для младших моделей); О адресное выражение, состоящее из 16-битового сегмента и 32-битового сме- щения; О константу со знаком из диапазона -279...279 - 1; О константу без знака из диапазона 0...280 - 1; О строку длиной до 10 байт, заключенную в кавычки; О упакованную десятичную константу в диапазоне 0...99 999 999 999 999 999 999.
а Библиотека приложение арифметических подпрограмм
В данном приложении приведены тексты подпрограмм, реализующих основ- ные арифметические операции с двоичными (BIN) и двоично-десятичными (BCD) операндами. Авторы надеются, что они будут использованы читателем как для более глубокого освоения материала книги, так и в практической рабо- те. Программы достаточно документированы и, с этой точки зрения, самодос- таточны. Подпрограммы для двоичных чисел Данные подпрограммы выполняют сложение, вычитание, умножение и деление над двоичными операндами. Обращение к подпрограммам производится с по- мощью макрокоманд. Порядок передачи параметров всем макрокомандам од- нотипен. Принцип тот же, что и для всех команд ассемблера с двумя операнда- ми. Принцип заключается в том, что результат помещается по месту первого операнда. Но, в отличие от команд ассемблера, оба операнда в приводимых ниже макрокомандах располагаются в памяти. Так как эти макрокоманды мо- гут работать с операндами любой разрядности, то при их вызове необходимо указывать и третий параметр — длину операндов, которая должна быть одина- ковой для обоих операндов. Init macro mem1,mem2,len ;макрос для инициализации адресных регистров mov di,offset mem1 mov si,offset mem2 mov ex,len endm AddBIN macro mem1,mem2,len ;сложение BIN-чисел ;результат помещается в первый операнд Init mem1,mem2,len call AddBINp endm SubBIN macro mem1,mem2,len ;вычитание BIN-чисел ;результат помещается в первый операнд Init mem1,mem2,len
Подпрограммы для двоичных чисел 619 call SubBINp endm MulBIN macro mem1,mem2,len умножение BIN-чисел ;результат: ;младшая часть помещается в первый операнд, ;старшая часть - во второй операнд Init mem1,mem2,len call MulBINp endm DivBIN macro mem1,mem2,len ;деление BIN-чисел ;результат: ;частное помещается в первый операнд, •.остаток - во второй операнд mov di,len-1+offset mem1 mov si,len-1+offset mem2 mov ex,len call DivBINp endm OutBIN macro mem,len ;вывод BIN-чисел на экран в текущую позицию mov si,len-1+offset mem mov cx.len call OutBINp endm data segment ;для операций умножения и деления необходим буфер •.размер буфера не менее 3*SIZE, ;где SIZE - размер чисел buffer db 512 dup(?) bufsize = $-buffer-1 a dd 315 b dd 12 c dd 24 data ends stk segment stack ’’stack" db lOOh dup (“?”) stk ends code segment assume cs:CODE,ds:DATA,es:DATA AddBINp proc ;процедура сложения bin-чисел push ax ;сохраним изменяемые регистры продолжение &
620 Приложение 7. Библиотека арифметических подпрограмм push di push si push ex cld ;начинаем с младших разрядов clc ;обнулим значение флага переноса add_: lodsb ;возьмем очередную цифру adc al,[di] ;сложение с учетом переноса stosb ;сохраним результат loop _add_ pop сх восстановим регистры pop si pop di pop ax ret endp SubBINp proc ;процедура вычитания BIN-чисел push ax ;сохраним изменяемые регистры push di push si push ex cld ;начинаем с младших разрядов clc ;обнулим значение переноса _sub_: lodsb ;возьмем очередную цифру sbb [di],al ;вычитание с учетом переноса mov al,[di] stosb ; сохраним результат loop _sub_ pop сх восстановим регистры pop si pop di pop ах ret endp MulBINp ргос ;Процедура умножения BIN-чисел push ax •.сохраним изменяемые регистры push bx push di push si push ex cld ;начинаем с младших разрядов mov bx,offset buffer
Подпрограммы для двоичных чисел 621 mov dh,cl ;запомним исходное состояние счетчика push bx ;заполним буфер результата нулями shl сх,1 необходим размер 2*SIZE xor al,al ;символ-заполнитель = О _null_: mov LbxJ.al inc bx loop _null_ mov cl.dh pop bx ; умножение будем проводить ’’столбиком" ;цикл по всем цифрам первого операнда _mul_o_: xor dl.dl ;обнулим значение переноса push сх push bx ;сохраним некоторые регистры push si mov cl.dh восстановим исходное ;значение счетчика ;цикл по всем цифрам второго операнда _mul_i_: lodsb ;возьмем очередную цифру mul byte ptr [di] ;умножим add al.dl ;учтем перенос adc ah,О add al,Ebx] ;сложим с результатом предыдущего умножения adc ah,О mov dl,ah ;запомним значение переноса xor ah,ah mov [bx],al ;сохраним результат inc bx loop _mul_i_ mov EbxJ.dl pop si восстановим регистры pop bx inc bx inc di ;перейдем к следующей цифре ;второго операнда pop сх loop _mul_o_ mov cl.dh восстановим исходное ;значение счетчика продолжение &
622 Приложение 7. Библиотека арифметических подпрограмм sub Ьх,сх ; сместим Ьх на младшую ;часть результата sub di.cx ;занесем результат (мл. часть) в первый операнд _move_l_: mov al,[bx] inc bx stosb loop _move_l_ mov cl,dh mov di,si ;занесем результат (старшая часть) во второй операнд _move_h_: mov al,[bx] inc bx stosb loop _move_h_ pop сх ;восстановим регистры pop si pop di pop bx pop ax ret endp SuMnvBINp proc ;вспомогательная процедура для операции деления ;производит вызов процедуры вычитания ;без начальной инициализации push si push di sub si, ex inc si sub di.cx inc di call SubBINp pop di pop si ret endp CmpBINp proc ;процедура сравнения BIN-чисел ;CF=O, если [si]>[di], иначе CF=1 push ах push di push si
Подпрограммы для двоичных чисел 623 push сх std _cmp_: lodsb cmp al,[di] jb _less_ ja .greater- dec di loop _cmp_ _less_: stc jc _cmp_q_ _greater_: clc _cmp_q_: pop ex pop si pop di pop ax ret endp PrepareForDiv proc ;процедура инициализации буфера для операции деления std ;0,[di] -> buffer (первый операнд в буфер) push di push si push di pop si mov di,bufsize+offset buffer xor al,al push ex stosb rep movsb ;0,[si] -> buffer (второй операнд в буфер) pop сх stosb pop si push ex ;для начала найдем первую значащую цифру _find_: lodsb dec сх cmp al,О je _find_ продолжение &
624 Приложение 7. Библиотека арифметических подпрограмм inc si inc ex mov dx, ex rep movsb pop ex push ex ;0,0..0 -> buffer - очистим место для результата хог al,al rep stosb ;переназначение регистров mov di,bufsize+offset buffer pop ex mov si,di inc ex sub si,ex pop bx ret endp DivBINp proc ;процедура деления BIN-чисел push ax ;сохраним изменяемые регистры push bx push di push si push ex push di call PrepareForDiv подготовим буфер xor ax,ax ;в al - очередная цифра результата ;в ah - количество цифр в результате call CmpBINp jne _next_1_ _div_: call CmpBINp jne _next_ inc al call SublnvBINp jmp _div_ _next_: mov [bx],al ;сохраним очередную цифру dec bx ;уменьшим порядок делимого _next_1_: dec di dec ex xor al, al inc ah
Подпрограммы для двоичных чисел 625 стр cx,dx ; сравним порядки делимого и делителя jne _div_ dec ah pop di pop сх push сх •«пересылаем результат из буфера в операнды mov si,di sub di,cx push ex mov cl, ah sub si, ex inc si inc di cld rep movsb pop ex sub cl, ah xor al, al rep stosb pop ex pop si push si push ex mov di, si mov si,bufsize+offset buffer dec si std rep movsb pop ex ;восстановим регис pop si pop di pop bx pop ax ret endp OutBINp proc ;процедура вывода BIN-чисел на экран push ax push bx push ex push dx push si mov ah,06h std продолжение &
626 Приложение 7. Библиотека арифметических подпрограмм _out_: lodsb mov bl,al mov dl.bl shr dl,4 or dl,30h cmp dl,39h jle _digit_1_ add dl,7 _digit_1_: int 21h mov dl.bl and dl.Ofh or dl,30h cmp dl,39h jle _digit_2_ add dl,7 _digit_2_: int 21h loop _out_ mov dl.Odh int 21h mov dl.Oah int 21h pop si pop dx pop ex pop bx pop ax ret endp main proc ; проверим, как работает пакет mov ах,DATA mov ds,ax mov es.ax OutBIN a,4 OutBIN b,4 AddBIN a, b, 4 OutBIN a,4 OutBIN b,4 SubBIN a, b, 4 OutBIN a,4 OutBIN b,4 MulBIN a, b, 4
Подпрограммы для двоим но-десятичных (BCD) чисел 627 OutBIN а, 4 OutBIN Ь, 4 OutBINс,4 DivBIN а, с, 4 OutBINа, 4 OutBIN с, 4 mov ax,4c00h int 21h main endp code ends end main Подпрограммы для двоично-десятичных (BCD) чисел Данные подпрограммы выполняют сложение, вычитание, умножение и деление над двоично-десятичными операндами произвольной размерности. Обращение к подпрограммам производится с помощью макрокоманд. Порядок передачи параметров всем макрокомандам однотипен. Результат помещается по месту первого операнда. Но, в отличие от команд ассемблера, оба операнда в приво- димых ниже макрокомандах располагаются в памяти. Так как эти макрокоман- ды могут работать с операндами любой разрядности, то при их вызове необхо- димо указывать и третий параметр — длину операндов, которая должна быть одинаковой для обоих операндов. Init macro mem1,mem2,len ;макрос для инициализации адресных регистров mov di,len-1+offset mem1 mov si,len-1+offset mem2 mov ex,len endm AddBCD macro mem1,mem2,len ;сложение BCD-чисел результат помещается в первый операнд Init mem1,mem2,len call AddBCDp endm SubBCD MACRO mem1,mem2,len ;вычитание BCD-чисел ;результат помещается в первый операнд Init mem1,mem2,len call SubBCDp endm MulBCD macro mem1,mem2,len продолжение &
628 Приложение 7. Библиотека арифметических подпрограмм ;умножение BCD-чисел ;результат: ;младшая часть помещается в первый операнд, ;старшая часть - во второй операнд Init mem1,mem2,len call MulBCDp endm DivBCD macro mem1,mem2,len ;деление BCD-чисел ;результат: ;частное помещается в первый операнд, ;остаток - во второй операнд mov di,offset mem1 mov si,offset mem2 mov ex,len call DivBCDp endm OutBCD macro mem,len ;вывод BCD-чисел на экран в текущую позицию mov si,offset mem mov ex,len call OutBCDp endm data segment ;для операций умножения и деления необходим буфер ;размер буфера не менее 3*SIZE, ;где SIZE - размер чисел buffer db 512 dup(?) a db 0,0,4,0,1,4,5,2,2,2 b db 0,0,0,0,0,3,8,9,7,8 с db 2,8,0,1,0,0,1,9,8,3 d db 9,9,3,3,3,3,3,3,3,3 data ends code segment assume cs:code,ds:data,es:data AddBCDp proc ;процедура сложения BCD-чисел push ax ;сохраним изменяемые регистры push di push si push ex std ;начинаем с младших разрядов clc _add_: ;обнулим значение переноса
Подпрограммы для двоично-десятичных (BCD) чисел 629 lodsb ; возьмем очередную цифру adc al,[di] ;сложение с учетом переноса ааа ;выровняем в формат BCD-чисел stosb ;сохраним результат loop .add- pop сх ;восстановим регистры pop si pop di pop ax ret endp SubBCDp proc ;процедура вычитания BCD-чисел push ax ;сохраним изменяемые регистры push di push si push ex std ;начинаем с младших разрядов clc ;обнулим значение переноса -Sub-: lodsb ;возьмем очередную цифру sbb [di],al ;вычитание с учетом переноса mov al,[di] aas ; выровняем в формат BCD-чисел stosb ;сохраним результат loop -Sub. pop сх ;восстановим регистры pop si pop di pop ax ret endp MulBCDp proc ;процедура умножения BCD-чисел push ax ;сохраним изменяемые регистры push bx push di push si push ex std ;начинаем с младших разрядов mov bx,offset buffer mov dh,cl ;запомним исходное состояние счетчика push bx ;заполним буфер результата нулями продолжение &
630 Приложение 7. Библиотека арифметических подпрограмм shl сх, 1 ; необходим размер 2*SIZE xor al,al ;символ-заполнитель = О _null_: mov [bx],al inc bx loop _null_ mov cl.dh pop bx ;умножение будем проводить «столбиком» ;цикл по всем цифрам первого операнда _mul_o_: хог dl.dl ;обнулим значение переноса push сх push bx ;сохраним некоторые регистры push si mov cl.dh восстановим исходное ;значение счетчика ;цикл по всем цифрам второго операнда _mul_i_: lodsb ;возьмем очередную цифру mul byte ptr [di] ;умножим aam ;коррекция результата add al.dl ;учтем перенос ааа add al,[bx] ;сложим с результатом •.предыдущего умножения ааа mov dl,ah ;запомним значение переноса хог ah,ah mov [bx],al ;сохраним результат inc bx loop _mul_i_ mov [bx],dl pop si ;восстановим регистры pop bx inc bx dec di ;перейдем к следующей ;цифре второго операнда pop сх loop _mul_o_ mov cl.dh восстановим исходное ;значение счетчика sub bx.cx ;сместим Ьх на младшую ;часть результата add di.cx
Подпрограммы для двоично-десятичных (BCD) чисел 631 ;занесем результат (мл. часть) в первый операнд _move_l_: mov al,[bx] inc bx stosb loop _move_l_ mov cl.dh mov di,si •.занесем результат (ст. часть) во второй операнд _move_h_: mov al,[bx] inc bx stosb loop _move_h_ pop сх восстановим регистры pop si pop di pop bx pop ax ret endp SublnvBCDp proc вспомогательная процедура для операции деления ;производит вызов процедуры вычитания ;без начальной инициализации push si push di add si, ex dec si add di, ex dec di call SubBCDp pop di pop si ret ENDP CmpBCDp proc процедура сравнения BCD-чисел ;CF=O, если [si]>[di], иначе CF=1 push ax push di push si push ex cld cmp_: продолжение &
632 Приложение 7. Библиотека арифметических подпрограмм lodsb cmp al,[di] jl _less_ jg .greater, inc di loop _cmp_ less.: stc jc .cmp.q. .greater.: clc cmp.q.: pop ex pop si pop di pop ax ret endp PrepareForDiv proc процедура инициализации буфера ;для операции деления cld ;0,[di] -> buffer (первый операнд в буфер) push di push si push di pop si mov di,offset buffer xor al,al push ex stosb rep movsb ;0,[si] -> buffer (второй операнд в буфер) pop сх stosb pop si push ex ;для начала найдем первую значащую цифру _find_: lodsb dec сх cmp al,О je .find. dec si inc ex
Подпрограммы для двоично-десятичных (BCD) чисел 633 mov dx,cx rep movsb pop ex push ex ;0,0. .0 -> buffer (очистить место для результата в буфере) хог al,al гер stosb ;переназначение регистров mov di,offset buffer pop ex mov si, di inc ex add si,ex pop bx ret endp DivBCDp proc •.процедура деления BCD-чисел push ax ;сохраним изменяемые регистры push bx push di push si push ex push di call PrepareForDiv ;подготовим буфер хог ax, ax ;в al - очередная цифра результата ;в ah - количество цифр в результате call CmpBCDp jne _next_1_ _div_: call CmpBCDp jne _next_ inc al call SublnvBCDp jmp _div_ _next_: mov [bxj.al ;сохраним очередную цифру inc bx ;уменьшим порядок делимого _next_1_: inc di dec ex xor al,al inc ah cmp cx.dx ;сравним порядки делимого и делителя jne _div_ продолжение &
634 Приложение 7. Библиотека арифметических подпрограмм dec ah pop di pop ex push ex ;пересылаем результат из буфера в операнды mov si,di add di,cx push ex mov cl,ah add si,ex dec si dec di std rep movsb pop ex sub cl,ah xor al, al rep stosb pop ex pop si push si push ex mov di,si mov si,offset buffer inc si cld rep movsb pop сх восстановим регистры pop si pop di pop bx pop ax ret endp OutBCDp proc ;процедура вывода BCD-чисел на экран mov ah,06h cld _out_: lodsb or al,30h mov dl,al int 21h loop _out_ mov dl.Odh
Подпрограммы для двоично-десятичных (BCD) чисел 635 int 21 h mov dl.Oah int 21h ret endp main ргос mov ах,data mov ds,ax mov es,ax OutBCD a, 10 OutBCD b,10 AddBCD a,b,10 OutBCD a, 10 OutBCD b,10 SubBCD a,b,10 OutBCD a, 10 OutBCD b. 10 MulBCD a,b,10 OutBCD a, 10 OutBCD b,10 DivBCD a,b,10 OutBCD a, 10 OutBCD b,10 mov ax,4c00h int 21h main endp code ends end main
ПРИЛОЖЕНИЕ ПРИМеР Раб0ТЫ ----------- со структурой
Данный пример программы можно рассматривать как дополнение к материалу уроков 12 и 13. Основа программы — работа с массивом структур. Интерфейс программы обеспечивается макрокомандами. В программе определяется массив структур и реализуются операции с этим массивом — добавление новой запи- си, поиск и удаление записей. ;pril_8.asm Демонстрация работы со структурами: ;1 - добавление новой записи; ;2 - поиск записи; ;3 - удаление записи. .386 MASM MODEL STACK include use16 small ;модель памяти 256 ;размер стека mac.inc подключение файла с макросами worker nam sex struc информация о сотруднике db 30 dup (” ”) ;фамилия, имя, отчество db " ” ; пол position db 30 dup (" ’’) должность аде db 2 dup (” ") ;возраст standing db 2 dup (” ’’) ;стаж salary db 4 dup (” ") ;оклад в рублях birthdate db 8 dup (” ’’) ;дата рождения worker ends .data N=5 ;массив StrWork ;размерность базы данных структур worker о ;рабочая структура для различных ;промежуточных манипуляций sot г worker N DUP (о) ; массив структур mes1 db db db db 10,13,10,13,’★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★’ 10,13,’*Демонстрация работы со структурами:*' 10,13,’*Режимы работы: *' 10,13,'*1 -добавление; *' Л продолжение &
638 Приложение 8. Пример работы со структурой db 10,13, '*2 - поиск; *' db 10,13,'*3 - удаление; *’ db 10,13,'*0 - выход. *' db 10,13,’*************************************' db 10,13,'Введите выбор : mname db msex db mposition mage db mstanding msalary db mbirthdate 10,13,10,13,'Введите имя (не более 30 символов) - $' 10,13,'Введите пол (""м" или '"'ж'') - $' db 10,13,'Введите должность (не более 30 символов) - $' 10,13,'Введите возраст (не более 2 символов) - $' db 10,13,'Введите стаж работы (не более 2 символов) - $ 10,13,'Введите оклад (не более 4 символов) - $' db 10,13,'Введите дату рождения (дд.мм.гг) - $' findname db 10,13,10,13,'Введите mes2 db 10,13,10,13,'Имя - " mnamel db 30 dup ('' ") db 10,13, 'Пол - " msexl db " " db 10,13,'Должность - " mpositionl db 30 dup (" ") db 10,13,'Возраст - " magel db 2 dup (" '') db 10,13,'Стаж - " mstandingl db 2 dup (" ") имя для поиска - $’ db 10,13, 'Оклад - " msalaryl db 4 dup (" ") db 10,13,'Дата рождения - " mbirthdatel db 8 dup (" mes3 db 10,13,10,13,'Запись успешно удалена! Егг1 db 10,13,10,13,'Нет места в базе данных. Егг2 db 10,13,10,13,'Такой записи нет в базе данных! Повторите зап- рос.', Thanks db 10,13,10,13,'Спасибо за внимание! Успехов в освоении ассембле- ра!', ' $’ Flag db 0 ;флаг для использования в процедуре delete . code assume ds:©data,es:©data main: ;начало программы mov ax,©data mov ds,ax xor ax,ax push ds pop es
639 ;выбор режима работы: до: OutStr mes1 ;вывод строки mes1 на экран GetChar ;ввод и определение режима работы стр al,31h je insert стр al,32h je search стр al, 33h je delete стр al,30h je exit jmp go Insert proc добавление новой записи ;заполняем рабочую структуру OutStr mname GetStr StrWork.nam,30 OutStr msex GetStr StrWork.sex,1 OutStr mposition GetStr StrWork. position, 30 OutStr mage GetStr StrWork. age, 2 OutStr mstanding GetStr StrWork.standing,2 OutStr msalary GetStr StrWork. salary, 4 OutStr mbirthdate GetStr St rWork. birthdate, 8 ;поиск свободного элемента в массиве структур ;(в структуре все поля пустые): lea mov di,sotr ex, N cycl: cmp [di].sex,’ " ;будем искать свободную запись ;по пустому полю sex je ш1 add di, type worker loop cycl OutStr Err1 jmp go ;копируем StrWork в свободную запись базы данных пИ: продолжение &
640 Приложение 8. Пример работы со структурой lea si, StrWork ;откуда - ds:si ; ;куда - es:di (уже загружены) mov cx.type worker rep movsb заканчиваем и выходим в основное меню программы: jmp go Insert endp search proc near ;процедура поиска записи в массиве и вывода ;на экран ее содержимого OutStr findname GetStr StrWork.nam,30 push ds pop es lea bx.sotr mov cx,N cyc2: push ex mov ex, 30 mov di.bx lea si,StrWork гере cmpsb ;будем искать путем сравнения цепочек jcxz m2 ;если цепочки совпадают, то переход на m2 add bx.type worker pop сх loop cyc2 OutStr Err2 jmp go m2: pop сх ;удалим из стека сх cmp Flag,О je m22 ret m22: ;выведем на экран содержимое найденной записи, ее адрес в Ьх mov.string mnamel,[Ьх].nam,30 mov.string msexl,[bx].sex,1 mov.string mpositionl,[bx].position,30 mov.string magel,[bx].age,2 mov.string mstandingl,[bx].standing,2 mov.string msalaryl,[bx].salary,4 mov.string mbirthdatel,[bx].birthdate,8 OutStr mes2 jmp go search endp
641 delete proc ;удаление - очистка полей структуры пробелами ;сначала найдем нужную для удаления запись, ;для этого частично используем код процедуры search, mov Flag,1 ;указать на частичное использование кода search call search mov Flag,0 ;сбросим Flag ;адрес удаляемой строки в Ьх ;очищаем поля записи, для чего ;используем макрокоманду null_string null_string [bx].nam,30 null_string [bx].sex,1 null_string [bx].position,30 null_string [bx].age,2 null.string [bx].standing,2 null_string [bx].salary,4 null_string [bx],birthdate,8 OutStr mes3 jmp go delete endp exit: OutStr Thanks _Exit стандартный выход; end main ;конец программы; Далее приведен фрагмент включаемого файла mac.inc, который содержит мак- рокоманды, используемые в приведенной выше программе. ;mac.inc (фрагмент) GetStr macro Buf.MaxLen local m.TmpBuf ;ввод строки произвольной длины (функция Oah int 21h) ;на входе: ;Buf - адрес строки, куда будет помещен ввод ;MaxLen - максимальная длина вводимой строки ;на выходе - введенная строка по адресу Buf ;al - длина введенной строки jmp m TmpBuf label byte ;временный буфер для функции Oah (int 21h) rept MaxLen+3 дополнительные три байта, служебная информация db endm m: SaveReg <ds,es,dx,cx> продолжение&
642 Приложение 8. Пример работы со структурой ХОГ сх,сх mov cs:TmpBuf,MaxLen+1 mov ah.Oah push ds pop es push cs pop ds lea dx,cs:TmpBuf int 21h mov al,cs:TmpBuf+1 ;пересылка TmpBuf в Buf co сдвигом на два байта влево ;(для удаления служебных символов): mov cl,al ;длина введенной строки в al lea si,cs:TmpBuf+2 ;откуда - ds:si lea di.buf ;куда - es:di rep movsb LoadReg <cx,dx,es,ds> endm mov_string macro dest,src,len ’.Пересылка строк ;На входе идентификаторы: строки-источника - src, ;строки-приемника - dest ;сегментные регистры ds (для источника) и es (для приемника) ;должны быть загружены ;правильными значениями до вызова макрокоманды mov cx.len lea si,src lea di,dest rep movsb endm null.string macro dest,len local m,Z_String ;очистка строки произвольной длины пробелами ;на входе: ;dest - адрес строки ;len - длина очищаемой строки jmp m Z.String label byte ;пустая строка rept len db ” " endm
643 SaveReg <ds,es,cx,si,di> mov ex,len push ds pop es ;адрес dest (приемник) - es:di push cs pop ds ;адрес Z_String (источник) - ds:si lea si,cs:Z_String lea di,dest rep movsb LoadReg <di,si,cx,es,ds> endm OutStr macro str ;Вывод строки на экран. ;На входе - идентификатор начала выводимой строки. ;Строка должна заканчиваться символом ; На выходе- сообщение на экране. SaveReg <ax,dx> mov ah,09h lea dx.str int 21h LoadReg <dx,ax> endm GetChar macro ;Ввод символа с клавиатуры. ;На выходе - в al введённый символ, mov ah.Olh int 21h endm SaveReg macro RegList Сохранение указанных в списке RegList регистров в стеке. Список регистров должен быть заключен в угловые скобки, ;например - <ах,Ьх,сх>. irp reg,<RegList> push reg endm endm LoadReg macro RegList ;Восстановление указанных в списке RegList регистров из стека. Список регистров должен быть заключен в угловые скобки, продолжение&
644 Приложение 8. Пример роботы со структурой ; например - <ах,Ьх,сх>. irp reg,<RegList> pop reg endm endm _Exit macro ;Выход из программы, mov ax,4c00h int 21h endm
ПРИЛОЖЕНИЕ Текст макроопределения SHOW
В данном приложении приведен текст полезного макроопределения show. Цель его разработки и назначение были рассмотрены в конце урока 13. Это макро- определение позволяет выводить на экран значение, находящееся в одном из регистров al, ah, ах или еах. Фактическим аргументом макроопределения долж- но быть имя одного из этих регистров. Работа макрокоманды заключается в том, что она извлекает двоичное содержимое регистра и преобразует его в пос- ледовательность символов шестнадцатеричных цифр. Сформированная после- довательность этих цифр в виде строки выводится на экран. Интерес представ- ляет способ такого вывода. На этот раз мы используем вывод путем прямого доступа к видеопамяти компьютера. Макрос не использует прерывания BIOS или функции DOS для работы с экраном, а также автоматически определяет текущий режим работы микропроцессора, что дает возможность использовать его и при работе в защищенном режиме работы микропроцессора. Макрос не различает строчных и прописных букв в именах регистров. Не пугайтесь, что текст макроса будет занимать у вас много места. Во-первых, реально в текст объектного модуля будет вставляться только часть макроса для вывода содер- жимого конкретного регистра. Во-вторых, такого рода средства нужны только на этапах отладки и тестирования программы; в окончательном тексте они не должны присутствовать. Кстати, для этого удобно использовать прием с кон- стантой debug, который мы обсуждали при рассмотрении директив условной компиляции IF и IFE на уроке 13. В программе у вас может быть несколько вызовов данного макроса в разных ее ветвях. Для того чтобы знать, какой мак- рос в какой ветви программы сработал, вы можете воспользоваться вторым параметром макроса. Он определяет позицию на экране, с которой будет выво- диться содержимое регистра. Задавая конкретные значения позиции на экране, вы можете определять, какой макрос в какой ветви программы сработал. ;; show.inc ;;макроопределение для визуализации регистров al, ah, ах, еах ;;на входе: ;;агд_п - имя одного из регистров al,ah,ах,еах ;; n_poz - номер позиции на экране, по умолчанию - 1000 Show MACRO arg_n,n_poz:=<1000> LOCAL main_part,disp,pause,template.VideoBuffer,pjnode,ml,m2 ;;переход на начало блока команд, ;;чтобы избежать выполнения данных jmp main.part
647 ;;некоторые константы и переменные FALSE equ 0 ;;ложь TRUE equ Offffh ;;истина ?reg8bit=false ;; флаг того, что передан регистр ah или al ?reg16bit=false ;;флаг того, что передан регистр ах ?reg32bit=false ;;флаг того, что передан регистр еах ;;таблица-шаблон для xlat template db "0123456789ABCDEF" ;;адрес видеобуфера - для прямого вывода на экран VideoBuffer dw 0b800h main_part: ;;начало блока команд ;;сохранение в стеке используемых регистров: ;;еах, ebx, есх, edx, edi, ds, es push eax push ebx push ecx push edx push edi push ds push es push cs pop ds ;;b bx - адрес таблицы-шаблона (для xlat) lea bx,cs:template xor сх,сх ;очистка ex ;;начало блока определения того, ;;какой регистр был передан макросу IFIDNI <al>,<&arg_n> ;;если аргумент=а1 или AL, ?reg8bit=TRUE ;;установка флага 8-битового регистра mov ah,al ENDIF ;;передан не al или AL IFIDNI <ah>, <&arg_n> ;;если аргумент=аГ1 или АН, ?reg8bit=TRUE ;;установка флага 8-битового регистра ENDIF ;;передан не АН или ah IFIDNI <ах>,<&arg_n> ;;если аргумент равен ах или АХ, ?reg16bit=TRUE ;;установка флага 16-битового регистра ENDIF ;;передан не ah, АН ,ах или АХ IFIDNI <еах>,<&arg_n> ;;если аргумент равен еах или ЕАХ, ? reg32bit=TRUE "установка флага 32-битового регистра ENDIF ;;обработка содержимого регистров al, ah, ах, еах продолжение&
648 Приложение 9. Текст макроопределения SHOW IF (?reg8bit) несли передан al или ah push еах and ah,OfOh ;;обращение к старшей четвёрке битов ah shr ах,12 ;;сдвиг битов в начало (16-4=12) xlat ; трансляция таблицы-шаблона ;;помещение символа из al в edi mov di,ах shl di,8 inc ex pop eax and ax.OfOOh ;;обращение к младшей тетраде ah shr ax,8 ;;сдвиг битов в начало (16-(4+4)=8) xlat ;;трансляция таблицы-шаблона or di, ах ;;помещение очередного символа в di shl edi,16 inc сх ENDIF IF (?reg16bit) ;;если передан ax или ax ;;начало обработки значения регистра ах push еах ;;обращение к старшей четвёрке битов ах and ax.OfOOOh shr ах, 12 подвиг битов в начало (16-4=12) xlat ;;трансляция таблицы-шаблона ;; помещение символа из al в старшую ;;тетраду старшей половины edi mov di,ах shl edi,8 inc ex pop eax push eax ;; обращение ко второй четвёрке битов ах and ax.OfOOh shr ах,8 подвиг битов в начало (16-(4+4)=8) xlat ;;трансляция таблицы-шаблона ;;помещение очередного символа в младшую ;;тетраду старшей половины edi or di,ах shl edi, 8 inc ex pop eax push eax and ax.OfOh ;;обращение к третьей четвёрке битов в ах shr ах,4 подвиг битов в начало (16-(4+4+4)=4) xlat ;;трансляция таблицы-шаблона
649 or di,ax ;;помещение очередного символа в edi shl edi,8 inc ex pop eax and ax.Ofh ;;обращение к младшей четвёрке битов ах xlat ;;трансляция таблицы-шаблона or di,ах ;;помещение очередного символа в edi inc сх ENDIF IF (?reg32bit) ;;если передан еах или ЕАХ ;;начало обработки значения регистра еах push еах ;;обращение к старшей четвёрке битов еах and еах,OfOOOOOOOh shr еах,28 ;;сдвиг битов в начало (32-4=28) xlat ; трансляция таблицы-шаблона ;;помещение символа из al в старшую ;;тетраду старшей половины edx mov dh.al shl edx,8 pop eax push eax inc ex pop eax push eax ;;обращение к следующей четвёрке битов еах and еах,OfOOOOOOh shr еах,24 ;;сдвиг битов в начало (32-(4+4)=24) xlat ;;трансляция таблицы-шаблона ;;помещение очередного символа из al в младшую ;;тетраду старшей половины edx mov dh.al shl edx,8 inc ex pop eax push eax and eax.OfOOOOOh shr eax,20 xlat mov dh.al inc ex pop eax push eax and eax.OfOOOOh shr eax,16 продолжение
650 Приложение 9. Текст макроопределения SHOW xlat mov dl.al inc ex pop eax push eax and eax.OfOOOh shr eax,12 xlat or di,ax shl edi,8 inc ex pop eax push eax and eax.OfOOh shr eax,8 xlat or di,ax shl edi,8 pop eax push eax inc ex and eax,OfOh shr eax,4 xlat or di,ax shl edi,8 inc ex pop eax push eax and eax,Ofh xlat or di,ax inc ex ENDIF ;;вывод на экран результата ;;результат в паре edx:ebx, количество цифр в сх ;; проверим режим работы микропроцессора mov еах.сгО test еах,00000001h jnz p_mode ;;для реального режима ;;загружаем в es адрес видеопамяти mov ах,cs:VideoBuffer mov es.ax pjnode:
651 ;;для реального и защищенного режимов ;;количество циклов в сх cld ;;просмотр вперед - для stosw xchg edi,ebx mov di,n_poz ;;начальная позиция для ;;вывода на экран disp: cmp есх, 4 jle ml ;переход, если есх<=4 shld еах,edx,8 shl edx,8 jmp m2 ml: shld eax,ebx,8 shl ebx,8 m2: mov ah,71h ;;байт-атрибут stosw ;;копирование значения ax ;;в es:di (видеобуфер) loop disp ;;повтор цикла сх раз mov сх,65535 ;;задержка pause: loop pause ;;переопределение/восстановление из стека ;;используемых регистров pop es pop ds pop edi pop edx pop ecx pop ebx pop eax ENDM Нужно заметить, что такой вариант макроопределения возможно не является оптимальным. Над ним еще можно поработать с тем, чтобы, в частности, ис- ключить повторы. Мы этого не стали делать потому, что обычно при такого рода оптимизациях становится менее явным смысл программы. Цель этой кни- ги — обучение. Читатель сам по мере накопления опыта наведет необходимый лоск, а возможно и предложит свои варианты решения задачи.
Предупреждающие сообщения и сообщения об ошибках
В данном приложении описаны основные сообщения, выдаваемые транслято- ром TASM. Эта информация позволит вам меньше ломать голову над правиль- ностью перевода текста сообщений с английского языка и поможет понять их смысл в контексте вашей программы. Сообщения об ошибках 32-bit segment not allowed without .386 32-битовые флаги без директивы . 386 не допускаются. Argument needs type override Требуется явно указать тип операнда. Требуется явно указать размер (тип) выражения, так как транслятор не может сделать этого исходя только из кон- текста (см. урок 5). Отметим лишь, что такого рода ошибки исправляются с помощью оператора PTR, позволяющего сообщить транслятору истинный раз- мер операнда. Argument to operation or instruction has illegal size Операнд операции или команды имеет недопустимый размер. Arithmetic overflow Арифметическое переполнение. Потеря значащих цифр при вычислении значе- ния выражения. ASSUME must be segment register В директиве ASSUME должен быть указан сегментный регистр. Bad keyword in SEGMENT statement Неверное ключевое слово в операторе SEGMENT. Один из параметров директивы SEGMENT: тип выравнивания, тип объединения или тип сегмента, — имеет недо- пустимое значение. Can’t add relative quantities Нельзя складывать относительные адреса.
654 Приложение 10. Предупреждающие сообщения и сообщения об ошибках Can’t address with currently ASSUMEd segment registers Невозможна адресация из текущих, установленных директивой ASSUME, сегментных регистров. В выражении содержится ссылка на переменную, для доступа к кото- рой не специфицирован сегментный регистр. Can’t convert to pointer Невозможно преобразование в указатель. Can’t emulate 8087 instruction Невозможна эмуляция команд сопроцессора 8087. Can’t make variable public Переменная не может быть объявлена как PUBLIC. Скорее всего, это вызвано тем, что данная переменная была уже где-то ранее объявлена таким образом, что уже не может быть определена как общая (PUBLIC). Can’t override ES segment Нельзя переопределить сегмент es. Это сообщение характерно для операций типа цепочечных. В некоторых из них нельзя переопределять местоположение сегментной части адреса операнда. Can’t subtract dissimilar relative quantities Недопустимое вычитание относительных адресов. Выражение содержит опера- цию вычитания двух адресов, которая для данных адресов является недопусти- мой. К примеру, это может случиться, если адреса находятся в разных сегментах. Can’t use macro name in expression Недопустимо использование имени макрокоманды в качестве операнда выра- жения. Can’t use this outside macro Использование данного оператора недопустимо вне макроопределения. Code or data emission to undeclared segment He объявлен сегмент для кода или данных. Это может случиться, если предло- жение программы, генерирующее код или данные, не принадлежит ни одному из сегментов, объявленных директивами SEGMENT. Constant assumed to mean Immediate const Константа интерпретируется как непосредственная. Constant too large Слишком большая константа. Константа превышает максимально допустимую для данного режима величину. Например, числа, большие Offffh, можно исполь- зовать, если только директивой . 386/. 386Р или .486/.486Р разрешены команды процессора i386 или i486 соответственно.
Сообщения об ошибках 655 CS not correctly assumed Некорректное значение в регистре cs. CS override in protected mode Переопределение регистра cs в защищенном режиме. Это предупреждающее сообщение выдается, если в командной строке указан параметр /Р. CS unreachable from current segment cs недостижим из текущего сегмента. При определении метки кода с помощью двоеточия (:) или с помощью директив LABEL или PROC сегментный регистр не указывает на текущий кодовый сегмент или группу, содержащую текущий ко- довый сегмент. Declaration needs name В директиве объявления не указано имя. Directive ignored in Turbo Pascal model В режиме TP AS CAL директива игнорируется. Directive not allowed inside structure definition Недопустимая директива внутри определения структуры. Duplicate dummy arguments Недопустимо использование одинаковых имен для формальных параметров Expecting METHOD keyword Требуется ключевое слово METHOD. Expecting offset quantity Требуется указать величину смещения. Expecting offset or pointer quantity Требуется указать смещение или указатель. Expecting pointer type Операнд должен быть указателем. Означает, что операндом текущей команды должен быть адрес памяти. Expecting record field name Требуется имя поля записи. Инструкция SETFIELD или GETFIELD использована без последующего имени поля. Expecting register ID Требуется идентификатор регистра.
656 Приложение 10. Предупреждающие сообщения и сообщения об ошибках Expecting scalar type Операнд должен быть константой. Expecting segment or group quantity Должно быть указано имя сегмента или группы. Extra characters on line Лишние символы в строке. Forward reference needs override Ошибка при использовании умолчания для ссылки вперед. Global type doesn’t match symbol type Тип, указанный в директиве GLOBAL, не совпадает с действительным типом имени идентификатора. ID not member of structure Идентификатор не является полем структуры. Illegal forward reference Недопустимая ссылка вперед. Illegal immediate Недопустим непосредственный операнд. Illegal indexing mode Недопустимый режим индексации. Illegal instruction Недопустимая команда. Illegal instruction for currently selected processor(s) Недопустимая команда для выбранного в настоящий момент процессора. Illegal local argument Недопустимый локальный параметр. Illegal local symbol prefix Недопустимый префикс для локальных имен идентификаторов. Illegal macro argument Недопустимый параметр макрокоманды. Illegal memory reference Недопустима ссылка на память.
Сообщения об ошибках 657 Illegal number Недопустимое число. Illegal origin address Недопустимый начальный адрес. Illegal override in structure Недопустимое переопределение в структуре. Illegal override register Недопустимое переопределение регистра. Illegal radix Недопустимое основание системы счисления. В директиве . RADIX в качестве ос- нования системы счисления указано недопустимое число. Основанием систе- мы счисления могут быть только числа 2, 8, 10 и 16. Эти числа интерпретиру- ются как десятичные независимо от текущей системы счисления. Illegal register for instruction Недопустимый регистр в инструкции. В качестве операнда инструкции SETFIELD и GETFIELD использован недопустимый регистр. Illegal register multiplier Недопустимо указание множителя для регистра. Illegal segment address Недопустимый сегментный адрес. Illegal use of constant Недопустимо использование константы. Illegal use of register Недопустимо использование регистра. Illegal use of segment register Недопустимо использование сегментного регистра. Illegal USES register В директиве USES указан недопустимый регистр. Illegal version ID Недопустимый идентификатор версии. Illegal warning ID Недопустимый идентификатор предупреждающего сообщения.
658 Приложение 10. Предупреждающие сообщения и сообщения об ошибках Instruction can be compacted with override Возможно сокращение длины команды, если явно указать тип имени. Из-за на- личия ссылки вперед на имя идентификатора объектный код содержит дополни- тельные команды NOP. Этим самым транслятор резервирует место для размеще- ния адреса идентификатора. При необходимости код можно сократить, убрав ссылку вперед либо явно указав тип символического имени. Invalid model type Недопустимая модель памяти. Invalid operand(s) to instruction Недопустимый операнд (операнды) для данной команды. Labels can’t start with numeric characters Метки не могут начинаться с цифровых символов. Line too long — truncated Строка слишком длинная, и поэтому производится усечение. Location counter overflow Переполнение счетчика адреса. Method call requires object name В вызове метода необходимо имя объекта. Missing argument list Отсутствует список аргументов. Missing argument or < Отсутствует аргумент либо не указана угловая скобка <. Missing argument size variable Отсутствует переменная для размера блока параметров. Missing СОММ ID Отсутствует идентификатор в директиве СОММ. Missing dummy argument Отсутствует формальный параметр. Missing end quote Отсутствует закрывающая кавычка. Missing macro ID Отсутствует идентификатор макрокоманды.
Сообщения об ошибках 659 Missing module name Отсутствует имя модуля. Missing or illegal type specifier Отсутствует или неверно указан спецификатор типа. Missing table member ID Пропущен идентификатор элемента таблицы. Missing term in list Отсутствует член в списке параметров. Missing text macro Отсутствует текстовая макрокоманда. Model must be specified first Сначала должна быть указана модель памяти. Module is pass-dependant — compatibility pass was done Модуль зависит от прохода. Выполнен проход, обеспечивающий совместимость с MASM. Name must come first Имя должно быть указано первым. Near jump or call to different CS Адресат ближнего перехода или вызова находится в другом кодовом сегменте. Need address or register Требуется указать адрес или регистр. Need colon Требуется двоеточие. Need expression Требуется указать выражение. Need file name after INCLUDE В директиве INCLUDE должно быть указано имя файла. Need left parenthesis Отсутствует левая круглая скобка. Need method name Требуется имя метода.
660 Приложение 10. Предупреждающие сообщения и сообщения об ошибках Need pointer expression Требуется выражение-указатель. Need quoted string Требуется указать строку в кавычках. Need register in expression В выражении требуется указать имя регистра. Need right angle bracket Отсутствует правая угловая скобка. Need right curly bracket Требуется правая фигурная скобка. Need right parenthesis Отсутствует правая круглая скобка. Need right square bracket Отсутствует правая квадратная скобка. Need stack argument Не указан стековый параметр в команде арифметики с плавающей запятой. Need structure member name He указано имя поля структуры. Not expecting group or segment quantity Использование имени группы или сегмента недопустимо. One non-null field allowed per union expansion При расширении объединения допускается указывать только одно поле непус- тым. Only one startup sequence allowed Допускается только одна директива генерации кода инициализации. Open conditional Открытый условный блок. После завершающей программу директивы END об- наружен незакрытый, условно ассемблируемый блок, открытый одной из ди- ректив IFxxx. Open procedure Открытая процедура. После завершающей программу директивы END обнару- жен незакрытый директивой ENDP блок описания процедуры, открытый где-то в программе директивой PROC.
Сообщения об ошибках 661 Open segment Открытый сегмент. После завершающей программу директивы END обнаружен незакрытый директивой ENDS сегмент, открытый где-то в программе директи- вой SEGMENT. Open structure definition He указан конец определения структуры (директива ENDS). Operand types do not match He совпадают типы операндов. Тип одного из операндов команды не совпадает с типом другого операнда либо не является типом, допустимым для данной команды. Operation illegal for static table member Для статического элемента таблицы операция не допускается. Pass-dependant construction encountered Обнаружена конструкция, зависящая от прохода. Данную ошибку можно ис- править, убрав ссылки вперед либо указав нужное число проходов транслятора в опции командной строки /т. Pointer expression needs brackets Адресное выражение должно быть заключено в квадратные скобки. Positive count expecting Счетчик должен быть положительным. Record field too large Слишком длинное поле в записи. Record member not found He найден статический элемент записи. Recursive definition not allowed for EQU Рекурсивное определение недопустимо в директиве EQU. Register must be AL or AX Допустимо указание только регистра al или ах. Register must be DX Допустимо указание только регистра dx. Relative jump out of range by__bytes Адрес назначения условного перехода превышает допустимый предел на_байт.
662 Приложение 10. Предупреждающие сообщения и сообщения об ошибках Relative quantity illegal Недопустимый относительный адрес. Ссылка на адрес памяти не может быть разрешена на этапе ассемблирования. Reserved word used as symbol Зарезервированное слово используется в качестве имени идентификатора. Rotate count must be constant or CL Счетчик в командах сдвига должен быть указан с помощью константы или реги- стра cl. Rotate count out of range Недопустимое значение счетчика сдвига. Segment alignment not strict enough Выравнивание сегмента недостаточно точное. Segment attributes illegally redefined Недопустимое переопределение атрибутов сегмента. Суть здесь в том, что пользователь может повторно открывать уже определенный ранее сегмент. Но при этом атрибуты этого сегмента должны иметь те же самые значения либо вообще быть опущены (тогда будут взяты прежние значения). Segment name is superfluous Имя сегмента игнорируется. String too long Слишком длинная строка. Длина указанной в кавычках строки превышает 255 символов. Symbol already defined:_ Имя идентификатора уже определено. Symbol already different kind Имя идентификатора уже объявлено с другим типом. Symbol has no width or mask Имя идентификатора не может быть использовано в операциях WIDTH и MASK. Symbol is not a segment or already part of a group Имя идентификатора не является именем сегмента, или оно уже определено в группе. Text macro expansion exceeds maximum line length Расширение текстовой макрокоманды превышает максимально допустимую длину.
Сообщения об ошибках 663 Too few operands to instruction В команде не хватает операндов. Too many errors or warnings Слишком много ошибок или предупреждений. Число сообщений об ошибках превысило максимально возможное число 100. Too many initial values Слишком много начальных значений. Too many register multipliers in expression В выражении содержится слишком много множителей для регистров. Too many registers in expression В выражении указано слишком много регистров. Too many USES registers Слишком много регистров в директиве USES. Trailling null value assumed Предполагается конечное пустое значение. Undefined symbol Идентификатор не определен. Unexpected end of file (no END directive) Неожиданный конец файла (нет директивы END). Unknown character Неизвестный символ. Unmatched ENDP:_ Непарная директива ENDP:_. Unmatched ENDS:_ Непарная директива ENDS:_. User-generated error Ошибка, сгенерированная пользователем. Сообщение выдается в результате выполнения одной из директив генерирования ошибки. USES has no effect without language USES игнорируется без спецификации языка. Value out of range Значение константы превышает допустимое значение.
664 Приложение 10. Предупреждающие сообщения и сообщения об ошибках Сообщения о фатальных ошибках Кроме вышерассмотренных ошибок TASM формирует еще один тип — сооб- щения о фатальных ошибках. Их особенность в том, что при их возникнове- нии TASM выдает соответствующее сообщение и немедленно прекращает ас- семблирование исходного файла. Bad switch Неверный параметр-переключатель командной строки. Can’t find ©file__ Не найден файл подсказок__. Can’t locate file_ Не обнаружен файл____. При выдаче этого сообщения нужно проверить, пра- вильно ли указаны в имени файла имя диска и путь к файлу, заданному в ди- рективе INCLUDE. Error writing to listing file Ошибка при записи в файл листинга. Возможно, просто исчерпано место на диске. Error writing to object file Ошибка при записи в объектный файл. Возможно, просто исчерпано место на диске. File not found Не найден файл. В командной строке указано имя несуществующего исходного файла. File was changed or deleted while assembly in progress Файл был изменен или уничтожен в процессе ассемблирования. Insufficient memory to process command line He хватает памяти для обработки командной строки. Internal error Внутренняя ошибка. Invalid command line Недопустимая командная строка. Invalid number after _ Недопустимый номер после
Сообщения о фатальных ошибках 665 Out of hash space He хватает памяти под хеш-таблицы. Для каждого имени идентификатора в про- грамме транслятор формирует один элемент таблицы идентификаторов. Эта таб- лица рассчитана на 16 384 имен идентификаторов. При необходимости это число можно увеличить, используя параметр командной строки /kh. Out of memory He хватает памяти. Для ассемблирования пользовательского файла недостаточно свободной памяти. Out of string space He хватает памяти под строки. Здесь имеется в виду оперативная память для хранения строк, представляющих собой имена идентификаторов, имена фай- лов, информацию для разрешения ссылок вперед, текстов макрокоманд. Допус- кается максимум 512 Кбайт памяти. Too many errors found Обнаружено слишком много ошибок. Трансляция прекращена, так как в исход- ном файле содержится слишком много ошибок. Unexpected end of file (no END directive) Неожиданный конец файла (отсутствует директива END).
Бесценный опыт ПРОФЕССИОНАЛОВ «Библиотека программиста» - Стен Трухильо «Графика для Windows средствами DirectDraw: библиотека программиста (+CD)» код 1163, в продаже Теллес Мэтт «Borland C++ Builder: библиотека программиста (+CD)» код 1139, в продаже Палмер Скотт «VBScript и ActiveX: библиотека программиста (+CD)» код 228, IV кв. 1998 Д. Тейлор «Delphi 3: библиотека программиста (+CD)» код 1134, в продаже это не только новая книга на вашей книжной полке, но и новая библиотека исходных кодов на вашем компьютере. Самое ценное, чем обладает любой специалист - это его опыт. Авторы книг этой серии щедро делятся своим богатым опытом с читателями. Книги содержат огромное количество примеров: от «маленьких хитростей» до законченных приложений. Ко всем книгам серии прилагается компакт диск, содержащий исходные тексты рассматриваемых в книге примеров, а также большое количество вспомогательных утилит и другого «freeware» программного обеспечения. Если вам нравятся красивые и нестандартные решения, «Библиотека программиста» - это серия для вас! Заказ книг наложенным платежом; 197198, Санкт-Петербург, а/я 619 postbook@piter-press.ru Оптовая продажа; (095) 235-5583 (812) 327-9337
Путь к вершинам МАСТЕРСТВА Книги серии «Для профессионалов» призваны облегчить труд программистов, системных администраторов, инженеров. Среди авторов этой серии — разработчики программных пакетов и технологий, опытнейшие программисты и менеджеры. В книгах серии «Для профессионалов» подробное описание практического применения новейших информационных технологий гармонично сочетается с теоретическим материалом. Множество практических советов, примеров и решений, справочная информация помогут читателям достичь вершин мастерства в своей работе. ЛНм Деннинг А. Деннинг ActiveX для профессионалов код 760, в продаже А. Горев, С. Макашарипов Microsoft SQL Server 6.5 для профессионалов код 738, в продаже В. Чен, Б. Вейн Реестр Windows NT для профессионалов IV кв. 1998 Б. Гербер Microsoft Exchange Server 5.5 для профессионалов код 1216, IV кв. 1998 А. Ноулс Оптимизация и настройка Windows NT 4 для профессионалов код 1196, в продаже Заказ книг наложенным платежом: 197198, Санкт-Петербург, а/я 619 postbook@piter-press.ru Оптовая продажа: (095) 235-5583 (812)327-9337 ПИТЕР www.piter-press.ru
Издательство «Питер» представляет новую книжную серию: Сертификационный ЭКЗАМЕН - ЭКСТЕРНОМ Книги «Сертификационный экзамен — экстерном»: > Позволяют систематизировать знания по предмету ► Помогают быстро и эффективно подготовиться к экзамену > Содержат все необходимые сведения и могут использоваться в качестве справочников В каждой книге серии: ► Сжатый конспект по теме каждого раздела > Типовые экзаменационные вопросы с разбором ответов ► Советы по стратегии и тактике сдачи экзамена ► Список дополнительной литературы ► Тестовый экзамен по всему курсу Состав серии: Networking Essentials. Экзамен 70-058 ► NT Server 4. Экзамен 70-067 ► NT Workstation 4. Экзамен 70-073 ► NT Server 4 in the Enterprise. Экзамен 70-068 TCP/IP. Экзамен 70-059 Круг читателей ► Системные администраторы ► Программисты ► Опытные пользователи ПК. желающие повысить свой профессиональный уровень РЕКОМЕНДОВАНО ПРЕДСТАВИТЕЛЬСТВОМ КОРПОРАЦИИ Microsoft В МОСКВЕ ДЛЯ ПОЛУЧЕНИЯ БОЛЕЕ ПОДРОБНОЙ ИНФОРМАЦИИ ОБРАЩАЙТЕСЬ НА НАШ WWW-СЕРВЕР: WWW.PITER-PRESS.RU Заказ книг наложенным платежом: 197198, Санкт-Петербург, а/я 619, e-mail: postbook@piter-press.ru http://vvww. piter-press. ru Оптовая продажа: Москва (095) 235-5583 С.-Петербург (812) 327-9337 ПИТЕР WWV И ГЕЯ РНЕ' >3 RU
Настольная книга для каждого Издательский дом «Питер» представляет вам новую серию справочников, К. Рейчард. Э. Фостер-Джонсон Unix: справочник код 1226. в продаже К. Рейчард, П. Фолькердинг Linux: справочник код 1288, в продаже П. Винтер Microsoft Word 97: справочник код 393, е продаже R Мадлен Microsoft Office 97: справочник код 368, е продаже Д. Снайдер Windows 95: справочник код 363, в продаже Р. Муллен HTML 4: справочник код 1144, в продаже Л. Вуд Web-графика: справочник код 686, е продаже М. Эферган Java:справочник код 371, е продаже R Дарнелл JavaScript: справочник код 392, е продаже которая избавит вас от утомительных поисков информации в горах литературы. Каждая из этих книг содержит исчерпывающую информацию и ответит на все вопросы по работе самых популярных программ и языков программирования. Глубина изложения, удобно структурированное расположение материала, алфавитный указатель, подробный словарь сделают эти книги незаменимыми помощниками в вашей повседневной работе. Г^ППТЕР www.piter-press.ru Заказ книг наложенным платежом: 197198, Санкт-Петербург, а/я 619 postbook@piter-press.ru Оптовая продажа: (095)235-5583 (812)327-9337
BYTE Всемирно известный компьютерный журнал для профессионалов —В сентябре 1998 года Издательский Дом «Питер» начал выпуск всемирно известного компьютерного журнала — BYTE. Журнал BYTE/Россия рассчитан на профессионалов в области информационных технологий. —errs новости и обзоры Основными направлениями редакционных материалов являются: • создание сетевых приложений: — Сетевая интеграция инструментальные средства разработки, операционные системы, аппаратные средства, защита информации, сетевое программное — Управление данными обеспечение и ПО для групповой работы, Internet/intranet технологии; о управление данными: серверы баз данных, —Архитектура программирование баз данных и инструменты администрирования, средства хранения — Сетевые приложения информации, руководства и документации, обзоры аппаратных и программных средств, складирование и анализ данных; — BYTE Lab: Software&Hardware • сетевая интеграция: аппаратные средства LAN/WAN, удаленный доступ, видеоконференции и компьютерная телефония, виртуальные сети, коллективная работа, электронная коммерция и защита электронных сделок и т. д. Наравне с переводными материалами, журнал публикует статьи российских авторов специально подготовленные для BYTE/Россия. http://www.piter-press.ru Мы предлагаем Вам оформить редакционную подписку на журнал BYTE/Россия (он будет высылаться на Ваш адрес в отдельном конверте) Цена подписки на 6 месяцев 81 руб. (13,50 руб. за номер) — для частных лиц 97,20 руб. (16,20 руб. за номер) — для организаций За справками о подписке, а также по вопросам оформления безналичной подписки обращайтесь по телефону/факсу: (812) 294-0104, или пишите на адрес: postbook@plter-press ru
Издательский дом с^пптер представляет новую книжную серию Наиболее полное и подробное руководство Энциклопедия Windows 98 В этой книге вы найдете всю информацию, которая может понадобиться при работе с новой операционной системой Microsoft Windows 98. Вам не придется перерывать горы литературы или тратить драгоценное время на изучение разделов справочной системы для того, чтобы найти ответ на интересующий вас вопрос. Аппаратные средства IBM PC. Энциклопедия Это книга, которая содержит все необходимее сведения для понимания работы компьютера. Кроме чисто справочной информации, вы найдете на ее страницах глубокие сведения как по отдельным электронным подсистемам, так и по их организации в единое целое — знаменитый персональный компьютер, со всеми его достоинствами и недостатками. Защита от компьютерных вирусов. Энциклопедия Наиболее полное описание существующих и существовавших вирусов и средств борьбы с ними. Вы узнаете о различных типах вирусов, научитесь правильно администрировать локальные сети, восстанавливать файлы из поврежденных разделов FAT и NTFS. Книга предназначена для широкого круга читателей, от радовых пользователей до системных администраторов и разработчиков. В этих книгах вы найдете ответ на любой вопрос, который может возникнуть у вас в ходе работы |фун£ (аментальное | = I Нзж^ниынпмгмож I § . ;r (М2) 327-933’ Посетите наш Web-магазин: http://www.piter-press.ru С ценами и условиями заказа книг можно ознакомиться на странице книжного клуба «Профессионал»
С^ППТЕР Книги издательства «Питер» можно купить В РОЗНИЦУ в магазинах городов России и СНГ ОПТОМ по минимальным ценам у наших дилеров: Москва_________________________________________________________________________ От ел сбыта издательства «Питер», м. «Серпуховская», 1-й Щиповский пер., 3, оф. 207, тел. (095) 235 55 83, факс 234 38 15 С.-Петербург___________________________________________________________________ Отдел сбыта издательства «Питер», м. «Электросила», ул. Благодатная. 67, тел. (812) 327 93 37,294 54 65, e-mail: salesfffipiter-press.ru Украина________________________________________________________________________ Харьков, фирма «Питер-Т», тел. (0572) 23 75 63, факс (0572) 14 96 09, e-mail: | piter-tfffilincom.kharkov.ua. Почтовый адрес: 310093, г. Харьков, а/я 9130 Киев, филиал фирмы «Питер-Т», тел. (044) 211 83 77 Беларусь_______________________________________________________________________ .Минск, фирма «ПитерБел», ул. Кедышко, 19, тел. факс (0172) 16 00 06 Северный Кавказ________________________________________________________________ Ессентуки, фирма «Россы», ул. Долина Роз, 23-27, тел, факс (86534) 6 93 09 Почтовый адрес: 357600 г. Ессентуки, а я S0 Урал ___ ______________________________________________________________________ Екатеринбург, магазин А” 14, ул. Челюскинцев, 23, тел. (3432) 53 24 90 Екатеринбург, фирма • Вадео.Д 1люс», тел. (3432) 42 07 75 БАШКОРТОСТАН___________________________________________________________________ Уфа, фирма «Азия», ул. Зенцова, 70 (оптовая продажа), маг. «Оазис», ул. Чернышевского, 88, телефакс (3472) 50 39 00, e-mail: asia@diaspro.com I Татарстан______________________________________________________________________ i Казань, фирма «Таис», ул. Гвардейская, 9а, тел. (8432) 76 34 55, факс 38 24 32, е- | mail: borism@tatincom.ru Сибирь_________________________________________________________________________ Новосибирск, фирма “Топ-книга”, тел. (3832) 36 10 26, факс 36 10 27, e-mail: office@top-kniga.ru, http://www.top-kniga.ru. Почтовый адрес: 630117, а/я 560 Тюмень, фирма «Друг», ул. Володарского, 38, тел./факс (3452) 24 27 18, e-mail: . drug@diaspr.tyumen.su Книги ПО ПОЧТЕ наложенным платежом: 197198, С.-Петербург, а/я 619 — для жителей России 310093, Харьков, а/я 9130 — для жителей Украины , 220012, Минск, а/я 104 — для жителей Беларуси e-mail: postbook@piter-press.ru http://www.piter-press.ru
В. Юров С. Хорошенко 17 уроков для освоения языка дискета прилагается С^ППТЕР
В. Юров С. Хорошенко Пройди путь от ученика до мастера Если вы хотите освоить новый язык Следите за новинками серии: срок выхода 14 уроков помогут вад освоить Visual C++ на уровне сертифицированного специалиста в продаже это то, что вам нужно для быстрого обучения и продуктивной работы! До сих пор программирование на языке низкого уровня Assembler было прерогативой узкого круга профессионалов. Впервые читателю предлагается учебное пособие, способствующее формированию фундаментальных знаний по архитектуре процессора Intel Pentium и основам программирования на языке Assembler, не требующее никакой начальной подготовки, кроме определенного опыта работы с персональным компьютером. Написанная профессиональным преподавателем, эта книга является великолепным учебником как для студентов вузов, так и для тех, кто хочет самостоятельно изучить Assembler. сделают из вас срок выхода программиста, даже если прежде вы не написали ни строчки программного кода 24 урока помогут разобраться с внутренним устройством компьютера и научиться решать типичные проблемы эксплуатации Многочисленные примеры исходных текстов помогут на практике овладеть приемами низкоуровневого программирования. На прилагаемой дискете вы найдете • исходный код всех программ, рассматриваемых в книге • справочную систему по языку Assembler, построенную на основе материала книги и предназначенную для оказания оперативной помощи пользователю при разработке программ Ь^ППТЕР www.piter-press.ru самые необходимые знания и навыки по архитектуре, установке, конфигурации и поддержке NT Server 4 срок выхода СЕРИЯ НАЧИНАЮЩИЙ ипгош »*«(» УЧЕБНОЕ ПОСОБИЕ. ЯЗЫКИ ПРОГРАММИРОВАНИЯ ISBN 5-314-00047-4