Галисеев
Самоучитель
АССЕМБЛЕР
для WIN32
Состав серии
1. Базылев Г.В. Photoshop CS. Самоучитель
2. Бидасюк Ю.М. Mathsoft® MathCAD 12. Самоучитель
3. Варакин А.С. AutoCAD 2006. Самоучитель
4. Галисеев Г.В. Программирование в среде Delphi 2005. Самоучитель
5. Галисеев Г.В. Программирование на языке C#. Самоучитель
6. Гусев В.С. Освоение Internet. Самоучитель
7. Гусев В.С. Поиск в Internet. Самоучитель
8. Дригалкин В.В. HTML в примерах. Как создать свой Web-сайт. Самоучитель
9. Захарченко Н.И. Бизнес-статистика и прогнозирование в MS Excel. Самоучитель
10.Козлин В.И. Музыкальный редактор Sibelius. Самоучитель
11. Курбатова Е.А. MATLAB 7. Самоучитель
12. Курбатова Е.А. Microsoft Excel 2003. Самоучитель
13. Куссуль Н.Н., Шелестов А.Ю. Использование PHP. Самоучитель
14. Меженный О.А. Microsoft Office 2003. Самоучитель
15. Меженный О.А. Microsoft Windows XP. Самоучитель, 2-е издание
16. Меженный О.А. Microsoft Word 2003. Самоучитель
17. Меженный О.А. Turbo Pascal. Самоучитель
18. Полонская Е.Л. Язык HTML. Самоучитель
19. Птицын К.А. Серверы Linux. Самоучитель
20. Сергеев А.П. Офисные локальные сети. Самоучитель
21. Сергеев А.П., Кущенко С.В. Основы компьютерной графики. Adobe Photoshop и CorelDRAW — два в
одном. Самоучитель
22. Сергеев А.П., Терен А.Н. Программирование в Microsoft Visual C++ 2005. Самоучитель
23. Сингаевская Г.И. Microsoft Project 2003. Самоучитель
24. Слепцова Л.Д. Программирование на VBA. Самоучитель
25. Смолина М.А. Adobe Illustrator CS. Самоучитель
26. Смолина М.А. CorelDraw X3. Самоучитель
27. Спека М.В. Microsoft PowerPoint 2003. Самоучитель
28. Спека М.В. Создание Web-сайтов. Самоучитель
29. Ставровский А.Б. Первые шаги в программировании. 2-е издание. Самоучитель
30. Степаненко О.С. Использование Adobe Photoshop CS2. Самоучитель.
31. Степаненко О.С. Настройка персонального компьютера. Установки BIOS. Самоучитель
32. Степаненко О.С. Первая помощь ПК. Анализ сбоев и устранение неполадок. Самоучитель
33. Степаненко О.С. Персональный компьютер. 2-е издание. Самоучитель
34. Степаненко О.С. Установка и настройка Windows XP. Самоучитель
35. Бурлаков М.А. Macromedia Flash 8. Самоучитель
36. Тимошок Т.В. Microsoft Access 2003. Самоучитель
37. Шмидский Я.К. Программирование на языке C++. Самоучитель
38. Футько В.П. ПК — это очень просто. Для тех, кто решил приобрести компьютер. Самоучитель
Самоучитель
АССЕМБЛЕР
для WIN32
www.dialektika.com
Галисеев, Г.В.
Г15 Ассемблер для Win 32. Самоучитель : М. : Издательский дом ‘‘Вильямс’’, 2007.
368 с. : ил.
ISBN 9785845911971 (рус.)
Эта книгасамоучитель поможет читателю самостоятельно освоить основы языка
ассемблера и научиться создавать программы на этом языке. Здесь подробно расска
зано о том, как начать работать с ассемблером и как писать программы на этом язы
ке. В книге рассматривается в основном 32разрядный режим работы ассемблера,
позволяющий обращаться к процедурам прикладного интерфейса (API) Windows.
Тем не менее, приведены и некоторые сведения, специфичные только для 16раз
рядного режима, например, описание работы прерываний и понятие о программи
ровании для DOS. Однако книга является лишь введением в язык ассемблера, по
этому в ней не отражены расширенные возможности языка ассемблера и команды,
разработанные для новейших версий процессоров.
Книга не является учебником по программированию для начинающих и для ра
боты с ней необходимо иметь базовые понятия о программировании, а также хотя
бы минимальное представление о том, как работает операционная система Windows.
Стр. 4
Îãëàâëåíèå
Введение 17
Глава 1. Предисловие 18
Стр. 5
Ñîäåðæàíèå
Введение 17
От издательства “Диалектика” 17
Глава 1. Предисловие 18
Что такое язык ассемблера 18
Прикладные программы на языке ассемблера 19
Машинный язык 20
Стр. 6
Архитектура памяти 47
Сегментированная модель памяти 48
Ввод&вывод 50
Резюме 51
Контрольные вопросы 51
Содержание 7
Стр. 7
Глава 4. Программирование для Windows 95
Программирование для консоли 95
Набор символов и функции Windows API 96
Уровни доступа 96
Типы данных Windows 97
Дескрипторы консоли 98
Функции консоли Win32 99
Использование функций 100
Ввод с консоли 104
Ввод одиночного символа 105
Структуры данных 106
Функция WriteConsoleOutputCharacter 106
Создание библиотеки загрузочных модулей 107
Подготовка модуля для библиотеки 108
Работа с файлами 112
Функция создания файла 112
Параметр DesiredAccess 112
Параметр CreationDisposition 112
Примеры работы с файлами 113
Функция CloseHandle 114
Функция ReadFile 114
Функция WriteFile 115
Пример записи в файл 115
Перемещение указателя файла 116
Пример чтения файла 117
Управление окном 118
Функция SetConsoleTitle 119
Функция GetConsoleScreenBufferlnfo 119
Функция SetConsoleWindowlnfo 120
Функция SetConsoleScreenBufferSize 121
Управление курсором 121
Управление цветом 121
Пример использования значений цвета 122
Функции времени и даты 123
Функции GetLocalTime и SetLocalTime 124
Функция GetTickCount 125
Функция Sleep 125
Процедура получения локального времени 125
Создание секундомера 126
Резюме 127
Контрольные вопросы 128
8 Содержание
Стр. 8
Представление строк символов 131
Оператор DUP 132
Объявление слов 132
Указатели 132
Формат перестановки в памяти 133
Объявление двойного слова 133
Символические константы 133
Директива равенства 133
Директива EQU 134
Директива TEXTEQU 134
Модели памяти 135
Выполняемые программы 136
Директивы установки типа процессора 137
Команды пересылки данных 138
Команда MOV 138
Контроль соответствия типов 139
Использование дополнительного смещения в операторах 140
Команда XCHG 140
Арифметические команды 141
Команды INC и DEC 141
Команда ADD 141
Команда SUB 142
Флаги, используемые в командах ADD и SUB 142
Операнды без знака 143
Знаковое переполнение 143
Tипы операндов 144
Регистровые операнды 144
Непосредственные операнды 145
Операнды с прямой адресацией 145
Оператор OFFSET 145
Операнды со смещением 145
Операторы и выражения 146
Арифметические операторы 149
Старшинство и ассоциативность операторов 149
Операторы OFFSET, SEG, PTR, LABEL и EVEN 150
Оператор OFFSET 150
Оператор SEG 151
Оператор PTR 151
Директива LABEL 151
Директивы EVEN и EVENDATA 152
Операторы типа и размера 152
Оператор SHORT 152
Оператор TYPE 152
Оператор LENGTH 153
Оператор SIZE 153
Команды JMP и LOOP 153
Команда JMP 154
Содержание 9
Стр. 9
Команда LOOP 154
Команды LOOP, LOOPW, LOOPD 155
Косвенная адресация 155
Косвенные операнды 155
Сегменты по умолчанию 156
Изменение значений по умолчанию 156
Использование команды LOOP для отображения строки 157
Использование команды LOOP для суммирования массива целых чисел 158
Базовые и индексные операнды 160
32&разрядные регистры 160
Базо&индексные операнды 160
Базо&индексные операнды с дополнительным смещением 161
Команды процессоров 80386 и более старших моделей 162
Команды MOVZX и MOVSX 162
Команда XADD 163
Процедуры 163
Резюме 164
Контрольные вопросы 165
10 Содержание
Стр. 10
Глава 7. Логические вычисления 188
Команды логических вычислений и сравнения 188
Команда AND 189
Команда OR 189
Команда XOR 191
Инверсия бит 191
Инверсия флагов клавиатуры 191
Команда NOT 191
Команда NEG 192
Команда TEST 192
Команды BT, BTC, BTR, BTS 192
Команды BSF и BSR 193
Команда CMP 193
Команда CMPXCHG 194
Логические структуры 195
Команда Jcond 196
Типы команд условного перехода 196
Генерация кодов для условных переходов 198
Примеры условных переходов 199
Проверка ввода букв 199
Наибольшее и наименьшее значения в массиве 200
Команда SETcond 201
Циклы с условием 202
Команды LOOPZ и LOOPE 202
Команды LOOPNZ и LOOPNE 202
Директивы для логических вычислений 203
Директива .IF 203
Директивы .REPEAT и .WHILE 205
Резюме 206
Контрольные вопросы 206
Содержание 11
Стр. 11
Директива RECORD 215
Оператор MASK 216
Оператор WIDTH 216
Сложение и вычитание больших чисел 217
Команда ADC 217
Команда SBB 218
Умножение и деление 219
Команда MUL 220
Команда IMUL 221
Команда DIV 221
Команда IDIV 222
Команды CBW, CWD, CDQ и CWDE 222
Кодировка ASCII и арифметика упакованных чисел 223
Команда AAA 224
Команда AAS 224
Команда AAM 224
Команда AAD 225
Команды DAA и DAS 225
Резюме 226
Контрольные вопросы 226
12 Содержание
Стр. 12
Директива IRP 243
Совмещение сдвигов и повторений 244
Директива IRPC 245
Резюме 245
Контрольные вопросы 246
Содержание 13
Стр. 13
ADD 277
AND 277
BOUND 277
BSF, BSR 277
BSWAP 278
BT, BTC, BTR, BTS 278
CALL 278
CBW 279
CDQ 279
CLC 279
CLD 279
CLI 279
CMC 279
CMP 280
CMPS, CMPSB, CMPSW, CMPSD 280
CMPXCHG 280
CWD 280
CWDE 281
DAA 281
DAS 281
DEC 281
DIV 281
ENTER 282
HLT 282
IDIV 282
IMUL 282
IN 283
INC 283
INS, INSB, INSW, INSD 283
INT 284
INTO 284
IRET 284
Jcondition 284
JCXZ, JECXZ 285
JMP 285
LAHF 285
LDS, LES, LFS, LGS, LSS 286
LEA 286
LEAVE 286
LOCK 286
LODS, LODSB, LODSW, LODSD 286
LOOP, LOOPW 287
LOOP 287
LOOPE, LOOPZ 287
LOOPNE, LOOPNZ 287
MOV 288
14 Содержание
Стр. 14
MOVS, MOVSB, MOVSW, MOVSD 304
MOVSX 304
MOVZX 304
MUL 305
NEG 305
NOP 305
NOT 305
OR 306
OUT 306
OUTS, OUTSB, OUTSW, OUTSD 306
POP 306
POPA, POPAD 307
POPF, POPFD 307
PUSH 307
PUSHA, PUSHAD 307
PUSHF, PUSHFD 308
PUSHW, PUSHD 308
RCL 308
RCR 308
REP 309
REPcondition 309
RET, RETN, RETF 309
ROL 309
ROR 310
SAHF 310
SAL 310
SAR 310
SBB 311
SCAS, SCASB, SCASW, SCASD 311
SETcondition 311
SHL 312
SHLD 312
SHR 312
SHRD 312
STC 313
STD 313
STI 313
STOS, STOSB, STOSW, STOSD 313
SUB 314
TEST 314
WAIT 314
XADD 314
XCHG 315
XLAT, XLATB 315
XOR 315
Содержание 15
Стр. 15
Библиотека функций для работы в 32&разрядном режиме 299
Подключаемый файл для работы с библиотекой функций 327
Подключаемый файл с макроопределениями 336
Перечень команд DOS для Windows XP 341
16 Содержание
Стр. 16
Введение
Данную книгу можно рассматривать как учебник, который позволит читателю понять
основы языка ассемблера и научиться создавать программы на этом языке. Для практиче&
ской работы необходимо использовать ассемблеры фирмы Microsoft, например MASM615
или другие, но при этом необходимо будет учитывать особенности этих ассемблеров. Хотя
во всех ассемблерах используется один и тот же базовый набор команд процессора, отличия
кроются в директивах, т.е. командах, которые исполняются самим ассемблером.
В книге в основном рассматривается 32&разрядный режим работы, т.е. используется
такой режим ассемблера, который позволяет обращаться к процедурам прикладного ин&
терфейса Windows. Приведены также некоторые сведения, специфичные только для 16&раз&
рядного режима, например, описание работы прерываний и понятие о DOS.
Программирование на языке ассемблера является низкоуровневым и позволяет обра&
щаться непосредственно к отдельным устройствам, поэтому данный язык более всего под&
ходит для разработки драйверов и таких программ, которым необходимо максимальное бы&
стродействие и код, учитывающий особенности работы отдельных устройств компьютера.
Книга является введением в ассемблер, поэтому в ней не отражены более расширен&
ные возможности языка ассемблера и последние команды, разработанные для новейших
версий процессоров. Такая задача и не ставилась, поскольку описать полностью возмож&
ности ассемблера невозможно в одной, даже очень объемной книге.
С целью сокращения объема книги в нее не включены полные тексты программ и много&
численные примеры &&&& такие программы можно в изобилии найти на специализированных
Internet&сайтах. Приведенные в книге примеры служат только для иллюстрации отдель&
ных тем и команд.
От издательства “Диалектика”
Вы, читатель этой книги, и есть главный ее критик. Мы ценим ваше мнение и хотим
знать, что было сделано нами правильно, что можно было сделать лучше и что еще вы хо&
тели бы увидеть изданным нами. Нам интересны любые ваши замечания в наш адрес.
Мы ждем ваших комментариев и надеемся на них. Вы можете прислать нам бумажное или
электронное письмо либо просто посетить наш Web&сервер и оставить свои замечания там.
Одним словом, любым удобным для вас способом дайте нам знать, нравится ли вам эта книга,
а также выскажите свое мнение о том, как сделать наши книги более интересными для вас.
Отправляя письмо или сообщение, не забудьте указать название книги и ее авторов, а так&
же свой обратный адрес. Мы внимательно ознакомимся с вашим мнением и обязательно
учтем его при отборе и подготовке к изданию новых книг.
Наши электронные адреса:
E&mail: info@dialektika.com
WWW: http://www.dialektika.com
Стр. 17
Глава 1
Предисловие
Знание языка ассемблера поможет программисту не только научиться программиро&
вать, но также лучше понять взаимодействие языков высокого уровня с аппаратным
обеспечением, что в конечном итоге приводит к общему пониманию всего процесса
выполнения программы на компьютере и способствует более осмысленному использова&
нию конструкций языка высокого уровня. Язык ассемблера помогает раскрыть все секреты
аппаратного и программного обеспечения, с его помощью можно получить представле&
ние о том, как аппаратная часть взаимодействует с операционной системой и как при&
кладные программы обращаются к операционной системе. Язык ассемблера &&&& это язык
низкого уровня, в котором каждая команда непосредственно интерпретируется в коман&
ду процессора. Поэтому изучение языка ассемблера позволит понять все нюансы обра&
ботки информации в процессоре и поможет программисту в управлении процессором
и операционной системой, а также получении прямого доступа к аппаратуре.
Язык ассемблера незаменим при оптимизации критических блоков в прикладных
программах с целью повышения их быстродействия. Например, многие фрагменты ис&
ходных кодов языка Delphi написаны именно на языке ассемблера, что и определяет бы&
стродействие программ, разработанных на языке Delphi.
Язык ассемблера используется также при изучении курса аппаратного обеспечения
компьютера и операционных систем, когда есть возможность проводить эксперимен&
тальную работу.
Возможно, вам придется создавать обслуживающие программы (утилиты) или писать
драйверы для отдельных устройств. Во многих случаях это невозможно реализовать на
языках высокого уровня и только язык ассемблера позволит это сделать.
Стр. 18
отсутствует. 19
системе MS&DOS (или в режиме эмуляции MS&DOS в системах Windows), либо непосредст&
венно в Windows. Наиболее популярны для семейства процессоров Intel ассемблеры MASM
(Microsoft Assembler) и TASM (Borland Turbo Assembler). Язык ассемблера называется язы&
ком низкого уровня потому, что он очень близок к машинному языку по своей структуре
и выполняемым функциям. Каждая команда языка ассемблера имеет взаимно однозначное
соответствие с машинными командами, что отличает его от языков высокого уровня, для
которых характерно то, что один оператор транслируется во множество машинных команд.
Глава 1. Предисловие 19
Стр. 19
блок данных из файла, операционная система вызывает подпрограмму драйвера для уст&
ройства считывания.
Программируя на языке ассемблера, необходимо абсолютно точно представлять рас&
пределение данных, иначе ошибки неизбежны. Языки высокого уровня скрывают от про&
граммиста специфические детали для удобства использования и получения переносимого
кода. С другой стороны, используя язык ассемблера, можно учитывать специфику отдель&
ных устройств и почти не иметь ограничений при реализации интерфейса или драйвера.
Главный недостаток языка ассемблера состоит в том, что написанная для одного типа
компьютеров программа не может быть перекомпилирована и использована на других
типах компьютеров. Для каждого семейства процессоров используется свой язык ассемб&
лера со своим синтаксисом &&&& это следствие того, что язык ассемблера очень близок ма&
шинным командам и архитектуре процессора. Для того чтобы создаваемая программа
могла работать на различных типах компьютеров, необходимо использовать языки высо&
кого уровня, такие как С++, C#, Delphi или Java.
Машинный язык
Компьютер не воспринимает непосредственно команды на языке ассемблера, он по&
нимает только машинные команды. Машинные команды состоят из чисел, которые ин&
терпретируются процессором. Процессор обычно имеет встроенный интерпретатор,
представляющий микропрограмму и преобразовывающий машинные команды непосред&
ственно в управляющие сигналы.
Машинный язык включает примитивные операторы, которые могут исполнить про&
стые арифметические действия или переместить данные. Система команд процессора со&
стоит из машинных команд, которые он может исполнить. Машинный язык для семей&
ства процессоров Intel в своей основе имеет систему команд процессора 8086, которые
могут выполняться процессорами всех последующих модификаций. Такая концепция
называется совместимостью сверху вниз.
По мере совершенствования процессоров появляются новые команды, которые уже
не могут использоваться при написании программ для старых моделей. Такие команды
необходимо использовать для повышения скорости работы процессора и разработки
программ, учитывающих специфику современных аппаратных устройств.
Конечно, можно написать целую программу на машинном языке, вводя в память дво&
ичные числа машинных команд и заставляя процессор исполнять их. Но человеческий
мозг не приспособлен для запоминания большого количества чисел и поэтому вместо
ввода чисел используют мнемонически понятный набор команд — язык ассемблера, ко&
торый преобразует мнемонические коды в машинные команды.
20 Глава 1. Предисловие
Стр. 20
Глава 2
Архитектура компьютера
В этой главе...
Современные процессоры
Устройство компьютера
Операционные системы и память
Ввод&вывод
Резюме
Контрольные вопросы
Стр. 21
исключало их взаимное влияние. Операционная система DOS поддерживала работу
только в реальном режиме, но надстройки, подобные Microsoft Windows, уже могли ис&
пользовать защищенный режим.
В 1985 году Intel представила процессор 80386 с 32&разрядными регистрами, который мог
работать с 32&разрядной внешней шиной данных. Шина данных также стала 32&разряд&
ной, что позволило адресовать 4 Гбайта памяти. Появилась торговая марка Intel386. Бо&
лее дешевый процессор 80386&SX имел 16&разрядную шину данных.
Процессор 80386 мог работать в трех режимах: реальном, защищенном и виртуальном.
Это позволяло на одном процессоре запускать несколько программ реального режима
как отдельные ‘‘виртуальные машины’’. Другими словами, стандартные приложения
DOS могли работать одновременно, причем считалось, что каждое из них имеет доступ
ко всей памяти объемом в 1 Мбайт.
Благодаря схеме адресации памяти, названной виртуальной, вся используемая процес&
сором память могла превышать физический размер памяти компьютера. Процессор авто&
матически перемещал содержимое временно не используемых участков памяти на жесткий
диск и предоставлял эту память другим программам. Именно этот процессор помог опера&
ционной системе Microsoft Windows 3.0 достичь грандиозной популярности. Операционная
система Windows предоставляла пользователям возможность работать как в защищенном,
так и в виртуальном режимах. В виртуальном режиме в отличие от операционной системы
DOS можно было запускать несколько больших приложений одновременно.
Семейство процессоров Intel486 включало в себя процессоры 486DX, 486DX2 и 486SX.
В архитектуре процессора были использованы элементы высокопроизводительных про&
цессоров RISC. Это связано с тем, что изначально применяемая с процессорами Intel сис&
тема команд была довольно неудобной для обработки в процессоре, так как имела слож&
ную структуру, а отдельные команды имели разную длину (от одного до шестнадцати байт).
Но отказаться от этой системы команд Intel уже не могла, и поэтому был использован
блок преобразования в более удобные команды RISC. Последовательность выполнения
команд была изменена, режимы декодирования и исполнения могли работать одновре&
менно, что позволяло выполнять многие команды всего за один такт. Это была первая
серия процессоров, у которых блок вычислений с плавающей запятой (математический
сопроцессор) находился с ядром процессора на одном кристалле, что и обеспечило значи&
тельный прирост производительности. Процессор Intel486 имел внутреннюю кэш&память
первого уровня объемом 8 Кбайт, в которой хранились последние используемые коман&
ды. К ним обеспечивался очень быстрый доступ. В более дешевом Intel486SX был отклю&
чен блок вычислений с плавающей запятой.
Дальнейшим шагом Intel был процессор Pentium. Согласно проведенным тестам, про&
изводительность процессора Pentium с тактовой частотой 90 МГц почти вдвое превышала
производительность процессоров Intel486. Pentium мог выполнять больше одной команды
за такт, потому что в нем использовалась суперскалярная архитектура с двумя конвейерами
для команд. Другими словами, одновременно декодироваться и выполняться могли сразу
две команды. Конвейеры были надежным и испытанным способом повышения произво&
дительности, так как они уже давно использовались в процессорах архитектуры RISC для
рабочих и графических станций. Способствовали успеху Pentium и такие особенности:
встроенная кэш&память для команд и данных объемом по 8 Кбайт (486 имел толь&
ко одну кэш&память);
улучшенный блок вычислений с плавающей запятой;
предсказание ветвлений позволяло процессору Pentium анализировать последователь&
ность команд. Когда встречались команды условия (например, цикл или команда IF),
Стр. 22
отсутствует. 23
Ядро процессора
Не будем вдаваться в тонкости построения процессоров, хотя эта тема и интересна сама
по себе. Для программиста важно знать только общий принцип работы процессора и те его
элементы, к которым можно обратиться в процессе написания программы. На рис. 2.1 по&
казан блок управления, который распределяет задания и выбирает данные из памяти, ариф&
метико&логический блок, где непосредственно и происходят вычисления, и регистры, в кото&
рых хранятся все необходимые данные для оперативного выполнения очередной команды.
Стр. 23
рассчитывается адрес;
выбираются операнды из памяти.
Исполнение команд:
выполняются необходимые расчеты;
сохраняются результаты в памяти или регистрах;
устанавливаются необходимые состояния флагов.
Подобно системной шине, в процессоре существует внутренняя шина, которая пред&
ставляет собой множество параллельных проводников, связывающих отдельные блоки
процессора для обмена данных между ними. Если данные должны быть считаны из
внешней памяти, блок управления рассчитывает их адрес и устанавливает его на внеш&
ней шине адреса, являющейся частью системной шины. Контроллер памяти считывает
адрес с шины адреса и размещает затребованные данные на шине данных, также входящей
в состав системной шины. Затем он устанавливает сигнал готовности на одной из линий
контрольных сигналов. Процессор проверяет наличие сигнала готовности на этой линии
и после его появления считывает данные во внутреннее устройство хранения. Обращение
к внешней памяти &&&& самый длительный процесс относительно остальных этапов обра&
ботки данных в процессоре. Системная шина и память уже давно стали узким местом при
повышении общей производительности компьютера. В первых процессорах даже вводи&
ли специальные холостые циклы процессора при ожидании данных из внешней памяти.
В настоящее время эти проблемы решают за счет усложнения конструкции компьютера
и процессора, устанавливая более быстродействующую и, соответственно, более дорогую
внешнюю память поближе к ядру процессора. Емкость такой памяти не может быть
очень большой, так как это грозит значительным повышением стоимости компьютера,
поэтому такая память является буферной между основной памятью и процессором, со&
храняя только небольшое количество команд, необходимых процессору для оперативной
работы. Это позволяет значительно снизить общее время обращения к памяти и поднять
производительность компьютера. Такую память называют кэш%памятью. Разработчики
могут установить несколько уровней кэш&памяти, оптимизируя стоимость и производи&
тельность компьютера, но самыми быстродействующими участками памяти являются
регистры, которые расположены непосредственно на ядре процессора.
Регистры &&&& это участки высокоскоростной памяти для хранения данных в процессо&
ре. Они непосредственно подключены к блоку управления и арифметико&логическому
устройству, поэтому доступ к регистрам из этих блоков происходит значительно быстрее,
чем доступ к внешней памяти. Команды, использующие только обращение к регистрам,
выполняются очень быстро, в отличие от команд, требующих получения операндов из
внешней памяти, что необходимо учитывать при программировании, если программа
должна работать максимально быстро.
Каждая отдельная операция, которая выполняется внутри процессора, должна быть
синхронизирована с работой остальных устройств. Отдельное устройство синхронизации
вырабатывает все тактовые импульсы, необходимые для работы процессора. Тактовая
частота у современных процессоров может достигать нескольких гигагерц.
Программируемые регистры
Программист может не знать все детали функционирования процессора, но понимать
назначение и роль регистров в процессе обработки информации он обязан, так как
именно регистры играют основную роль при обработке отдельных данных и выполнении
Стр. 24
отсутствует. 25
Стр. 25
31 15 0 Номера битов для 32разрядных регистров
Регистры данных
EAX AH AL Аккумулятор
EBX BH BL Базовый регистр
EDX CH CL Счетчик
ECX DH DL Регистр данных
Регистры сегментов
CS Регистр сегмента команд
DS Регистр сегмента данных
SS Регистр сегмента стека
ES Регистр дополнительного сегмента
FS Регистр дополнительного сегмента
GS Регистр дополнительного сегмента
79 Регистры сопроцессора 0
ST(0)
ST(1)
ST(2)
ST(3)
ST(4)
ST(5)
ST(6)
ST(7)
Стр. 26
отсутствует. 27
Стр. 27
Флаг прерывания IF устанавливается, если возникает системное прерывание. Сис&
темные прерывания могут быть произведены различными устройствами, например кла&
виатурой, драйверами дисков или системным таймером. В некоторых программах иногда
необходимо запретить выполнение прерываний на период выполнения критичных по вре&
мени процедур. Флаг считается установленным, если соответствующий бит равен 1, и сбро&
шенным, если бит равен 0. Состояние флага изменяется командами CLI и STI.
Флаг трассировки TF заставляет процессор останавливаться после выполнения каждой
команды, что обычно используется при пошаговой отладке программы. Когда флаг уста&
новлен, программа отладки позволяет программисту последовательно просматривать выпол&
нение команд (трассировка). Флаг установлен, если бит равен 1, и сброшен при нулевом бите.
Современные процессоры
Несмотря на то что система команд первых процессоров Intel в своей основе не изме&
нялась до настоящего времени и идеология обработки команд также не претерпела суще&
ственных перестроек, тем не менее производительность современных процессоров зна&
чительно возросла и обычный настольный компьютер сейчас имеет такую же
производительность, какую десяток лет назад имели только отдельные суперкомпьюте&
ры. Росту производительности работы процессора способствовало то, что разработчики
предпринимали усилия по повышению скорости обработки данных сразу по нескольким
направлениям. Во&первых, улучшение технологического процесса производства микросхем
Стр. 28
отсутствует. 29
32*разрядные процессоры
Все процессоры Intel, начиная с 80386, имеют 32&разрядные регистры (см. рис. 2.2). Это
означает, что все программы, рассчитанные на эти процессоры, могут оперировать 32&
разрядными данными и использовать регистры индексов для адресации 4 Гбайт памяти.
Для работы с 32&разрядным форматом были добавлены новые команды.
Регистры сегментов остались 16&разрядными, но были добавлены два новых регистра &&&&
FS и GS. Не получили условного обозначения и верхние половины 32&разрядных регистров.
Для того чтобы разместить значение в верхней половине регистра, необходимо сначала
разместить это значение в нижней половине (например AX) и применить команду сдвига.
Немного подробнее рассмотрим регистры состояния и управления, так как в послед&
нее время в основном используются 32&разрядные процессоры, содержащие, соответст&
венно, 32&разрядные регистры флагов. При программировании нужно будет обращаться
именно к этим флагам.
Стр. 29
Регистры состояния и управления 32разрядного процессора
В 32разрядный процессор включены несколько регистров, которые постоянно со
держат информацию о состоянии как самого процессора, так и программы, команды ко
торой в данный момент загружены на конвейер.
К этим регистрам относятся:
регистр флагов EFLAGS;
регистр указателя команды EIP.
Используя эти регистры, можно получать информацию о результатах выполнения ко
манд и влиять на состояние самого процессора. Рассмотрим подробнее назначение и со
держимое этих регистров.
Регистр флагов EFLAGS
Регистр флагов EFLAGS содержит 32 разряда. Отдельные биты данного регистра име
ют определенное функциональное назначение и называются флагами. Младшая часть
этого регистра полностью аналогична регистру FLAGS для процессоров 8086. На рис. 2.3
показано содержимое регистра EFLAGS.
Флаг переноса
Флаг четности
Флаг вспомогательного переноса
Флаг нуля
Флаг знака
Флаг направления
Флаг переполнения
Флаг привелигерованности вводавывода
Флаг вложенности задач
31 ... 21 20 11 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 ... ID VIP VIF AC VM RF 0 NT IOPL OF DF IF TF SF ZF 0 AF 0 PF 1 CF
Флаг трассировки
Флаг прерывания
Флаг возобновления
Флаг виртуального 8086
Флаг контроля выравнивания
Флаг виртуального прерывания
Флаг отложенного прерывания
Флаг идентификации процессора
Стр. 30
отсутствует. 31
Стр. 31
Окончание табл. 2.1
Флаг Описание Бит Состояние и назначение
VM Флаг режима 17 Признак работы процессора в режиме виртуального 8086:
виртуального 8086 1 &&&& процессор работает в режиме виртуального 8086;
0 &&&& процессор работает в реальном или защищенном режиме
АС Флаг контроля 18 Предназначен для разрешения контроля выравнивания при
выравнивания обращениях к памяти. Используется совместно с битом AM
в системном регистре CR0. К примеру, Pentium разрешает размещать
команды и данные с любого адреса. Если требуется контролировать
выравнивание данных и команд по адресам, кратным 2 или 4,
то установка этих битов приведет к тому, что все обращения по
некратным адресам будут генерировать исключительную ситуацию
VIF Флаг виртуального 19 При определенных условиях (одно из которых &&&& работа процессора
прерывания в V&режиме) является аналогом флага IF. Флаг VIF используется
совместно с флагом VIP. Флаг появился в процессоре Pentium
VIP Флаг отложенного 20 Устанавливается в 1 для индикации отложенного прерывания.
виртуального Используется при работе в V&режиме совместно с флагом VIF.
прерывания Флаг появился в процессоре Pentium
ID Флаг 21 Используется для того, чтобы показать факт поддержки процессором
идентификации команды CPUID. Если программа может установить или сбросить
этот флаг, то это означает, что данная модель процессора
поддерживает инструкцию CPUID
64*разрядные процессоры
Новое семейство процессоров Intel &&&& Itanium &&&& рассчитано на использование в вы&
сокопроизводительных серверах. Для того чтобы обеспечить резкое повышение произво&
дительности, необходимой в серверах, процессоры Itanium были построены по новому
архитектурному принципу, обозначенному как явно параллельные выполнения команд
(EPIC &&&& Explicitly Parallel Instruction Computing). Система команд дополнена новыми
командами для 64&разрядных процессоров, причем совместимость с системой команд
для 32&разрядных процессоров сохранена. Применение 32&разрядных команд также по&
зволяет использовать преимущества новой архитектуры процессора и получать более бы&
стродействующие программы.
Процессоры AMD
Фирма AMD выпускает несколько семейств процессоров, которые программно
совместимы с семейством х86 и имеют логотип, указывающий на совместимость с Windows.
Однако их необходимо устанавливать только в те системные платы, в описании которых
есть явное указание на их поддержку. В противном случае возможны сбои в работе.
Стр. 32
отсутствует. 33
Устройство компьютера
Для прикладного программиста необязательно глубоко знать архитектуру компьюте&
ра, но знание базовых принципов работы компьютера и взаимодействие его основных
частей необходимо. Без таких знаний невозможно написать даже простейшую програм&
му, для чего потребуется организовать ввод и вывод информации, т.е. необходимо обра&
щаться к периферийным устройствам, таким как клавиатура и монитор. А поскольку без
аппаратных устройств не обойтись, надо иметь представление о том, как они работают,
и что нужно для того, чтобы данные обрабатывались именно так, как они должны обра&
батываться. Устройство компьютера описано в следующих разделах.
Стр. 33
Внутренние компоненты
Системная (материнская) плата &&&& основной компонент любого компьютера. Имен&
но на этой плате располагаются процессор, системный набор микросхем для обслужива&
ния процессора, системная память, соединители ввода&вывода, разъемы для подключения
питания и разъемы расширения. Все компоненты соединяются друг с другом при помо&
щи системной шины, которые упрощенно можно представить как ряд параллельных
проводников, вытравленных в проводящем слое платы и соединяющих отдельные эле&
менты платы. Однако это очень упрощенное объяснение, так как современные шины
представляют собой помимо проводников еще и управляющие микросхемы. И если в пер&
вых компьютерах можно было рассматривать системную шину как ряд параллельных на&
прямую подключенных к процессору проводников, где всеми сигналами, проходящими
по шине, управляет процессор, то к современным системным платам это не имеет ника&
кого отношения. Современную системную шину необходимо рассматривать как отдель&
ное устройство, которое может работать независимо от процессора, хотя в любом случае
общее управление всей системой осуществляет процессор, и только процессор решает,
в каком режиме будет работать отдельное устройство.
Системные платы, независимо от компании&производителя, имеют стандартный набор
функций, отличаясь лишь производительностью и возможностями модификации. Совре&
менные платы обязательно имеют базовый набор соединителей, который включает в себя:
панельку для установки процессора (она имеет различные параметры в зависимо&
сти от типа процессора);
разъемы для установки системной памяти (модули памяти SDRAM, DDRAM или
DDR);
разъем для микросхемы BIOS (часто возникает необходимость в модернизации BIOS,
которая раньше производилась путем смены микросхемы ROM; в настоящее время
все микросхемы BIOS можно перепрограммировать электрически и обычно эти
микросхемы впаяны в плату);
соединители для подключения гибких и жестких дисков, а также CD&ROM приводов;
стандартные соединители для подключения клавиатуры, мышки, джойстика, ви&
деоустройств, принтера и устройств связи.
Шинная архитектура
Как уже отмечалось, обмен информацией между отдельными устройствами на сис&
темной плате происходит через шину. В современном компьютере шина представляет со&
бой отдельный распределительный узел, к которому подключаются все (как внутренние,
так и внешние) устройства. Во многом именно от грамотного построения шины зависит
производительность компьютера. Шинная архитектура реализуется с помощью специ&
ального набора микросхем, называемого системным набором.
В первых компьютерах IBM PC использовалась 8&разрядная шина для передачи дан&
ных между процессором и другими компонентами компьютера. Скорость передачи состав&
ляла около одного миллиона битов в секунду, что позволяло процессору 8088 работать
без задержек. Однако с увеличением производительности процессоров такой скорости
для работы шины оказалось недостаточно.
Шина PC AT, представленная IBM для работы с процессором Intel 80286, имела
16 разрядов для передачи данных и 24 разряда для передачи адреса. С увеличением такто&
вой частоты процессора увеличилась и частота работы шины до 8 МГц.
Стр. 34
отсутствует. 35
В настоящее время широко распространена шина PCI, которая была разработана в 1992 году
компанией Intel для совместной работы с процессорами Pentium, требующих высокой ско&
рости передачи данных. Эта шина до сих пор остается доминирующей во всех системах, ис&
пользующих процессоры Intel. Спецификация шины учитывает особенности как 32&раз&
рядных, так и на 64&разрядных системных плат. Диапазон тактовой частоты работы шины
может быть в пределах 25&&1000 МГц, обеспечивая скорость передачи до 500 Мбит в секун&
ду. Во всех системных платах используется мост для перехода от внутренней 64&разрядной
шины процессора к внешней 64&разрядной шине. На смену шине PCI приходит шина PCI
Express, характеристики которой значительно превышают возможности шины PCI и кото&
рая будет основной шиной компьютеров в течение нескольких ближайших лет.
Видеоадаптер и монитор
Видеоадаптер рассчитан на совместную работу с дисплеями по спецификации IBM
и состоит из двух основных частей: видеоконтроллера и видеопамяти. Видеоадаптер мо&
жет быть в виде отдельной платы, вставляемой в разъем расширения, или может быть
расположен непосредственно на системной плате. Здесь необходимо уточнить некоторые
терминологические тонкости. Видеоадаптером можно считать простую графическую
плату, которая рассчитана на воспроизведение как двухмерных, так и трехмерных изо&
бражений. Однако при создании трехмерного изображения графическая плата не может
обойтись без процессора, загружая его расчетами каркаса изображения и занимая значи&
тельную часть процессорного времени. Более совершенные графические платы называют
графическими ускорителями. Такие платы почти не обращаются к процессору при рас&
чете трехмерной сцены, где они учитывают все источники света, определенные в сцене,
и создают очень реалистичные световые и цветовые переходы.
Все символы и графические изображения перед отображением на дисплее должны
быть записаны в видеопамять, откуда они и посылаются на дисплей видеоконтроллером.
Видеоконтроллер, или видеопроцессор, &&&& это процессор, который берет на себя всю ра&
боту с видеоустройствами, освобождая от этих обязанностей центральный процессор.
Обычно видеоадаптер содержит не менее 4 Мбайт видеопамяти и оптимизирован для ра&
боты с двухмерными или трехмерными графическими изображениями. Современные ви&
деоадаптеры рассчитаны на работу не менее чем с 16 млн цветов и обеспечивают разре&
шение экрана не менее 1024×768. Эти характеристики постоянно улучшаются.
Что же касается мониторов, то для получения изображения в них используется техно&
логия, называемая растровым сканированием. Электронный луч создает на экране све&
тящиеся точки &&&& пиксели. Электронная пушка управляет лучом, постоянно передвигая
его слева направо и сверху вниз, проходя через все точки экрана. Достигнув конца самой
нижней строки, электронный луч перескакивает обратно в верхний левый угол, завершая
один цикл, называемый кадром. Для уменьшения мерцания экрана используют метод
чересстрочной развертки, когда луч пробегает за одну половину кадра только четные
строки, а за вторую &&&& нечетные, хотя в современных компьютерах этот метод почти не
используется и развертка всегда является прогрессивной, или последовательной.
Современные плоские мониторы используют другой принцип отображения инфор&
мации на экране. Мельчайшая сетка экрана состоит из жидких кристаллов, которые под
воздействием электрических сигналов могут пропускать или задерживать свет. Используя
это свойство жидких кристаллов, можно применить решетку из красных, синих и желтых
ячеек для создания цветного изображения.
Есть и другие принципы создания изображения на плоском экране, но в данном слу&
чае это не принципиально. Поговорим о характеристиках монитора.
Стр. 35
Качество монитора определяют несколько факторов. Это величина шага между распо&
ложенными рядом точками. В современных мониторах эта величина не превышает 0,26 мил&
лиметра и чем она меньше, тем лучше. Также важны скорость вертикальной и горизонтальной
разверток. Горизонтальная развертка определяется временем пробега луча по одной линии
(от левого края до правого), а скорость вертикальной развертки определяет полное время
пробега всех линий. Если скорость вертикальной развертки будет меньше, чем 70 кадров в се&
кунду, то изображение на экране будет мерцать при смене кадра, что вредно для глаз.
Скорость вертикальной развертки и разрешение экрана связаны обратно&пропорцио&
нальной зависимостью. Если увеличить разрешение, то может случиться так, что монитор
или видеоконтроллер не смогут поддерживать высокую скорость вертикальной развертки.
Разрешение экрана можно изменять программно, но оно ограничено возможностями
видеоконтроллера и объемом видеопамяти. Разрешение определяется количеством пик&
селей, размещаемых по горизонтали и вертикали. Стандартным значением для режима
VGA будет 640 пикселей по горизонтали и 480 по вертикали (640×480), для режима super
VGA &&&& 800×600, для extended VGA &&&& 1024×768, 1152×864, 1280×1024 и 1600×1400.
Количество одновременно отображаемых цветов может быть от 256 до 16 млн.
Память
В компьютерах используются три основных типа памяти: динамическая RAM, стати%
ческая RAM и видео RAM. Динамическая память должна обновляться не меньше, чем раз
в миллисекунду, иначе она потеряет свое содержимое, так как электроны постоянно
‘‘просачиваются’’ из созданного для них кармана. Статическая память не требует обнов&
ления. Видеопамять используется только для хранения данных, предназначенных для
отображения на дисплее. CMOS RAM предназначена для хранения информации о сис&
темных установках и для сохранения в ней информации при выключенном компьютере
используется специальная батарейка.
Основная используемая память является динамической памятью, так как она самая деше&
вая. Ее часто называют системной, или оперативной памятью. В некоторых системах исполь&
зуют память с проверкой ошибок. Такая память способна определять ошибки в нескольких
битах и корректировать ошибки в одном бите каждого байта, но это приводит к некоторому
удорожанию системы и в настоящее время в обычных компьютерах динамическая память
не используется. Это связано также и с тем, что сбои при передаче достаточно редки.
Обычно процессор работает значительно быстрее, чем оперативная память, поэтому
разработчикам приходится усложнять конструкцию для того, чтобы согласовать работу
процессора и памяти без потери производительности. Частично эту проблему решает вы&
сокоскоростная кэш&память первого уровня, включенная в состав процессоров Intel на&
чиная с модели 80386. Для более лучшего согласования используют кэш&память второго
уровня, которая подключается непосредственно к шине процессора. Ее объем обычно
составляет не менее 512 Кбайт. В этой памяти сохраняются недавно используемые ко&
манды и данные. Например, если встречаются циклические операции, то процессору со&
всем не придется обращаться к медленной основной оперативной памяти.
Стр. 36
отсутствует. 37
Платы расширения
Платы расширения вставляются в разъемы расширения, размещенные на системной
или специальной разработанной плате, конструкция которой позволяет платам занимать
меньше места. На платах расширения обычно размещают:
графический ускоритель;
звуковую плату с портом для игровых устройств;
синтезатор звука;
контроллер SCSI;
контроллер ленточного накопителя;
устройство видеозахвата;
дополнительные параллельные и последовательные порты.
В полноразмерных конструкциях типа ‘‘башня’’ может быть от пяти до восьми разъе&
мов расширения и несколько отсеков для приводов, что позволяет подключать дополни&
тельные устройства. Массовые настольные компьютеры имеют меньше разъемов расши&
рения, так как часть функций реализована непосредственно на системной плате или
встроена в системный набор микросхем. Такое решение позволяет разрабатывать не&
большие и недорогие компьютеры.
Процессоры поддержки
Почти все системные платы имеют в своем составе системный набор микросхем,
включающий в себя две или три микросхемы с процессорами, которые выполняют от&
дельные функции (некоторые из них перечислены ниже).
Контроллер прямого доступа к памяти соединяет внешние устройства непосредст&
венно с памятью без использования центрального процессора.
Контроллер прерываний передает запросы на обслуживание от аппаратуры в цен&
тральный процессор.
Счетчик времени управляет системными часами, которые обновляются с частотой
18,2 раза в секунду.
Контроллер моста для перехода от локальной шины к шине PCI или PCIE.
Контроллер системной памяти и кэш&памяти.
Контроллер для работы с клавиатурой и мышкой.
BIOS
ROM BIOS &&&& это память только для чтения, которая содержит программы и данные,
не подлежащие стиранию или изменению. ROM BIOS можно рассматривать как часть
операционной системы. Когда компьютер включается, то процессор обращается непо&
средственно к BIOS, так как только в ней есть рабочие программы, все остальное еще
Стр. 37
предстоит загрузить в оперативную память с жесткого диска. Это программы контроля
аппаратной части и программы загрузки части операционной системы из загрузочного
сектора диска. Большинство систем копируют BIOS в более быстродействующую сис&
темную память на момент загрузки.
Наиболее важная особенность ROM BIOS &&&& возможность проверять наличие аппа&
ратных устройств и готовить операционную систему для работы с устройствами в соот&
ветствии с информацией, полученной от них. Никакой дополнительной настройки уст&
ройств при этом не требуется, поэтому такой режим назвали ‘‘подключи и работай’’.
Современные операционные системы не всегда доверяют созданным BIOS установкам
и сами производят конфигурацию отдельных или всех устройств.
Порты
Параллельные порты. Большинство принтеров подключаются к компьютеру через па&
раллельный порт. Слово ‘‘параллельный’’ означает, что 8 битов могут одновременно пе&
редаваться от компьютера к принтеру по восьми параллельным проводникам, но на не&
большие расстояния (обычно не более 10 метров). При этом данные могут передаваться
очень быстро. В компьютере имеется три параллельных порта: LPT1, LPT2 и LPT3, кото&
рые могут быть двунаправленными, позволяя компьютеру принимать информацию от
принтера о его состоянии.
Последовательные порты. Последовательные порты также называются интерфейсами
RS&232. Последовательные порты используются для подключения мышки, модема или
некоторых других устройств. Микросхема, которая управляет последовательными порта&
ми, обозначается как 16550 UART и может находиться на системной плате или плате
расширения.
В таких портах каждый бит посылается последовательно один за другим, что не спо&
собствует высокой скорости передачи, но зато становится возможной передача на большие
расстояния. Операционные системы поддерживают два последовательных порта COM1
и COM2, у которых разные типы соединителей: в одном 9 контактов, а в другом &&&& 25.
В связи с развитием технологической базы стало возможным обеспечивать большие
скорости передачи по последовательным портам. Поэтому в современных компьютерах
происходит замена параллельных портов на более удобные, позволяющие получить вы&
сокое быстродействие последовательные порты, такие как SATA или USB. Эти порты
можно использовать для подключения периферийных устройств любого типа.
Порты USB. Наиболее современные порты для ввода и вывода. Они обеспечивают
высокую скорость передачи данных и позволяют унифицировать ввод&вывод периферийных
устройств. В настоящее время наблюдается все более активный переход на порты USB,
которые в конечном итоге должны заменить все остальные порты. Порт USB помимо вы&
сокой скорости передачи предоставляет и ряд других преимуществ, среди которых удоб&
ство подключения и автоматическое распознавание вновь подключенного устройства.
Работа компьютера
Теперь, когда вы имеете общее представление о различных устройствах компьютера,
схематично рассмотрим совместную работу этих устройств. Более подробно работу от&
дельных устройств будем рассматривать в соответствующих разделах.
Итак, начнем с самого начала. В момент включения компьютера процессор обращается
к участку памяти, которую занимает BIOS, так для работы процессора нужна программа,
которую в начальный момент можно получить только из энергонезависимой памяти
BIOS. Оперативная память еще ничем не заполнена и в начальный момент неизвестно,
Стр. 38
отсутствует. 39
Стр. 39
система делает работу с компьютером такой простой и удобной, при этом она эффектив
но использует ресурсы компьютерной системы. Как и любая другая программа, операци
онная система состоит из команд, которые считывает процессор. Уникальностью этой
программы является ее назначение операционная система позволяет процессору ис
пользовать все доступные системные ресурсы и распределять время при исполнении
других программ. Однако для того, чтобы компьютер мог выполнять полезную работу,
операционная система должна периодически переключать процессор на выполнение при
кладных программ. Таким образом, операционная система передает управление другой
программе, чтобы процессор смог выполнить заданную пользователем работу, а затем во
зобновляет контроль над системой для подготовки процессора к следующей работе.
На рис. 2.4 показаны основные ресурсы, которыми управляет операционная система.
Память
Контроллер Устройство Принтер
Операционная вводавывода вводавывода Клавиатура
система Модем
Монитор
Контроллер Устройство USB
вводавывода вводавывода ...
Внешняя
память
Программы
пользователей Операционная
и данные система
Программы
Контроллер Устройство
вводавывода вводавывода Данные
Процессор
Стр. 40
отсутствует. 41
когда в Советский Союз начали поступать первые персональные компьютеры, они не мог&
ли отображать русские буквы, так как операционная система не имела соответствующих
шрифтов. Но очень скоро наши программисты ‘‘научили’’ компьютеры понимать русский
язык. Специальные программы&русификаторы встраивались в систему при инициализации
компьютера и позволяли отображать и вводить русские символы.
Здесь нет ничего удивительного, поскольку операционную систему можно изменять как
и любую другую программу. Но лучше этого не делать, так как, вероятнее всего, вы не смо&
жете улучшить то, что создавалось большими коллективами высококлассных программи&
стов. Тем более что исходные коды операционных систем закрыты и разработчики обыч&
но их никому не предоставляют. Исключение составляет операционная система Linux,
в поставку которой входят исходные коды.
Стр. 41
Эволюция операционных систем
В первых компьютерах в конце 40&х и до середины 50&х годов прошлого века про&
граммы непосредственно взаимодействовали с аппаратным обеспечением машины &&&&
операционных систем тогда еще не было. Эти компьютеры управлялись с пульта управ&
ления, состоящего из сигнальных ламп, тумблеров, устройства для ввода данных и прин&
тера. Программы в машинных кодах и данные загружались через устройство ввода дан&
ных (например, устройство ввода с перфокарт). Если из&за ошибки выполнение
программы прекращалось, о возникновении сбойной ситуации сообщали аварийные
сигнальные лампы. Чтобы определить причину ошибки, программист вручную должен
был проверить состояние регистров процессора и основной памяти. Если программа ус&
пешно завершала свою работу, ее выходные данные распечатывались на принтере. Такой
режим работы можно назвать последовательной обработкой данных. Это название отра&
жает тот факт, что пользовательские программы исполнялись на компьютере последователь&
но. Все это сопровождалось массой неудобств, а выполнение пользовательской программы
занимало слишком много времени. Для устранения этих недостатков были разработаны
различные системные инструментальные программы. К ним относятся библиотеки функ&
ций, редакторы связей, загрузчики, отладчики и драйверы ввода&вывода, существующие
в виде программного обеспечения, общедоступного для всех пользователей.
В дальнейшем для повышения эффективности работы была предложена концепция
пакетной операционной системы. Первые пакетные операционные системы для машин
типа IBM 701 были разработаны в середине 50&х годов компанией General Motors. Впо&
следствии эта концепция была усовершенствована и внедрена на машинах серии
IBM 704. В начале 60&х годов некоторые поставщики разработали пакетные операцион&
ные системы для своих компьютеров. Одна из примечательных систем того времени &&&&
IBSYS фирмы IBM, разработанная для компьютеров 7090/7094 и оказавшая значитель&
ное влияние на другие системы.
Главная идея, лежащая в основе простых пакетных схем обработки, состоит в исполь&
зовании программы, известной под названием монитор. Используя операционную сис&
тему такого типа, пользователь не имеет непосредственного доступа к машине. Вместо
этого он передает свое задание на перфокартах или магнитной ленте оператору компью&
тера, который собирает разные задания в пакеты и помещает их в устройство ввода дан&
ных. Так информация передается монитору. Каждая программа составлена таким образом,
что по завершении ее работы управление переходит к монитору, который автоматически
загружает следующую программу. Задания в пакетах выстраиваются в очередь и выпол&
няются без простоев максимально быстро. Кроме того, монитор помогает в подготовке
программы к исполнению. В каждое задание включаются простые команды языка управ&
ления заданиями. Это специальный тип языка программирования, используемый для
того, чтобы отдавать команды монитору. При этом монитор обеспечивал защиту памяти
и во время работы программа пользователя не могла внести изменения в область памяти,
в которой находился сам монитор. Монитор использовал таймер для снятия программ,
превысивших выделенное для них время работы; только он мог выполнять некоторые
привилегированные команды машинного уровня. Например, команды ввода&вывода
контролировались монитором, и если программе пользователя нужно было произвести
ввод&вывод, ей приходилось запрашивать для выполнения этих операций монитор. Но
в первых операционных системах отсутствовали прерывания, которые придают системе
большую гибкость при управлении ресурсами процессора.
Занимая часть ресурсов компьютера, монитор существенно повышал эффективность
его использования, что с учетом дорогостоящих компьютеров и дорогого процессорного
Стр. 42
отсутствует. 43
Стр. 43
Современные операционные системы
Операционные системы относят к числу самых сложных программных комплексов.
Это связано со стремлением разработчиков сделать системы максимально удовлетво&
ряющие требованиям удобства работы и эффективности, но при этом ОС не должны ут&
ратить способности к дальнейшему совершенствованию. В процессе развития операци&
онных систем были проведены исследования в нескольких основных направлениях.
Одной из главных концепций, лежащих в основе операционных систем, является
концепция процессов. Этот термин впервые был применен в 60&х годах и с тех пор широ&
ко используется. В разных операционных системах под процессом понимают несколько
отличающиеся действия операционных систем. Фактически процесс можно разделить на
три компонента:
выполняемая программа;
данные, нужные для ее работы (переменные, рабочее пространство, буферы и т.д.);
состояние программы.
Очень важен компонент состояния программы, который включает в себя всю инфор&
мацию, нужную операционной системе для управления процессом, и процессору &&&& для
его выполнения. Данные, характеризующие это состояние, включают в себя содержимое
различных регистров процессора, таких как программный счетчик и регистры данных.
Сюда же входит информация, используемая операционной системой (приоритет процес&
са и сведения о том, находится ли данный процесс в состоянии ожидания какого&то со&
бытия, связанного с вводом&выводом).
Таким образом, процесс реализуется в виде структуры данных. Он может выполнять&
ся или находиться в состоянии ожидания. Состояние процесса в каждый момент времени
заносится в специально отведенную область данных. Использование структуры позволя&
ет развивать мощные методы координации и взаимодействия процессов.
Четкое обоснование понятия процесса позволило решить проблемы, возникающие на
этапе развития операционных систем и связанных с распределением времени и синхро&
низацией.
Были разработаны системы групповой обработки нескольких программ, при этом
процессы могли запускаться в режиме разделения времени и транзакций одновременно.
В ответ на сигналы, сообщающие о завершении транзакций ввода&вывода, процессор пе&
реключается с одной программы на другую.
Следующим направлением развития являются системы разделения времени. Основ&
ная цель их разработки &&&& удовлетворение потребностей каждого пользователя при усло&
вии их одновременной работы на компьютере. В этих системах учитывается тот факт, что
пользователь реагирует на события намного медленнее, чем компьютер.
Еще одним важным направлением развития являются системы обработки транзакций
в реальном времени. При этом необходимо учитывать то, что большое число пользовате&
лей могут одновременно отправлять запросы в базу данных или вносить в нее изменения,
и при этом не должно быть большой задержки с ответом.
Прерывание стало важным инструментом, которое могли использовать системные
программисты уже на ранних стадиях развития многозадачных и многопользовательских
интерактивных систем. Выполнение любого задания может быть прервано при наступле&
нии определенного события, например завершения ввода&вывода. При этом процессор
сохраняет информацию о текущем состоянии программы и переключается на выполне&
ние программы обработки прерываний, которая выясняет причину прерывания, обраба&
тывает его, а затем возобновляет исполнение прерванной программы.
Стр. 44
отсутствует. 45
Управление памятью
Лучше всего потребности пользователя удовлетворяются вычислительной средой, под&
держивающей модульное программирование и гибкое использование данных. Эффек&
тивный контроль над размещением данных в оперативной памяти со стороны операци&
онной системы позволяет избавиться от многих проблем. При этом операционная
система должна следить за тем, чтобы ни один из независимых процессов не смог изме&
нить содержимое памяти, отведенное другому процессу. Программы должны динамически
размещаться в памяти в соответствии с определенными требованиями. Распределение па&
мяти должно происходить автоматически, независимо от программиста, хотя программист
и может управлять распределением. Таким образом, программист будет избавлен от необ&
ходимости следить за ограничениями памяти, а операционная система повышает эффек&
тивность работы вычислительной системы, выделяя заданиям только тот объем памяти,
который им необходим. Однако программист должен иметь возможность определять мо&
дули программы, а также динамически их создавать, уничтожать и изменять их размер.
При совместном использовании памяти на каждом ее иерархическом уровне одна
программа может обратиться к пространству памяти другой программы. Хотя такая воз&
можность необходима, она представляет угрозу целостности программ и самой операци&
онной системы. Операционная система должна обязательно следить за тем, каким обра&
зом отдельные пользователи осуществляют доступ к различным областям памяти.
Обычно операционные системы выполняют эти требования с помощью средств вир&
туальной памяти и файловой системы. Файловая система обеспечивает долгосрочное
хранение информации, помещаемой в именованные объекты, которые называются фай&
лами. Файл — это удобная для широкого использования структура данных, доступ к ко&
торой и ее защита осуществляются операционной системой.
Виртуальная память — это абстрактное понятие, позволяющее программистам рассмат&
ривать память с логической точки зрения, не заботясь о наличии физической памяти доста&
точного объема. Принципы работы с виртуальной памятью позволяли добиться того, чтобы
задания нескольких пользователей выполнялись параллельно и могли одновременно присут&
ствовать в основной памяти. При такой организации процессов задержка между их выполне&
нием отсутствует: как только один из процессов заносится на вспомогательное запоминающее
Стр. 45
устройство, считывается следующий процесс. Из&за различий в количестве памяти, которое
требуется для разных процессов, при переключении процессора с одного процесса на дру&
гой возникают трудности с компактным их размещением в основной памяти. Поэтому бы&
ли разработаны системы со страничной организацией памяти, при которой процесс разби&
вается на блоки фиксированного размера, или страницы. Обращение программы к слову
памяти происходит по виртуальному адресу, который состоит из номера страницы и смеще&
ния относительно ее начала. Страницы одного и того же процесса могут быть разбросаны по
всей основной памяти. Система разбивки на страницы обеспечивает динамическое соответст&
вие между виртуальным адресом, который использует программа, и реальным адресом основ&
ной памяти. При этом было исключено требование, чтобы все страницы процесса одновре&
менно находились в основной памяти; достаточно, чтобы все они хранились на диске. Во
время выполнения процесса только некоторые его страницы находятся в основной памяти.
Если программа обращается к странице, которая там отсутствует, то система управления
памятью обнаружит это и организует загрузку недостающих страниц.
Стр. 46
отсутствует. 47
Архитектура памяти
Физическая память, к которой микропроцессор имеет доступ по шине адреса (см. рис. 2.1),
называется оперативной памятью. Конструктивно память компьютера можно рассмат&
ривать как массив битов. Один бит может хранить значение 0 или 1 и для работы с ними
идеально подходят логические схемы. Однако работать с памятью на уровне битов край&
не неудобно, поэтому реально ОЗУ организовано как последовательность из восьми ячеек,
которую называют байтом. Один байт состоит из 8 бит. Каждому байту соответствует
свой уникальный адрес, называемый физическим адресом. Диапазон значений физиче&
ских адресов зависит от разрядности шины адреса микропроцессора. Для Intel486 и Pentium
Стр. 47
32
он находится в пределах от 0 до 2 &&1 (4 Гбайт). Для микропроцессоров семейства Р6 этот
36
диапазон шире &&&& от 0 до 2 &&1 (64 Гбайт).
Механизм управления памятью полностью аппаратный. Это означает, что в програм&
ме нельзя сформировать физический адрес памяти на адресной шине, необходимо учи&
тывать конструктивные особенности процессора. Такой механизм позволяет обеспечить:
компактность хранения адреса в машинной команде;
гибкость механизма адресации;
защиту адресных пространств задач в многозадачной системе;
поддержку виртуальной памяти.
Микропроцессор аппаратно поддерживает две модели использования оперативной
памяти.
Сегментированную модель. В этой модели программе выделяются непрерывные
области памяти (сегменты), а сама программа может обращаться только к данным,
которые находятся в этих сегментах.
Страничную модель. Ее можно рассматривать как надстройку над сегментирован&
ной моделью. В случае использования этой модели оперативная память рассмат&
ривается как совокупность блоков фиксированного размера (4 Кбайт). Основное
применение этой модели связано с организацией виртуальной памяти, что позво&
ляет операционной системе использовать для работы программ пространство па&
мяти большее, чем объем физической памяти. Для микропроцессоров Intel486
и Pentium размер возможной виртуальной памяти может достигать 4 Тбайт.
Особенности использования и реализации этих моделей зависят от режима работы
микропроцессора. В реальном режиме работал процессор Intel 8086, защищенный режим
позволяет максимально реализовать все архитектурные идеи, заложенные в модели мик&
ропроцессоров Intel, начиная с i80286. Программы, разработанные для Intel 8086, не мо&
гут функционировать в защищенном режиме. Одна из причин этого связана именно
с особенностями формирования физического адреса в защищенном режиме. Для того
чтобы использовать такие программы, предусмотрен режим виртуального 8086. Переход
в этот режим возможен, если микропроцессор уже находится в защищенном режиме. От&
личительной особенностью этого режима является возможность одновременной работы
нескольких программ, разработанных для Intel 8086. Несмотря на то что микропроцессор
находился в защищенном режиме, в режиме виртуального i8086 возможна работа про&
грамм реального режима. Дело в том, что процесс формирования физического адреса для
этих программ производится по правилам реального режима.
И последний новый режим работы &&&& режим системного управления. Этот режим впер&
вые появился в микропроцессоре Pentium. Он предоставляет операционной системе ме&
ханизм для выполнения машинно&зависимых функций, таких как перевод компьютера
в режим пониженного энергопотребления или выполнения действий по защите системы.
Для перехода в этот режим микропроцессор должен получить специальный сигнал от
усовершенствованного программируемого контроллера прерываний (APIC), при этом
сохраняется состояние вычислительной среды микропроцессора. Функционирование
микропроцессора в этом режиме подобно его работе в режиме реальных адресов.
Стр. 48
отсутствует. 49
Смещение
Сегментированная
+ Сегмент модель
памяти
Адрес реального
(Сегментный *4 режима
регистр)
Сегмент
Смещение
Сегментированная
+ Сегмент модель
памяти
Селектор защищенного
(Сегментный режима
регистр)
Таблица
дескрипторов Сегмент
Смещение
Плоская
+ модель
памяти
Селектор защищенного
(Сегментный режима
регистр)
Таблица
дескрипторов
Селектор
(Сегментный
регистр)
Стр. 49
ции памяти: сегментированная модель памяти реального режима, сегментированная мо&
дель памяти защищенного режима и сплошная модель памяти защищенного режима.
Для примера рассмотрим порядок формирования физического адреса в реальном ре&
жиме. Под физическим адресом понимается адрес памяти, выдаваемый на шину адреса
микропроцессора.
Формирование физического адреса в реальном режиме
В реальном режиме механизм адресации физической памяти имеет следующие харак&
теристики:
диапазон изменения физического адреса от 0 до 1 Мбайт. Эта величина определя&
ется тем, что шина адреса Intel 8086 имела 20 линий;
максимальный размер сегмента 64 Кбайт. Это объясняется 16&разрядной архитектурой
Intel 8086. Поскольку максимальное значение, которое могут содержать 16&разрядные
16
регистры, составляет 2 &&1, то это значение и определяет величину 64 Кбайт.
Для обращения к конкретному физическому адресу оперативной памяти необходимо
определить адрес начала сегмента и смещение внутри сегмента. Однако поскольку адрес
начала сегмента представляет собой всего лишь 16&битовое значение, помещенное в один
из сегментных регистров, то максимальное значение, которое при этом получается, соот&
16
ветствует 2 &&1. Если исходить из этого, то получается, что адрес начала сегмента может
быть только в диапазоне 0&&64 Кбайт от начала оперативной памяти. Но тогда как адресо&
вать остальную часть оперативной памяти вплоть до 1 Мбайт с учетом того, что размер
самого сегмента не превышает 64 Кбайт? Дело в том, что в сегментном регистре содержатся
только старшие 16 бит физического адреса начала сегмента, которые сдвигаются на 4 разря&
да влево для получения 20&битового адреса. Эта операция сдвига выполняется аппаратно
и для программного обеспечения абсолютно прозрачна. Получившееся 20&битовое значе&
ние и является настоящим физическим адресом, соответствующим началу сегмента. Что
касается второго компонента, участвующего в образовании физического адреса некоторого
объекта в памяти &&&& смещения, то оно представляет собой 16&битовое значение. Это значе&
ние может содержаться явно в команде либо косвенно в одном из регистров общего назна&
чения. В микропроцессоре эти две составляющие складываются на аппаратном уровне,
в результате чего получается физический адрес памяти размером 20 бит. Данный механизм
образования физического адреса позволяет сделать программное обеспечение перемещае&
мым, т.е. не зависящим от конкретных адресов загрузки его в оперативной памяти.
Разумеется, в такой организации памяти можно найти много недостатков, но на ран&
них этапах разработки процессоров подобная архитектура имела определенные преиму&
щества. Никто не предполагал, что для обычного компьютера может понадобиться опе&
ративная память в несколько гигабайт. Билл Гейтс при разработке операционной
системы DOS выделил для системы 640 Кбайт и при этом заявил, что он не представляет,
для чего может понадобиться такое огромное количество оперативной памяти. Появле&
ние защищенного режима вызвано желанием ввести в архитектуру процессора средства,
позволяющие избавиться от недостатков реального режима.
Ввод*вывод
Без получения и отображения информации в удобном виде вся работа компьютера не име&
ет смысла. Поэтому система ввода&вывода (BIOS) является одной из важнейших систем,
которая реализована как на самом низшем (аппаратном) уровне, так и на более высоких
уровнях. Например, вы можете написать команду для отображения данных на языке высокого
Стр. 50
отсутствует. 51
уровня, таком как С или Java. При выполнении такой программы произойдет обращение
к операционной системе в тот момент, когда возникнет необходимость отображения дан&
ных. Операционная система в свою очередь обратится к соответствующей функции базовой
системы ввода&вывода для вывода необходимых данных, так как только BIOS имеет воз&
можность произвести запись данных непосредственно в память графического адаптера. Та&
ким образом, взаимодействие с аппаратной частью производится только через BIOS.
Такой подход позволяет создавать переносимые программы, т.е. такие программы,
которые могут выполняться на различных типах компьютеров без переделки самих про&
грамм, поскольку взаимодействие с различным оборудованием производится через BIOS,
который настраивается именно на то оборудование, для которого он предназначен.
Необходимо добавить, что стандартный BIOS, который входит в состав любого ком&
пьютера, обычно ‘‘умеет’’ обращаться только к тем устройствам, которые входят в базо&
вый состав компьютера, и ничего ‘‘не знает’’ о новых устройствах, которые могут быть
дополнительно установлены в компьютер. Для работы с такими устройствами необходи&
мо расширить BIOS, т.е. дополнить его такими программами (драйверами), которые
смогут работать с новым оборудованием. Установка дополнительных драйверов произво&
дится в момент загрузки компьютера и современные операционные системы могут делать
это автоматически. Однако, к примеру, в DOS необходимо записать специальную коман&
ду в файл инициализации, такую как
DEVICE = CDROM.SYS
где файл CDROM.SYS является драйвером устройства считывания компакт&дисков.
В отличии от программ, написанных на языке высокого уровня, программы, напи&
санные на ассемблере, могут быть непереносимыми, &&&& возможно, они будут обращаться
непосредственно к аппаратной части (рис. 2.7).
Преимущество таких программ &&&& высокая скорость работы, так как программист
может не использовать стандартные подходы, которые из&за своей универсальности яв&
ляются довольно медленными, а реализовать уникальный и быстрый метод доступа.
Резюме
В данной главе кратко описаны технические средства компьютера, которые необхо&
димо знать программисту для того, чтобы писать эффективные программы. Понимание
архитектуры компьютера позволит непосредственно обращаться в отдельным блокам
и узлам компьютера и тем самым создавать очень быстро работающие программы, так как
будут исключены все промежуточные программные модули, обеспечивающие поддержку
всевозможных режимов и настроек, но при этом замедляющие работу программы.
Контрольные вопросы
1. Какова роль процессора в компьютерной системе?
2. Назовите основные компоненты компьютера, без которых невозможна работа
компьютера.
3. Как взаимосвязаны операционная система и аппаратная часть компьютера?
4. Как вы считаете, операционная система разрабатывается под определенную аппа&
ратную платформу или аппаратная часть разрабатывается для определенной опе&
рационной системы?
5. Почему операционные системы постоянно модифицируются?
Стр. 51
6. Зачем нужна оперативная память?
7. Что такое кэш&память и почему она необходима в компьютере?
8. Зачем нужны регистры в процессоре?
9. Назовите четыре регистра общего назначения.
10. Как обозначается старшая половина регистра CX? Сколько битов она может хранить?
11. Какое приоритетное назначение регистра ECX?
12. Почему доступ к оперативной памяти происходит значительно дольше по време&
ни, чем к регистрам?
13. В чем преимущество защищенного режима в процессорах? Для каких прикладных
программ необходим такой режим?
14. Можно ли в операционной системе DOS использовать защищенный режим?
15. Почему с изменением регистра AH одновременно изменяется регистр EAX?
16. В каком 16&разрядном регистре будет находиться верхняя часть результата пере&
множения чисел?
17. Какой регистр является указателем базы для всех выполняемых команд?
18. Какой регистр является указателем базы для стека?
19. Помимо указателя стека, с помощью каких других регистров можно вычислить пе&
ременную в стеке?
20. Назовите два регистра индексов.
21. Какие три флага называются флагами контроля?
22. Флаги состояния реагируют на дополнительный перенос, четность, перенос, пере&
полнение. Какие еще есть флаги состояния?
23. Какой флаг устанавливается, когда результат арифметической операции с числами
без знака становится слишком большим для размещения результата в регистре?
24. Какой флаг устанавливается, когда в результате арифметической или логической
операции возникает отрицательный результат?
25. Какой флаг отражает состояние одного бита результата операции?
Стр. 52
Глава 3
Введение
в язык ассемблера
В этой главе...
Представление данных
Основы языка ассемблера
Разработка программы на языке ассемблера
Работа в DOS под Windows NT
Инструментальные средства
Пример простой программы
Ассемблер Microsoft
Отладчик
Резюме
Контрольные вопросы
Представление данных
Поскольку общение с компьютером происходит на машинном уровне, необходимо
иметь представление о том, как сохраняется и обрабатывается информация. Для этого ис&
пользуются электрические элементы, которые могут принимать только два состояния:
‘‘включено’’ и ‘‘выключено’’. При сохранении данных в устройствах хранения, последова&
тельность электрических или магнитных зарядов также интерпретируется как состояние
‘‘включено’’ или ‘‘выключено’’, что и составляет содержимое записанной информации.
Стр. 53
Двоичные числа
Компьютер сохраняет команды и данные в оперативной памяти как последователь
ность заряженных или разряженных ячеек. Образно можно представить состояние каж
дой ячейки как переключатель с двумя состояниями: ‘‘включено и выключено’’ или ‘‘ис
тина и ложь’’. Такие ячейки идеально подходят для хранения двоичных чисел, которые
используют базовое число 2, так как отдельные биты могут принимать только два состоя
ния 0 или 1. Ячейки памяти, соответствующие единице, имеют повышенный заряд,
а соответствующие нулю почти разряжены. На рис. 3.1 условно показано соответствие
переключателей и двоичных чисел.
Включено Выключено Включено Включено Выключено Выключено Включено Выключено
1 0 1 1 0 0 1 0
байт
двойное
слово
учетверенное
слово
Стр. 54
отсутствует. 55
Команды и данные
В языках высокого уровня команды и данные имеют существенное логическое различие,
однако в машине они все представлены одинаково, как наборы нулей и единиц. Например,
следующая последовательность двоичных разрядов может включать первые три символа
алфавита, сохраненные в строковой переменной, или может быть машинной командой.
010000010100001001000011
Именно поэтому программисты, использующие язык ассемблера, должны разделять
данные и команды, чтобы процессор не ‘‘выполнял’’ переменные и не воспринимал ко&
манды как переменные.
Числовые системы
Каждая числовая система имеет основание системы счисления, или базовое число &&&&
максимальное значение, которое может быть присвоено отдельной цифре. В табл. 3.2 при&
ведены разрешенные значения для различных систем счисления. Во всех последующих
главах при отображении записей в памяти, значений регистров и адресов будут использо&
ваться шестнадцатеричные числа, для которых основанием системы счисления является
число 16. Для компактного отображения значений больше 9 используются шестнадца%
теричные символы от A до F, соответствующие десятичным значениям от 10 до 15.
Когда записывают двоичное, восьмеричное или шестнадцатеричное число, к нему до&
бавляют определенный символ, представленный строчной буквой. Например, шестна&
дцатеричное число 45 должно быть записано как 45h, восьмеричное 76 &&&& как 76o, а дво&
ичное 11010011 необходимо записать как 11010011b. Таким образом ассемблер
распознает числовые константы в исходной программе.
Двоичная 2 01
Восьмеричная 8 01234567
Десятичная 10 0123456789
Шестнадцатеричная 16 0123456789ABCDEF
Стр. 55
Но если это число послать в память видеоадаптера, то он воспримет его как символ, по&
этому на экране увидим букву А. Это происходит потому, что в соответствии с кодиров&
кой ASCII для символа А выбрано значение 01000001. Таким образом, интерпретация
данного значения зависит от определенных условий, которые и придают ему смысл.
Двоичное число &&&& сохраняется в памяти как последовательность битов, готовых к ис&
пользованию в расчетах. Целые двоичные числа сохраняются по 8, 16 или 32 разряда.
Символы стандартного набора ASCII &&&& могут быть представлены в памяти подоб&
но числовому значению, например как 123 или 65. Для отображения символов
может быть использован любой числовой формат, как показано в табл. 3.3.
0 1 8 256
2 2
1 2 9 512
2 2
2 4 10 1024
2 2
3 8 11 2048
2 2
4 16 12 4096
2 2
5 32 13 8192
2 2
6 64 14 16384
2 2
7 128 15 32768
2 2
Стр. 56
отсутствует. 57
8 + 1 + 9
0 0 0 0 1 0 0 1
Шестнадцатеричные числа
Большие двоичные числа почти невозможно прочитать, поэтому используют шестна
дцатеричные числа, которые удобно преобразовывать в двоичные числа и которые до
вольно хорошо воспринимаются при просмотре листингов. Их используют и в языке ас
семблера, и в отладчиках для отображения двоичных данных и машинных команд.
Каждое шестнадцатеричное число заменяет четыре двоичных бита, а два шестнадцате
ричных числа представляют байт.
На рис. 3.4 показано представление двоичного числа 000101100000011110010100
в шестнадцатеричном виде 160794h.
1 6 0 7 9 4
0001 0110 0000 0111 1001 0100 = 160794h
0000 0 0 1000 8 8
0001 1 1 1001 9 9
0010 2 2 1010 10 A
0011 3 3 1011 11 B
0100 4 4 1100 12 C
0101 5 5 1101 13 D
0110 6 6 1110 14 E
0111 7 7 1111 15 F
Каждая позиция шестнадцатеричного числа представляет степень числа 16, что ис
пользуется при вычислении десятичного значения числа, как показано в табл. 3.6.
Для преобразования шестнадцатеричного значения в десятичное необходимо умно
жить значение каждого разряда на соответствующий десятичный эквивалент, а потом их
просуммировать. На рис. 3.5 приведен пример преобразования числа 3BA4h. Берется
наибольшее значение 3 и умножается на десятичный эквивалент позиции 4096. Сле
дующее число B умножается на 256, A умножается на 16 и последнее число 4 умножается
на 1. Все просуммировав, получим соответствующее десятичное число 15268.
Стр. 57
Таблица 3.6. Степени числа 16
16n Десятичное 16n Десятичное
0 4
16 1 16 65 536
161 16 165 1 048 576
2 6
16 256 16 16 777 216
163 4096 167 268 435 456
Числа со знаком
Двоичные числа могут быть как со знаком, так и без знака. Числа без знака использу
ют все восемь битов для получения значения (например, 11111111 = 255). Просумми
ровав значения всех битов для преобразования в десятичное число, получим максималь
но возможное значение, которое может хранить байт без знака (255). Для слова без знака
это значение будет составлять 65535. Байт со знаком использует только семь битов для
получения значения, а старший восьмой бит зарезервирован для знака, при этом 0 соот
ветствует положительному значению, а 1 отрицательному. На представленном ниже
рис. 3.6 показано отображение положительного и отрицательного числа 10.
Знаковый бит
1 1 1 1 0 1 1 0 = 10
0 0 0 0 1 0 1 0 = +10
Дополнение до двух
Чтобы не усложнять процессор, отдельный блок для реализации операции вычитания
не делают; эту операцию выполняет блок суммирования. Перед суммированием отрица
тельные числа преобразовываются в дополнительные числа. Это такое число, которое
в сумме с исходным числом дает 0. Например, десятичное –6 будет дополнением к 6, так
как 6+(-6)=0. Таким образом, вместо операции вычитания A–B процессор суммирует
с положительным числом A дополнительное к B: A+(-B). Вместо того чтобы вычесть 4 из 6,
процессор просто складывает –4 и 6.
При работе с двоичными числами для дополнительного числа используется термин
дополнение до двух (встречается также определение двоичное дополнение). Например, для
двоичного значения 0001 двоичным дополнением до двух будет 1111. Такое число полу
чается из исходного числа после изменения всех единиц на нули, а нулей на единицы
(инверсия) и прибавления к полученному числу единицы, как показано ниже. Инверсия
Стр. 58
отсутствует. 59
битов в двоичном числе обозначается NOT(n). Поэтому двоичное дополнение может быть
представлено выражением NOT(n)+1.
число 0001
инверсированное число 1110
добавить 1 1111
Еще несколько примеров преобразования чисел приведено в табл. 3.7 (для дополне
ния до двух используем аббревиатуру NEG(n)).
Хотя процессор выполняет вычисления без учета знака числа, в программе знак операн
да необходимо обязательно указывать. Сложение операндов со значениями +16 и -23 будет
выглядеть в командах ассемблера следующим образом.
MOV AX,+16
ADD AX,-23
Стр. 59
В двоичном выражении число 16 будет выглядеть как 00010000, а -23 &&&& как
11101001. Когда процессор складывает эти числа, он получает 11111001. Это двоичное
число соответствует десятичному -7, как показано в примере.
00010000 16
+11101001 -23
=11111001 -7
Хранение символов
Компьютеры могут хранить только двоичные значения, но нам необходимо работать не
только с численными значениями, но и с символами, такими как ‘‘A’’ или ‘‘$’’. Для этого
компьютер использует схему кодирования символов, которая позволяет преобразовывать
символы в числа и наоборот. Наиболее известная система кодирования для компьютеров
обозначается аббревиатурой ASCII (American Standard Code for Information Interchange).
В ASCII каждому символу присваивается уникальный код, включая контрольные символы,
используемые при печати и передаче данных между компьютерами. Стандартный ASCII&
код использует только 7 разрядов в диапазоне 0-127. Значения от 0 до 31 заняты слу&
жебными кодами, используемыми при печати, передаче информации и выводе на экран.
В обычном режиме они не отображаются на экране. Остальные значения, допустимые в бай&
те, &&&& дополнительные, их применяют для расширения символьного ряда. В операционной
системе MS DOS значения 128-255 используются для получения графических символов
и греческих букв. В операционной системе Windows существует множество наборов симво&
лов, и в каждом из них дополнительным значениям соответствуют различные символы.
Строка символов представляет в памяти последовательность байт. Например, число&
вым кодам строки ‘‘ABC123’’ будет соответствовать последовательность значений 41h,
42h, 43h, 31n, 32h и 33h.
Таблица кодов ASCII приведена в справочном разделе. Чтобы найти шестнадцате&
ричное значение нужного символа, используйте соответствующие значения второй стро&
ки и второй колонки, на пересечении которых находится символ. Старший разряд числа
находится в строке, младший &&&& в колонке. Например, чтобы найти шестнадцатеричное
значение символа ‘‘a’’, посмотрим на соответствующее значение в строке. Это будет 6,
соответствующее значение в колонке будет 1. Таким образом, получаем шестнадцате&
ричное значение 61h.
Хранение чисел
Как наиболее эффективно сохранять числа в памяти? Это зависит от того, как эти
числа будут использоваться. Если числа используются для вычислений, должно быть
применено двоичное представление числа, и наоборот, лучше хранить коды ASCII, если
данные значения будут использоваться для отображения символов на экране. Например,
число 123 можно сохранить в памяти двумя способами: как последовательность кодов
ASCII для чисел 1, 2 и 3 или как один байт со значением 123, как показано на рис. 3.7.
Двоичное содержимое памяти всегда можно просмотреть, но по одному лишь значе&
нию нельзя определить, что оно представляет. Предположим, два байта памяти имеют
Стр. 60
отсутствует. 61
Данные, код или текст? Это невозможно узнать, 00110001 00110010 00110011 = “123”
Стр. 61
Константы и выражения
Цифровой литерал является комбинацией цифр и дополнительных символов &&&& зна&
ков, десятичных точек и экспонент:
5;
5,5;
26,E+05.
Целочисленные константы могут оканчиваться дополнительным буквенным символом,
который является указателем базы системы счисления: h — шестнадцатеричная, q (или o) &&&&
восьмеричная, d &&&& десятичная, b &&&& двоичная. Если дополнительного буквенного сим&
вола нет, то по умолчанию принимается десятичная система счисления. Буквенный символ
может быть строчным или заглавным. В табл. 3.10 приведено несколько примеров цело&
численных констант.
Если число не сопровождается дополнительным буквенным символом, то по умолчанию
принимается, что для данного числа используется десятичная система счисления.
Стр. 62
отсутствует. 63
Утверждения
В языке ассемблера утверждение состоит из имени, мнемокода, операндов и коммен&
тариев. Утверждения бывают двух типов: команды и директивы. Команды &&&& это утвер&
ждения, выполняемые в программе, а директивы &&&& утверждения, информирующие ас&
семблер о том, как создавать выполняемый код. Общая форма утверждения выглядит так:
[имя] [мнемокод] [операнды] [; комментарии]
Утверждение имеет свободную форму записи. Это означает, что его можно записы&
вать с любой колонки и с произвольным количеством пробелов между операндами. Ут&
верждение должно быть записано на одной строке и не заходить за 128&ю колонку. Мож&
но продолжить запись со следующей строки, но при этом первая строка должна
заканчиваться символом ‘‘\’’ (обратная косая черта), как показано в примере ниже.
longArrayDefinition DW l000h, 1020h, 1030h \
1040h, 1050h, 1060h, 1070h, 1080h
Команда &&&& это утверждение, которое выполняется процессором во время работы
программы. Команды могут быть нескольких типов: передачи управления, передачи дан&
ных, арифметические, логические и ввода&вывода. Команды транслируются ассемблером
непосредственно в машинные коды. Ниже приведен фрагмент листинга со всеми исполь&
зуемыми категориями команд.
CALL MySub ; Передача управления.
MOV EAX,5 ; Передача данных.
ADD EAX,20 ; Арифметическая.
JZ next1 ; Логическая (переход, если установлен флаг нуля).
IN AL,20 ; Ввод-вывод (чтение из аппаратного порта).
Стр. 63
Директива это утверждение, которое выполняется ассемблером во время трансля
ции исходной программы в машинные коды. Например, директива DB заставляет ас
семблер выделить память для однобайтовой переменной, названной count, и поместить
туда значение 50.
count DB 50
Следующая директива .STACK заставляет ассемблер зарезервировать пространство
памяти для стека.
.STACK 4096
Имена
Имена определяют метки, переменные, символы или ключевые слова. Они могут со
стоять из символов, приведенных в табл. 3.11.
Стр. 64
отсутствует. 65
слова не могут использоваться программистом для каких&либо других целей, например как
имена. Если использовать ключевое слово ADD как метку, это будет синтаксической ошибкой.
ADD: MOV EAX,10 ; Синтаксическая ошибка!
Стр. 65
которые потом можно собрать вместе с помощью компоновщика. При этом можно исполь
зовать модули, написанные другими программистами, или ранее написанные и отлажен
ные модули. Если есть набор подходящих модулей, то разработка сложной программы мо
жет занять не так уж и много времени. Надо только объединить уже существующие и вновь
написанные модули и получить один исполняемый модуль что и сделает компоновщик.
Компоновщик должен вызываться для любой написанной программы, даже если она
состоит только из одного объектного модуля. Одномодульные программы компоновщик
сразу преобразует в перемещаемый модуль. Если программа состоит из двух или боль
шего количества модулей, то компоновщик сначала объединяет их, а затем преобразовы
вает результат в перемещаемый модуль.
Завершенную программу можно вызвать для выполнения двумя способами:
набрать ее имя в качестве команды или щелкнуть на имени программы мышкой;
выполнить ее под управлением программы DEBUG.
Обычно программу следует выполнять только в том случае, если есть уверенность в ее
безошибочной работе. Пока она не будет полностью отлажена, необходимо вызывать
программу только под управлением отладчика DEBUG. Это связано с тем, что непрове
ренная программа может нанести системе непоправимые повреждения. При программи
ровании под Win32 это маловероятно, но если вы программируете под DOS, то это пра
вило необходимо соблюдать.
С помощью отладчика DEBUG можно управлять процессом выполнения программы.
Наряду с другими функциями, DEBUG позволяет отображать и изменять значения пере
менных, останавливать выполнение программы в заданной точке или выполнять про
грамму по шагам. Таким образом, DEBUG является основным инструментом для поиска
и исправления ошибок в программе.
На рис. 3.8 показаны этапы разработки программ с помощью ассемблера. В скобках
для каждого модуля указаны расширения файлов, в которых модули сохраняются на диске.
Библиотека
загрузочных
модулей
Текстовый редактор
Исходная программа Ассемблер Объектный модуль Загрузчик Исполняемый модуль
Отладчик
(.asm) (.obj) (.exe)
Стр. 66
отсутствует. 67
деталями. Набросок должен представлять собой ряд строк, в которых описаны действия
программы. Например, при разработке программы, которая выполняет одну из несколь&
ких функций по выбору пользователя, набросок может выглядеть следующим образом.
; Изобразить меню возможных функций
; Запросить у пользователя выбор из меню
; Прочитать ответ пользователя
; Проверить допустимость ответа
; Если ответ допустим, выполнить требуемую функцию
Точки с запятой означают, что эти строки представляют собой не команды, а коммен&
тарии, которые необходимы только разработчику. Ассемблер пропускает все до конца
строки после точки с запятой.
Затем, с учетом этого текста, производится вставка необходимых команд между стро&
ками. Так как каждая строка описывает относительно небольшую задачу, проще всего
выполнить каждую задачу в отдельности, проверяя решение перед тем, как двинуться
дальше. Иначе говоря, начните со вставки первой группы команд (в представленном
примере с команд для изображения меню), затем запишите полученную программу на
диск и завершите все последующие этапы (трансляцию, компоновку и выполнение).
Реализация такой частичной программы покажет, правильно ли она работает. Если она
работает неправильно, отладьте ее и попробуйте еще раз. После того как первая часть от&
лажена, перейдите ко второй части, затем к третьей и т.д.
Может показаться, что это очень медленный метод разработки программы, но только
так можно разрабатывать программы, которые содержат мало ошибок и которые впо&
следствии можно модифицировать. При этом достигаются следующие цели:
четко просматривается логика программы;
производится документирование программы с помощью комментариев, впослед&
ствии программу будет легко модифицировать;
обеспечивается правильность работы каждой части до того, как произойдет пере&
ход к разработке следующей части программы.
Стр. 67
и экрана, работа с загружаемыми драйверами устройств, обработка запросов на ввод&
вывод с диска). Windows NT может эмулировать определенные операционные системы
с использованием модулей, которые называются подсистемами среды, и DOS &&&& одна из
таких операционных систем, работающая как программа под управлением Windows NT.
Подсистема среды DOS предоставляет весь системный сервис, как это обычно делает
DOS, но эти функции интегрированы в Windows NT, а не расположены отдельно от нее.
Windows NT имеет специальное окно для DOS, которое называется командная кон%
соль, и работа в этом окне очень похожа на сеанс MS DOS в Windows 3x или Windows 9x.
Интерпретатор команд содержит богатый набор команд, размеры окна можно изменять,
окно имеет полосу прокрутки и не ограничено 24 строками, как в DOS.
Эмуляцию DOS обеспечивает 32&разрядное приложение с именем cmd.exe, которое яв&
ляется расширенной версией MS DOS и не только обеспечивает совместимость с MS DOS,
но и позволяет запускать приложения Windows, OS/2 и POSIX в режиме командной строки.
Сеанс DOS, который создается при запуске приложения DOS из командного окна,
можно сконфигурировать с помощью загружаемых драйверов устройств, TSR и т.д.
При запуске программы DOS из Windows NT создается виртуальная машина DOS,
благодаря которой программа DOS работает как бы на отдельном компьютере. Windows NT
создает отдельную виртуальную машину для каждого запускаемого приложения DOS.
Каждая такая машина имеет весь сервис, необходимый для работы как с 16&разрядными,
так и с 32&разрядными вызовами DOS в соответствии с требованиями DOS 6. Этой вир&
туальной машине выделяется 16 Мбайт памяти. При необходимости могут поддержи&
ваться диспетчеры памяти. Есть и ограничения. В целях безопасности и защиты ядра
приложения DOS должны быть изолированы. Для этого подсистема среды DOS перехва&
тывает все процессы ввода&вывода, проверяет их, а затем направляет данные по назначе&
нию. Этот процесс обслуживается перехватчиками ввода&вывода, которые, в свою оче&
редь, передают данные программе NT Executive для доставки по назначению.
Любые традиционно написанные программы DOS, которые выполняют ввод&вывод
с помощью стандартных системных вызовов DOS, будут работать под Windows NT без
проблем. А программы, выполняющие запись непосредственно на устройство, для кото&
рого драйверы не разрешают прямого доступа (например, драйверы жестких дисков), бу&
дут прерваны программой контроля безопасности, что приведет к сообщению об ошибке
и завершению опасной программы.
Если программа пытается записывать и читать из портов СОМ и LPT, с экрана или
клавиатуры, то Windows NT выполнит это безупречно, однако другие виды прямого
управления памятью, диском или системным устройством разрешены не будут.
Большинство программ DOS, которые используют драйвер мыши или клавиатуры, бу&
дут работать, поскольку строки ввода драйвера мыши и клавиатуры эмулируются (их ис&
пользуют очень многие программы DOS).
Операционная система DOS использует командную строку для ввода отдельных ко&
манд. Перечень всех доступных команд можно получить, если ввести команду help.
Список всех команд приведен в табл. 3.13.
Инструментальные средства
Термины ‘‘трансляция’’, ‘‘компоновка’’, ‘‘отладка’’ и другие, связанные с этапами ра&
боты ассемблера, уже упоминались в этой книге, но о самом ассемблере пока ничего не
говорилось. В этом разделе будут даны начальные сведения о наиболее популярных ас&
семблерах TASM и MASM.
Стр. 68
отсутствует. 69
Стр. 69
Пример простой программы
В представленном листинге приведена простая программа для Win32, которая отобра&
жает на экране традиционное приветствие ‘‘Hello, world!’’. В этой программе показаны
основные особенности приложений на языке ассемблера. В первой строке использована
директива Title, остальные символы строки трактуются как комментарий, подобно всем
символам во второй строке. Исходный код этой программы написан на языке ассемблера
и должен быть оттранслирован в машинные коды перед запуском программы.
Сегменты являются строительными блоками программы. Сегмент кодов определяет
место, где хранятся коды программы, сегмент данных включает все переменные, а сегмент
стека включает исполнительный стек. Стек &&&& это специальное пространство в памяти,
которое обычно используется программой при вызове и возврате подпрограмм.
ExitProcess PROTO,
x:dword
WaitMsg PROTO
WriteString PROTO
Crlf PROTO
.data
strHello BYTE "Hello Word!",0
.code
main PROC
mov EDX, OFFSET strHello ; Аргументы для WriteString.
invoke WriteString ; Вывод строки на консоль.
invoke Crlf ; Перевод каретки.
invoke WaitMsg ; Запрос для нажатия клавиши.
invoke ExitProcess,0 ; Корректное окончание программы.
main ENDP
END main
Стр. 70
отсутствует. 71
Стр. 71
Директива .STACK устанавливает размер пространства для стека емкостью 100h
(256) байт. Директива .DATA отмечает начало сегмента данных, где сохраняются
переменные. Здесь под именем strHello сохраняется строка ‘‘Hello, world!’’, за
которой следуют два служебных символа перехода на новую строку (0dh,0ah).
Символ ‘‘$’’ используется как символ конца строки для подпрограмм, которые бу&
дут считывать эту строку.
Директива .CODE отмечает начало сегмента кодов, где должны находиться выпол&
няемые команды. Директива PROC объявляет начало процедуры. В этой программе
объявлена процедура с именем main.
Первые две команды процедуры main копируют адрес сегмента данных (@data)
в регистр DS. Команда MOV всегда сопровождается двумя операндами: первый ука&
зывает, куда поместить данные, а второй &&&& откуда эти данные взять.
Затем в процедуре main на экран выводится строка символов. При этом вызыва&
ется функция DOS, которая непосредственно выводит на экран строку символов,
начиная с адреса, который указан в регистре DX. Номер этой функции предвари&
тельно должен быть помещен в регистр AH.
Последние две команды процедуры main (MOV AX,4C00h и INT 21h) заканчи&
вают программу и передают управление операционной системе.
Утверждение main ENDP использует директиву ENDP, которая отмечает конец
главной процедуры.
В самом конце находится директива END, заканчивающая программу, которая долж&
на быть оттранслирована. Следующая за ней метка main говорит об окончании
главной процедуры, или программы. Эта директива необходима для компилятора.
Как видите, отличие есть, и оно заключается в том, что вместо процедур Windows
здесь используются прерывания DOS и номера функций, которые выполняют аналогич&
ные действия, но менее удобны в использовании, так как приходится запоминать боль&
шое число номеров функций и прерываний и знать, как передавать и получать из них
рассчитанные значения.
В табл. 3.12 приведен список наиболее часто используемых директив ассемблера.
После того как программа ‘‘Hello World’’ написана в текстовом редакторе, ее необхо&
димо сохранить на диске под именем hello.asm. После этого ее можно компилировать.
Стр. 72
отсутствует. 73
Стр. 73
процедуре и признаком окончания строки служит нулевой символ. Именно поэтому при
объявлении строки в конце поставлено значение нуль.
strHello BYTE "Hello Word!",0
Следовательно, для использования процедуры WriteConsole предварительно необ&
ходимо рассчитать и длину строки.
Полностью программа Hello, которая использует только процедуры Windows и не
обращается к сторонним библиотекам, будет выглядеть так, как показано в листинге 3.1.
.data
strHello BYTE "Hello Word!",13,10,0
.code
main PROC
invoke Initialize
mov EDX, OFFSET strHello ; Аргументы для WriteString.
invoke WriteString ; Вывод строки на консоль.
invoke WaitMsg ; Запрос для нажатия клавиши.
invoke ExitProcess,0 ; Корректное окончание программы.
main ENDP
Стр. 74
отсутствует. 75
;---------------------------------------------------------
Initialize PROC private
; Получить стандартные дескрипторы консоли для входа и выхода
;----------------------------------------------------
.data
consoleOutHandle DWORD ?
consoleInHandle DWORD ?
.code
pushad
popad
ret
Initialize ENDP
;---------------------------------------------------------
Str_length PROC USES edi,
pString:PTR BYTE ; Указатель на строку
; Возвращает длину строки с нулевым окончанием.
; Принимает: pString - указатель на строку
; Возвращает: EAX = длина строки
;---------------------------------------------------------
mov edi,pString
mov eax,0 ; Счетчик символов.
L1:
cmp BYTE PTR [edi],0 ; Конец строки? (сравниваем с нулем)
je L2 ; Да: выход. (переход на метку L2)
inc edi ; Нет: выбираем следующий символ.
inc eax ; Добавляет в счетчик 1.
jmp L1 ; Переход на метку L1.
L2: ret ; Возврат из процедуры.
Str_length ENDP
;---------------------------------------------------------
WriteString PROC
; Записывает строку с нулевым окончанием в стандартный выход.
; Принимает: EDX указывает на строку.
;--------------------------------------------------------
pushad ; Сохраняем регистры.
INVOKE Str_length,edx ; Возвращает длину строки в EAX.
cld ; Необходимо выполнить до WriteConsole.
INVOKE WriteConsoleA,
consoleOutHandle, ; Дескриптор выхода.
edx, ; Указывает на строку.
eax, ; Длина строки.
OFFSET strHello, ; Число записанных байт.
0
popad ; Восстанавливаем регистры.
ret
WriteString ENDP
;------------------------------------------------------
Стр. 75
WaitMsg PROC
; Отображает запрос и ожидает нажатия клавиши <Enter>.
;------------------------------------------------------
.data
waitmsgstr DB "Press [Enter] to continue...",0
localBuf BYTE 5 DUP(?)
bytesRead DWORD ?
.code
pushad ; Сохраняем регистры.
mov edx,OFFSET waitmsgstr
call WriteString
w1: INVOKE FlushConsoleInputBuffer,consoleInHandle
INVOKE ReadConsoleA,
consoleInHandle, ; Дескриптор устройства ввода.
OFFSET localBuf, ; Указатель на буфер.
5, ; Размер буфера.
OFFSET bytesRead,
0
cmp bytesRead,2 ; Сравнение с 2.
jnz w1 ; Повторение, пока не считано 2 байта
END main
Итак, основная часть программы, которая находится в сегменте данных и кодов, поч&
ти не изменилась. В сегменте данных несколько изменилось объявление строки
strHello, где в конце добавлены два байта со значениями 13 и 10. Эти байты будут вос&
приниматься устройством вывода на консоль как перевод каретки, что аналогично ис&
пользованию в первом случае процедуры Crlf. Это не принципиально и сделано для по&
каза различных возможностей ассемблера.
Также не принципиально и появление новой процедуры Initialize, которая полу&
чает и присваивает значения дескрипторов соответствующим переменным, используе&
мым при инициализации устройств ввода&вывода. Эта процедура использовалась и ра&
нее, только это было не так явно.
Теперь для компиляции этой программы необходимо использовать команды, как по&
казано на рис. 3.10
Стр. 76
отсутствует. 77
Давайте последовательно пройдем шаги, которые необходимы для того, чтобы вернуть
эту программу в исходное состояние, т.е. представим ее в удобном для программиста и тех,
кто ее анализирует, виде. Для этого существуют такие возможности, как создание биб&
лиотек и заголовочных файлов, в которые и будет вынесено все, что не используется при
описании логики программы, но что необходимо для того, чтобы компилятор мог реали&
зовать эту логику.
Сначала создадим собственную библиотеку с именем MyLib и вынесем в нее все, что
не составляет суть программы. Это будут все объявления и процедуры, которые описаны
после конца процедуры main: Initialize, WriteString, WaitMsg и Str_length.
Создадим отдельный файл и перенесем в него все эти объявления и процедуры. Этот
файл может выглядеть так
TITLE Библиотека необходимых процедур (MyLib.asm)
.386
.model flat, stdcall
.code
;---------------------------------------------------------
Initialize PROC
; Получить стандартные дескрипторы консоли для входа и выхода.
;----------------------------------------------------
.data
consoleOutHandle DWORD ? ; Дескриптор устройства вывода.
consoleInHandle DWORD ? ; Дескриптор устройства ввода.
.code
Стр. 77
pushad
popad
ret
Initialize ENDP
;---------------------------------------------------------
Str_length PROC USES edi,
pString:PTR BYTE ; Указатель на строку
; Возвращает длину строки с нулевым окончанием.
; Принимает: pString - указатель на строку
; Возвращает: EAX = длина строки
;---------------------------------------------------------
mov edi,pString
mov eax,0 ; Счетчик символов.
L1:
cmp BYTE PTR [edi],0 ; Конец строки? (сравниваем с нулем)
je L2 ; Да: выход. (переход на метку L2)
inc edi ; Нет: выбираем следующий символ.
inc eax ; Добавляет в счетчик 1.
jmp L1 ; Переход на метку L1.
L2: ret ; Возврат из процедуры.
Str_length ENDP
;---------------------------------------------------------
WriteString PROC
; Записывает строку с нулевым окончанием в стандартный выход.
; Принимает: EDX указывает на строку.
;--------------------------------------------------------
.data
wsBuf DWORD ?
.code
pushad ; Сохраняем регистры.
INVOKE Str_length,edx ; Возвращает длину строки в EAX.
cld ; Выполнить до WriteConsole.
INVOKE WriteConsoleA,
consoleOutHandle, ; Дескриптор выхода.
edx, ; Указывает на строку.
eax, ; Длина строки.
OFFSET wsBuf, ; Число записанных байт.
0
popad ; Восстанавливаем регистры.
ret ; Возврат из процедуры.
WriteString ENDP
;------------------------------------------------------
WaitMsg PROC
; Отображает запрос и ожидает нажатия клавиши <Enter>.
;------------------------------------------------------
.data
waitmsgstr DB "Press <Enter> to continue...",0
localBuf BYTE 5 DUP(?) ; Буфер.
Стр. 78
отсутствует. 79
Initialize PROTO
Стр. 79
ExitProcess PROTO,
x:DWORD
WaitMsg PROTO
WriteString PROTO
.data
strHello BYTE "Hello Word!",13,10,0
.code
main PROC
invoke Initialize
mov EDX, OFFSET strHello ; Аргументы для WriteString.
invoke WriteString ; Вывод строки на консоль.
invoke WaitMsg ; Запрос для нажатия клавиши.
invoke ExitProcess,0 ; Корректное окончание программы.
main ENDP
END main
При компиляции будем использовать вновь созданную библиотеку MyLib.obj, т.е. бу&
дем использовать команды, как показано на рис. 3.12.
Здесь также можно наблюдать некоторое отличие от первой программы Hello, что
наглядно демонстрирует гибкость ассемблера &&&& вы можете так настроить программу, что она
будет полностью отвечать вашим запросам.
Например, в библиотеке необходимо четко разделить процедуры и объявления. Все
объявления следует вынести в отдельный заголовочный файл, который постоянно будет
использоваться с различными библиотеками, а в самой библиотеке оставить только описа&
ния процедур, для чего она и предназначена. Обо всем этом и пойдет речь в дальнейшем.
Ассемблер Microsoft
Как уже не раз отмечалось, операционная система DOS сохранена для работы с множе&
ством все еще существующих программ, написанных под DOS. Ассемблер Microsoft &&&& од&
на из таких программ. Для этой программы нет соответствующего оконного интерфейса
под Windows, и приходится работать в менее удобном консольном окне. Поэтому для нача&
ла работы с ассемблером необходимо запустить интерпретатор команд DOS, для чего выби&
рается команда Start All Programs Accessories Command Prompt в системном меню. За&
тем необходимо установить пути для удобного запуска программ ассемблера. Для этого
существует команда PATH из набора команд DOS. Рассмотрим подробнее работу в окне DOS.
Стр. 80
отсутствует. 81
Работа с DOS
Для работы с DOS необходимо запустить интерпретатор команд DOS. Для операци&
онной системы Windows XP это делается так, как описано в предыдущем разделе. Полу&
чим окно, показанное на рис. 3.13.
Стр. 81
Продолжение табл. 3.13
Команда Описание
DIR Отображает список файлов и подкаталогов
DISKCOMP Сравнивает содержимое двух флоппи&дисков
DISKCOPY Копирует содержимое флоппи&диска на другой диск
DOSKEY Редактирует командную строку, повторяет команды и создает макросы
ECHO Отображает сообщения или выключает и включает отображение вводимых
символов на экране (эхо)
ENDLOCAL Завершает изменения локализации Windows в командных файлах
ERASE Удаляет один или несколько файлов
EXIT Закрывает интерпретатор команд
FC Сравнивает содержимое нескольких файлов и отображает различие между ними
FIND Выполняет поиск текстовой строки в одном или нескольких файлах
FINDSTR Осуществляет поиск строк в файлах
FOR Запускает указанную команду для каждого файла из набора нескольких файлов
FORMAT Выполняет форматирование диска для работы с Windows
FTYPE Осуществляет отображение или модификацию типов файлов, используемых
в перечне соответствий типов и расширений
GOTO Выполняет переход к помеченной строке в командных файлах
GRAFTABL Разрешает системе Windows отображать расширенный набор символов
в графическом режиме
HELP Используется для получения справочной информации
IF Используется для создания условного утверждения в командных файлах
LABEL Создает, изменяет или удаляет метки томов на диске
MD Создает каталог
MKDIR Создает каталог
MODE Конфигурирует системные устройства
MORE Отображает часть выходных данных на экран за один раз (чтобы получить
продолжение, необходимо еще раз выполнить эту команду)
MOVE Перемещает один или несколько файлов из одного каталога в другой
PATH Отображает или устанавливает путь поиска файлов для операционной системы
PAUSE Приостанавливает обработку команд в командных файлах и отображает сообщение
POPD Восстанавливает предыдущее значение текущего каталога, сохраненного командой PUSHD
PRINT Распечатывает текстовый файл
PROMPT Изменяет вид запроса
PUSHD Сохраняет текущий каталог перед его изменением
RD Удаляет каталог
RECOVER Восстанавливает доступную информацию с запорченных дисков
REM Отмечает комментарии в командных файлах или файле CONFIG.SYS
REN Переименовывает один или несколько файлов
RENAME Переименовывает один или несколько файлов
Стр. 82
отсутствует. 83
Как видите, команд операционной системы DOS не так уж и много и запомнить их не со&
ставляет особого труда. Но даже этого делать не надо (те несколько команд, которые необ&
ходимы для запуска и работы с ассемблером, будут рассмотрены подробно далее, а аналоги
всех команд есть в системе Windows, так как режим DOS просто эмулируется системой
Windows и в конечном итоге производится обращение к процедурам Windows). В Windows 98
и в более ранних версиях можно работать и непосредственно в DOS, так как эти системы
являются только надстройками над DOS, но начиная с версии Windows NT все обстоит
по&другому. Здесь системы DOS в чистом виде уже нет и вся работа производится только
через процедуры Windows.
Чтобы получить более подробное описание каждой команды, необходимо набрать в ко&
мандной строке имя команды и дополнить ее символами ‘‘/?’’. Например, если набрать
cd /?, то получим следующую справку.
Отображает или изменяет имя текущего каталога.
CHDIR [/D] [drive:][path]
CHDIR [..]
CD [/D] [drive:][path]
CD [..]
Две точки (..) указывают, что необходимо перейти к каталогу, включающему данный
каталог.
Если набрать CD без параметров, то отобразится обозначение текущего тома и каталог.
Если набрать CD с обозначением тома, то отобразится текущий каталог.
Параметр /D используется для переключения текущего тома на указанный том с не&
обходимым каталогом.
Команда CHDIR используется в тех случаях, когда имена файлов или каталогов со&
держат пробелы.
Стр. 83
А вот командные файлы, которые существовали еще в первых системах DOS, могут
принести пользу даже при работе в системе Windows. Рассмотрим подробнее команд&
ные файлы.
Командный файл
Как уже отмечалось, в среде DOS работать довольно неудобно, так как приходится вруч&
ную набирать довольно длинные команды и пути файлов, что часто приводит к ошибкам.
Командные файлы созданы для облегчения работы в среде DOS, но и в среде Windows
они тоже приносят довольно ощутимую пользу.
Командный файл &&&& это обычный текстовый файл с последовательностью команд, ко&
торые должна выполнить система. Командный файл можно написать в обычном тексто&
вом редакторе и сохранить с расширением .bat. Файлы с таким расширением операци&
онная система трактует как командные и вызывает для их выполнения интерпретатор
команд, а не текстовый редактор.
В командный файл можно включать все команды DOS, а также команды условного пе&
рехода for, goto и if, которые позволяют реализовывать различную последовательность
выполнения команд в зависимости от наличия определенных условий. Еще несколько
команд позволяют контролировать ввод&вывод и вызывать другие командные файлы.
Контролировать процесс выполнения можно по возвращаемым приложениями кодам
ошибок, которые могут быть равны 0 (ошибок нет) или 1 (большие значения при нали&
чии ошибок).
Командный файл может иметь параметры, которые дописываются в командный файл
при его запуске на выполнение. Для подстановки параметров в команды используются
переменные от %0 до %9. Если используется переменная %0, то вместо ее при запуске
подставляется имя командного файла, а переменные от %1 до %9 заменяются соответст&
вующими аргументами. Для доступа к аргументу за пределами %9 используется команда
shift. Переменная %* ссылается на все аргументы, за исключением %0.
Например, для копирования содержимого каталога Folder1 в каталог Folder2
можно создать командный файл Mybatch.bat, содержимое которого будет представлять
одну строку
xcopy %1\*.* %2
и при вызове этого файла как
Mybatch.bat C:\folder1 D:\folder2
вместо переменной %1 будет подставлен каталог Folder1, а вместо переменной %2 &&&&
каталог Folder2.
Тот же самый результат можно получить, если в среде DOS выполнить команду
xcopy C:\folder1\*.* D:\folder2
Дополнительно с параметрами командного файла можно применять модификаторы.
Модификаторы используют информацию о текущем диске и каталоге для расширения
параметров. При использовании модификаторов сначала поставьте символ процента (%),
затем тильду (~), а за ней &&&& требуемый модификатор.
Все возможные модификаторы перечислены в табл. 3.14.
Стр. 84
отсутствует. 85
Стр. 85
Таблица 3.15. Опции компилятора MASM
Опция Описание
/AT Разрешена тонкая модель памяти (.COM)
/Bl<linker> Использовать альтернативный компоновщик
/c Компилировать без компоновки
/Cp Сохранять регистры пользовательских идентификаторов
/Cu Все идентификаторы в верхнем регистре
/Cx Сохранять регистры для открытых и внешних идентификаторов
/coff Генерировать объектный файл в формате COFF
/D<name>[=text] Задать макроопределение
/EP Вывести листинг препроцессора
/F <hex> Задать размер стека в байтах
/Fe<file> Ввести имя исполняемого файла
/Fl[file] Генерировать листинг
/Fm[file] Генерировать карту распределений
/Fo<file> Ввести имя объектного файла
/Fpi Эмулировать код 80×87
/Fr[file] Генерировать ограниченную обзорную информацию
/FR[file] Генерировать полную обзорную информацию
/G<c|d|z> Использовать вызовы Pascal, C или Stdcall (по умолчанию Stdcall)
/H<number> Установить максимальную длину внешних имен
/I<name> Добавить пути для подключаемых файлов
/link Задать <Опции компоновщика и библиотеки>
/nologo Не выводить авторские права
/omf Генерировать объектный файл в формате OMF
/Sa Задать максимальный размер листинга
/Sc Генерировать временные метки
/Sf Генерировать листинг первого прохода
/Sl<width> Задать длину строки
/Sn Не выводить таблицу перекрестных ссылок
/Sp<length> Задать длину страницы
/Ss<string> Задать подзаголовок
/St<string> Задать заголовок
/Sx Выводить ошибочные условия
/Ta<file> Компилировать файл с не .ASM&расширением
/w То же самое, что и /W0 /WX
/WX Трактовать предупреждения как ошибки
/W<number> Задать уровень предупреждений
/X Игнорировать путь к INCLUDE
/Zd Добавить номера строк в отладочную информацию
Стр. 86
отсутствует. 87
TITLE (.asm)
.386
.model flat, stdcall
ExitProcess PROTO,
x:dword
WaitMsg PROTO
WriteString PROTO
Crlf PROTO
00000000 .data
00000000 48 65 6C 6C 6F strHello BYTE "Hello Word!",0
20 57 6F 72 64
21 00
00000000 .code
Стр. 87
00000000 main PROC
00000000 BA 00000000 R
mov EDX, OFFSET strHello ; Аргументы для WriteString.
invoke WriteString ; Вывод строки на консоль.
invoke Crlf ; Перевод каретки.
invoke WaitMsg ; Запрос для нажатия клавиши.
invoke ExitProcess,0 ; Корректное окончание программы.
0000001B main ENDP
END main
Microsoft (R) Macro Assembler Version 6.15.8803 08/15/06 16:28:02
(.asm) Symbols 2 - 1
Segments and Groups:
N a m e Size Length Align Combine Class FLAT GROUP
_DATA . . 32 Bit 0000000C DWord Public 'DATA'
_TEXT . . 32 Bit 0000001B DWord Public 'CODE'
Symbols:
0 Warnings
0 Errors
В этом листинге приведена вся информация о созданной программе, начиная с ис&
ходных команд и их машинных эквивалентов и заканчивая распределением имен, памяти
и сегментов. При профессиональном программировании приходится довольно часто об&
ращаться к файлам листингов, но на первом этапе изучения языка в этом большой необ&
ходимости нет.
Синтаксические ошибки
Очень немного найдется программистов, которые сразу смогут написать даже неболь&
шую программу без ошибок. Поэтому ассемблер проверяет написанные команды и вы&
водит на экран все строки, в которых есть ошибки, причем с их объяснением. Например,
если в программе Hello первую команду MOV ошибочно набрать как MIV, то появится
следующее сообщение:
Стр. 88
отсутствует. 89
Assembling: Hello.asm
Hello.asm(17) : error A2008: syntax error : edx
Иными словами, компилятор зафиксирует синтаксическую ошибку перед операндом
edx в строке номер 17.
Компоновка программы
После компиляции будет получен объектный файл с расширением .obj, который не&
обходимо преобразовать в исполняемый модуль. На данном этапе используется про&
грамма&компоновщик, которая использует объектный файл (в данном случае
hello.obj) в качестве входного и создает выполняемый файл, называя его hello.exe.
Командная строка для компоновщика имеет следующий формат:
LINK [options] [files] [@commandfile]
Здесь link &&&& имя программы компоновщика, files &&&& файлы библиотек.
Не будем рассматривать все опции компоновщика, так как на первом этапе они не приго&
дятся. Необходимо знать только опцию /DEBUG (она заставляет компоновщик вставлять
в создаваемую программу нужные для отладчика коды) и опцию /SUBSYSTEM:CONSOLE
(приводит к созданию формата файла, который ‘‘понимает’’ Windows и рассчитан на ра&
боту в консольном режиме). Чтобы просмотреть все опции 32&разрядного отладчика,
просто наберите в командном окне имя отладчика link32. Также можно использовать
опцию /MAP, которая приводит к созданию файла распределения памяти с расширением
.map. Попробуйте создать этот файл и проанализируйте его. От его использования тоже
может быть ощутимая польза.
После работы компоновщика будет создан исполняемый файл с расширением .exe,
который уже можно запускать на выполнение как любую программу Windows.
Использование программы ML позволяет выполнять компиляцию и компоновку одной
командой. Но можно использовать отдельно компилятор (MASM) и 32&разрядный компо&
новщик (LINK32). Также необходимо отметить, что версии 32&разрядного компоновщика,
которые Microsoft использует в более поздних версиях, чем MASM615, называются просто
LINK, без цифр 32 (в версии MASM615 так называется 16&разрядный компоновщик). На&
чиная с этой версии, Microsoft уже не разрабатывает 16&разрядные компоновщики.
Запуск программы
Для запуска программы наберите в командной строке имя выполняемой программы
hello или просто щелкните на ней мышкой в среде Windows.
Отладчик
Основной инструмент, с которым приходится работать при создании программ на ас&
семблере, &&&& отладчик. В дальнейшем будут рассматриваться небольшие примеры про&
грамм на языке ассемблера, и лучшим способом для их изучения является использование
отладчика. Отладчик &&&& это программа, позволяющая отображать на экране значения необ&
ходимых переменных, получать состояние всех регистров и ячеек памяти при пошаговом
выполнении программы, вносить изменения в программу, указывать точки останова
и многое другое. Это необходимо при проверке написанных на языке ассемблера программ.
Существует несколько хороших отладчиков, но мы будем использовать универсаль&
ный и довольно удобный отладчик Microsoft, который распространяется бесплатно и на&
зывается dbg_x86_6.5.3.8.exe. Этот отладчик имеет оконный интерфейс и привыч&
ные для работающих в среде Windows функции.
Стр. 89
Простая программа
Для знакомства с отладчиком напишем на языке ассемблера небольшую программу
Sum, которая складывает три числа и сохраняет сумму в памяти. Оттранслируем и вы
полним компоновку программы с использованием опций отладки, как показано на
рис. 3.14, а затем начнем работу с отладчиком.
Программа Sum
TITLE Программа суммирования (sum.asm)
.386
.MODEL flat, stdcall ; Задаем модель памяти.
WaitMsg PROTO ; Прототип функции WaitMsg.
ExitProcess PROTO, ; Прототип функции ExitProcess.
x: DWORD
Стр. 90
отсутствует. 91
Стр. 91
пользователя, создавая удобную для себя среду отладки. Все окна по умолчанию стараются
пристыковаться к ближайшей границе (dock) окна, хотя их можно сделать и плавающими
(floating). Потребуется некоторая практика, чтобы быстро создавать удобный рабочий стол от&
ладчика. Щелчок правой кнопкой мыши на заголовке окна приводит к появлению контекст&
ного меню, которое позволяет установить необходимый режим работы окна.
Окна можно открывать либо воспользовавшись пунктом меню Window, либо с помо&
щью отдельных кнопок, расположенных на управляющей панели. Названия кнопок, ко&
торые появляются в момент задержки курсора на кнопке, перечислены в табл. 3.16.
Стр. 92
отсутствует. 93
выполняя команды Step into, Step over, Run to cursor и другие. Результаты, получаемые
в процессе работы программы, будут отображаться в соответствующем консольном окне.
В окнах отладчика можно видеть как команды ассемблера, так и коды машинных ко&
манд. Все это не только значительно помогает в отладке программы, но и удобно при изу&
чении языка ассемблера, а также в процессе исследования операционной системы и аппа&
ратной части компьютера. Фрагмент дисассемблированного текста показан в табл. 3.17.
Sum!main:
00401038 b805000000 mov eax,0x5
0040103d 83c00a add eax,0xa
00401040 83c00f add eax,0xf
00401043 66a300404000 mov [Sum!sum (00404000)],ax
00401049 e8cbffffff call Sum!ILT+20(_WaitMsg (00401019)
0040104e 6a00 push 0x0
00401050 e8b7000000 call Sum!ExitProcess (0040110c)
Резюме
В этой главе даны общие сведения о языке ассемблера, используемых форматах дан&
ных и принципах разработки программ на языке ассемблера. Описана работа с ассембле&
ром и основные этапы разработки программ с использованием ассемблера. Акцент сде&
лан на разработке библиотек, использование которых значительно облегчает работу
с программой. Отмечена важность этапа отладки программ и приведены базовые сведе&
ния о работе с отладчиком. Для лучшего понимания материала вниманию читателя пред&
ставлены простейшие примеры разработки и отладки программ.
В данной главе приводились примеры довольно длинных последовательностей ко&
манд, которые необходимы для ассемблирования исходного файла. Конечно, команды
можно набирать вручную, но лучше использовать командные файлы, которые позволяют
значительно облегчить работу. Существенно сэкономить время позволит использование
специального интерфейса пользователя (который можно найти на специальных сайтах
или разработать самому, если вы знаете язык высоко уровня). Такой интерфейс может
создавать необходимые последовательности команд при нажатии определенных кнопок
и запускать их на выполнение.
Четкое представление последовательности шагов, выполняемых ассемблером при об&
работке исходной программы и получении исполняемого файла, значительно облегчит
вашу работу в дальнейшем.
Стр. 93
Контрольные вопросы
1. Существует ли взаимно однозначное соответствие между командами языка ас&
семблера и машинными кодами?
2. Можно ли написать программу в машинных кодах?
3. Сколько бит находится в байте, слове, двойном и учетверенном слове?
4. Подсчитайте диапазон значений для слова без знака.
5. Что такое команды и что такое данные?
6. Что такое основание системы счисления?
7. Почему удобно использовать шестнадцатеричную систему счисления для отобра&
жения данных?
8. Как представлены в памяти числа без знака и со знаком?
9. Что такое дополнение до двух?
10. Как сохраняется в памяти строка символов? Как подсчитать размер занимаемой
памяти для отдельной строки?
11. Как сохраняется в памяти числовое значение? Как подсчитать размер занимаемой
памяти для числового значения?
12. Что такое константа? Чем константа отличается от переменной?
13. Какие типы утверждений используются в языке ассемблера?
14. Как разрабатывается программа на языке ассемблера?
15. Назовите основные этапы выполнения программы.
16. Назовите основные опции компилятора.
17. Для чего нужен отладчик?
18. Каковы особенности работы DOS в операционной системе Windows NT?
19. Можно ли использовать все возможности языка ассемблера при работе в DOS под
управлением Windows NT?
20. Чем отличаются ассемблеры Microsoft и Borland?
Стр. 94
Глава 4
Программирование
для Windows
В этой главе...
В этой главе рассмотрен процесс создания 32&разрядных приложений для работы только
с консолью. Разработку графических окон в стиле Windows вы изучите после того, как
ознакомитесь с основными командами и синтаксисом ассемблера. В этой главе также бу&
дут представлены некоторые функции операционной системы Windows и приведены от&
дельные примеры. Для более полного знакомства с библиотеками функций Windows
можно установить Microsoft Visual Studio, Delphi или Borland C++ и использовать соот&
ветствующую документацию. Справочную информацию по Windows вы найдете на сайте
www.msdn.microsoft.com.
Стр. 95
Под термином интерфейс прикладного программирования (API &&&& Application Programming
Interface) понимается набор типов, констант и функций, которые позволяют непосредст&
венно управлять объектами из программы. В дальнейшем мы будем использовать интер&
фейс Win32 API, который обеспечивает доступ к 32&разрядным версиям объектов плат&
формы MS&Windows.
С Win32 API непосредственно связан набор инструментальных средств разработки
платформы Microsoft (SDK &&&& Software Development Kit), содержащий прикладные про&
граммы, библиотеки, примеры кода и документацию, которые помогают программисту
создавать программы. Слово платформа означает операционную систему или группу тес&
но взаимосвязанных операционных систем.
При запуске приложения для Windows оно создает или консольное окно (консоль),
или графическое окно.
Для того чтобы получить консольное приложение, работающее в защищенном режи&
ме, необходимо при вызове компоновщика использовать опцию
/SUBSYSTEM:CONSOLE.
Программа для консоли ведет себя точно так, как и программа для MS&DOS, за ис&
ключением небольших улучшений. Чтение и запись происходят в стандартные входы
и выходы. Ошибки также записываются в стандартный файл ошибок. Консоль имеет
один входной буфер и один или несколько экранных буферов.
Входной буфер содержит последовательность входных записей, каждая из которых
включает данные о входных событиях (например, нажатие клавиш, щелчок мыши
или изменение размеров окна).
Экранный буфер содержит двумерный массив символов и данных о цвете, кото&
рые необходимы для отображения символов на экране.
Уровни доступа
Можно использовать два уровня доступа: высокий и низкий, для простоты доступа
или полноты контроля соответственно.
Функции высокого уровня считывают поток символов из входного буфера консо&
ли. Выходные данные записываются в экранный буфер консоли. Как вход, так и вы&
ход может быть перенаправлен для чтения или записи в текстовый файл.
Стр. 96
отсутствует. 97
Стр. 97
Дескрипторы консоли
Почти все функции для работы с консолью требуют передачи дескриптора в качестве
первого аргумента. Дескриптор является 32&разрядным целым числом без знака, которое
уникально идентифицирует объект, такой как растровое изображение (bitmap) или чер&
тежное перо, либо входные и выходные устройства. В дальнейшем в главе по мере изло&
жения материала будут использоваться дескрипторы для следующих устройств:
STD_INPUT_HANDLE &&&& стандартный вход;
STD_OUTPUT_HANDLE &&&& стандартный выход;
STD_ERROR_HANDLE &&&& стандартный выход ошибок.
Будут также представлены дескрипторы для записи в экранные буферы.
Функция GetStdHandle возвращает дескриптор потока для консоли: вход, выход
или выход ошибок. Дескриптор необходим для создания входа&выхода в программах, ра&
ботающих с консолью. Ниже показан прототип функции.
GetStdHandle PROTO, nStdHandle:DWORD
Функция GetStdHandle может принимать значения STD_INPUT_HANDLE,
STD_OUTPUT_HANDLE или STD_ERROR_HANDLE. Эти константы описаны в стандартных
заголовочных файлах и имеют значения &&10, &&11 и &&12 соответственно. Функция возвра&
щает значение дескриптора в регистр EAX, которое должно быть сохранено для дальней&
шего использования. Ниже показан небольшой пример.
.DATA
inputHandle DWORD ?
.CODE
INVOKE GetStdHandle, STD_INPUT_HANDLE
MOV inputHandle,EAX
Поскольку константа STD_INPUT_HANDLE определяет устройство, для которого
в Windows используется численное значение –10, то в данном случае вместо идентифика&
тора STD_INPUT_HANDLE можно подставить число –10. Однако использование понятного
идентификатора значительно облегчает работу и освобождает от запоминания абсолютно
не информативных чисел. К тому же идентификатору STD_INPUT_HANDLE где&то должно
быть присвоено значение –10. Обычно это делается в заголовочных (подключаемых)
файлах, которые имеют расширение .inc и которые должны быть подключены к вашей
библиотеке или программе с помощью ключевого слова INCLUDE. Например, так:
INCLUDE G:/Masm/MyLib.inc
А в самом подключаемом файле должна находиться следующая строка
STD_INPUT_HANDLE = -10
или
STD_INTPUT_HANDLE EQU -11
Поэтому вы должны создать собственный заголовочный файл, в котором будете опре&
делять часто используемые константы, и подставлять его в свои программы. Такие про&
граммы будут более читабельны, поскольку в них будет отсутствовать информация, не
имеющая отношения к логической сути программы. А компилятор просто подставит все,
что находится в этом файле, вместо строки со словом INCLUDE.
Стр. 98
отсутствует. 99
Стр. 99
Окончание табл. 4.2
Функция Описание
ReadConsoleOutputCharacter Копирует указанное число последовательных символов
из экранного буфера консоли
ScrollConsoleScreenBuffer Перемещает блок данных в экранный буфер
SetConsoleActiveScrecnBuffer Устанавливает указанный экранный буфер как текущий
экранный буфер консоли
SetConsoleCP Устанавливает входную кодовую страницу, используемую
консолью для вызвавшего процесса
SetConsoleCtrlHandler Добавляет или удаляет определенные дескрипторы в приложении
HandlerRoutine из списка обработчиков функций для
вызываемого процесса
SetConsoleCursorlnfo Устанавливает размер и видимость курсора для экранного
буфера консоли
SetConsoleCursorPosition Устанавливает позицию курсора в экранном буфере консоли
SetConsoleMode Устанавливает режим ввода для входного буфера консоли
или режим вывода для экранного буфера консоли
SetConsoleOutputCP Устанавливает используемую консолью кодовую страницу
для вывода, связанную с вызываемым процессом
SetConsoleScreenBufferSize Изменяет размер экранного буфера консоли
SetConsoleTextAttribute Устанавливает цветовые атрибуты текста и фона символов,
записываемых в экранный буфер консоли
SetConsoleTitle Устанавливает заголовок текущего окна консоли
SctConsoleWindowInfo Устанавливает текущий размер и положение окна для
экранного буфера консоли
SetStdHandle Устанавливает дескриптор для стандартного входа,
стандартного выхода и стандартного выхода ошибок
WriteConsole Записывает символьную строку в экранный буфер консоли,
начиная с текущего положения курсора
WriteConsoleInput Записывает данные непосредственно в выходной буфер
консоли
WriteConsoleOutput Записывает символ и цветовые атрибуты в указанный
прямоугольный блок в экранном буфере консоли
WriteConsoleOutputAttribute Копирует несколько цветовых атрибутов для текста и фона
в последовательные ячейки экранного буфера консоли
WriteConsoleOutputCharacter Копирует несколько символов в последовательные ячейки
экранного буфера консоли
Использование функций
Для того чтобы лучше понять, как пишутся программы для платформы Win32, подробно
рассмотрим небольшой пример вывода на консоль текста. Ниже приведен полный листинг
программы, который можно ассемблировать, скомпоновать и выполнить. Номера строк
в программу не входят и добавлены для дальнейших ссылок из текста на отдельные строки.
Стр. 100
отсутствует. 101
Стр. 101
которого они будут браться и который должен быть объявлен при компоновке. В нашем
случае это модуль KERNEL32.LIB. Этот модуль входит в поставку ассемблера и названия
указанных функций написаны в модуле следующим образом:
_GetStdHandle@4
_WriteConsoleA@20
_ExitProcess@4
Написание функций отличается от того, что приведено в таблице функций для консо&
ли, но ассемблер автоматически приводит их к виду, используемому в библиотеке функ&
ций, которые создавались для языка C. В соответствии с соглашениями для этого языка,
системные функции должны иметь в начале имени символ подчеркивания ‘‘_’’, а в конце
должно указываться количество байт, отводимых для параметров. Именно этим объясня&
ется такое несколько странное написание, хотя можно использовать макроопределения
и писать имена функций так, как они приведены в таблице (см. далее).
После описания функций идет описание того сообщения, которое должно быть выве&
дено на экран.
message BYTE "-------------- Console1.asm --------------------"
BYTE endl,endl
BYTE "Программа демонстрирует вывод на консоль сообщения,",endl
BYTE "которое вы сейчас видите. Использованы функции",endl
BYTE "Windows GetStdHandle и WriteConsole ",endl
BYTE "---------------------------------------------------------"
Перед этим сообщением в строке 11 объявляется переменная endl, определяющая
два символа, которые приводят к переходу на новую строку и помещению курсора в на&
чало строки (возврат каретки).
После объявления данных идет непосредственно раздел кодов, в котором функции
вызываются с помощью директивы CALL. При этом перед каждым вызовом происходит
помещение необходимых аргументов в стек в порядке справа налево, приведенном в опи&
сании функции. Например, функция WriteConsoleA представлена в справочнике сле&
дующим образом.
BOOL WriteConsole(
HANDLE hConsoleOutput, // Дескриптор экранного буфера консоли.
CONST VOID *lpBuffer, // Указатель на исходный текст.
DWORD nNumberOfCharsToWrite, // Количество символов для записи.
LPDWORD lpNumberOfCharsWritten, // Указатель на количество
// записанных символов.
LPVOID lpReserved // Зарезервировано.
);
Как видно из листинга 4.1, для данной функции сначала в стек помещается 0, затем
указатель на количество записанных символов и т.д.
Теперь запишем ту же программу с использованием макроопределений. Выглядеть
она будет так (листинг 4.2).
Стр. 102
отсутствует. 103
.DATA
endl EQU <0dh,0ah> ; Конец строки.
message \
BYTE "-------------------- Console1.asm -----------------------"
BYTE endl,endl
BYTE "Программа демонстрирует вывод на консоль сообщения,",endl
BYTE "которое вы видите. Использованы функции Windows",endl
BYTE "GetStdHandle и WriteConsole ",endl
BYTE "---------------------------------------------------------"
BYTE endl,endl,endl
messageSize = ($-message)
consoleHandle DWORD 0 ; Дескриптор стандартного выходного устройства.
bytesWritten DWORD ? ; Количество записанных байт.
.CODE
main PROC
; Получить дескриптор стандартного выходного устройства.
INVOKE GetStdHandle, STD_OUTPUT_HANDLE
MOV consoleHandle,EAX
; Закончить выполнение.
INVOKE ExitProcess,0
main ENDP
END main
Стр. 103
Этот файл подключается к основной программе с помощью директивы INCLUDE.
INCLUDE E:\MASM615\INCLUDE\MyLib.inc
В таком варианте уже не надо искажать оригинальные имена функций &&&& все это
будет сделано автоматически. Вызов функций производится с помощью директивы
INVOKE. В справочном отделе приведены полная библиотека необходимых функций
и заголовочные файлы.
Дальше рассмотрим, как можно произвести ввод информации с консоли.
Ввод с консоли
Консоль Win32 имеет входной буфер, содержащий массив входных записей. Каждое
событие, такое как нажатие клавиши, перемещение мыши или щелчок кнопкой мыши,
создает запись во входном буфере консоли. Функции высокого уровня ReadConsole
фильтруют и обрабатывают входные данные, возвращая только поток входных символов.
Функция ReadConsole
Функция ReadConsole используется для считывания текстовых данных и помеще&
ния их в буфер. Ниже приведен прототип функции.
ReadConsole PROTO,
handle:DWORD, ; Дескриптор входа.
pBuffer:PTR BYTE, ; Указатель на буфер.
maxBytes:DWORD, ; Количество символов для чтения.
pBytesRead:PTR DWORD, ; Указатель на считанные символы.
notUsed:DWORD ; Не используется.
Параметр handle &&&& это дескриптор, возвращаемый функцией GetStdHandle. Па&
раметр pBuffer представляет адрес символьного массива. Параметр maxBytes является
32&разрядным целым числом и указывает максимальное число символов, которые долж&
ны быть считаны. Параметр pBytesRead указывает на двойное слово, которое означает
число считанных символов. Последний параметр не используется и его можно заменять
любым числом, например нулем.
Ниже приведен пример программы для считывания данных с консоли.
Стр. 104
отсутствует. 105
MOV consoleHandle,EAX
; Отобразить буфер.
INVOKE WriteConsoleA, consoleHandle, ADDR buffer, BufSize-2,
ADDR bytesRead, 0
INVOKE Crlf
INVOKE WaitMsg
EXIT
main ENDP
END main
Стр. 105
тановка всех флагов в нуль, как показано в примере ниже, где выполняется ввод одиноч&
ного символа.
.DATA
saveFlags DWORD ? ; Копия флагов.
.CODE
Структуры данных
Некоторые функции консоли Win32 используют предопределенные структуры дан&
ных, такие как COORD и SMALL_RECT. Структура COORD содержит координаты экрана X и Y,
измеряемые в символах. Соответственно они могут принимать значения от 0 до 79 и от 0 до 24.
COORD STRUCT
X WORD ?
Y WORD ?
COORD ENDS
Функция WriteConsoleOutputCharacter
Эта функция, прототип которой представлен ниже, копирует массив символов в по&
следовательные ячейки экранного буфера консоли, начиная с указанного места.
WriteConsoleOutputCharacter PROTO,
handleScreenBuf:DWORD, ; Дескриптор консоли.
pBuffer:PTR BYTE, ; Указатель на буфер.
Стр. 106
отсутствует. 107
Стр. 107
Листинг 4.5. Вывод опций для Microsoft LIB
Microsoft (R) Library Manager Version 3.20.010
Copyright (C) Microsoft Corp 1983-1992. All rights reserved.
Usage: LIB library [options][commands][.listfile[.newlibrary]]
Options:
/? ; отобразить опции LIB
/HELP ; отобразить HELP для LIB
/IGNORECASE ; игнорировать регистр в именах
/NOEXTDICTIONARY ; не создавать расширенный словарь
/NOIGNORECASE ; не игнорировать регистр в именах
/NOLOGO ; не отображать поле заголовка
/PAGESIZE:n ; установить размер страницы n
Commands:
+name ; добавить объектный файл
-name ; стереть объектный файл
-+name ; заменить объектный файл
*name ; скопировать объектный файл
-*name ; переместить объектный файл
Если ввести команду lib /help, то появится окно с перечнем всех доступных спра&
вочных данных, как показано на рис. 4.1.
Стр. 108
отсутствует. 109
WriteString PROC
; Записывает строку с нулевым окончанием в стандартный
; выход. Входной параметр EDX указывает на строку.
PUSHAD
; Получаем дескриптор консоли.
INVOKE GetStdHandle, STD_OUTPUT_HANDLE
MOV [consoleOutHandle],EAX
; Получаем длину строки.
INVOKE Str_length,EDX ; Возврат длины строки в EAX.
CLD ; Сброс флага направления.
; Выводим строку на консоль.
INVOKE WriteConsoleA,
consoleOutHandle, ; Дескриптор консоли.
EDX, ; Указывает на строку.
EAX, ; Длина строки.
OFFSET bytesWritten, ; Число записанных байт.
0
POPAD
RET
WriteString ENDP
END
Данный модуль включает две процедуры: процедуру для подсчета длины строки
Str_length, которая возвращает количество символов в строке, и процедуру вывода
строки на консоль WriteString, в которой используется значение, возвращаемое про&
цедурой Str_length.
Все команды, используемые в этих процедурах, будут подробно описаны в дальней&
ших главах. Изучив их, всегда можно вернуться к этим программам и понять все тонко&
сти использования команд ассемблера. А пока отметим только тот факт, что ассемблерную
программу удобно разбить на фрагменты, которые легко разрабатывать и тестировать от&
дельно от основной программы. В дальнейшем все эти фрагменты быстро состыковывают&
ся и из отдельных процедур, которые не требуют дополнительной отладки, можно собрать
большую программу. В этом случае время, затрачиваемое на разработку программы, бу&
дет минимальным.
Для того чтобы получить объектный файл, нужно ассемблировать созданный модуль
c необходимыми процедурами. Для этого можно использовать команду
..\Masm615\ML -c -coff
Стр. 109
или написать командный файл, при условии, что ассемблер находится в каталоге
E:\Masm615, со следующей строкой:
E:\Masm615\ML -c -coff %1.asm
В данном случае ассемблер находится на диске E. На вашем компьютере все может
быть установлено по&другому, поэтому внесите соответствующие изменения.
После ассемблирования будет получен объектный файл с именем WriteString.obj,
который необходимо расположить в каталоге с библиотеками.
Теперь можно использовать этот файл в процессе компоновки модулей. При этом ес&
ли раньше командный файл выглядел следующим образом:
ML -Zi -c -Fl -coff %1.asm
if errorlevel 1 goto terminate
LINK32 %1.obj E:\MASM615\LIB\KERNEL32.LIB /SUBSYSTEM:CONSOLE /DEBUG
if errorLevel 1 goto terminate
dir %1.*
:terminate
pause
то в данном случае строку с командой LINK32 запишем так:
LINK32 %1.obj E:\MASM615\LIB\KERNEL32.LIB
Е:\MASM615\LIB\WRITESTRING.OBJ /SUBSYSTEM:CONSOLE /DEBUG
Лучше переименовать этот файл и назвать его MyLib.obj. В дальнейшем мы будем
его дополнять и создавать собственную библиотеку функций, необходимых для работы.
Например, возьмем программу вывода заранее подготовленного текста на консоль.
Без использования созданных нами процедур она будет выглядеть так:
Стр. 110
отсутствует. 111
Как видно, вторая программа занимает меньше места и ее назначение понятнее, чем в пер&
вом случае. В этой программе обратите внимание на нуль, который появился в конце тек&
ста, предназначенного для вывода на консоль. Дело в том, что процедура WriteString ра&
ботает только со строками с нулевым окончанием, и подсчет количества символов
производится до появления байта с нулевым значением. Для вызова процедур здесь исполь&
зуются директивы CALL и INVOKE. Их назначение приблизительно одинаковое, но дирек&
тива INVOKE более мощная и с ее помощью в процедуры можно передавать параметры.
Теперь объектный файл WriteString.obj можно преобразовать в библиотечный файл
с расширением .lib. Но здесь возникает определенная сложность, так как утилита LIB,
которая входит в поставку ассемблера, рассчитана на работу только с 16&разрядными про&
граммами. Поэтому для того чтобы создавать библиотеки с расширением .lib для 32&раз&
рядных программ, используйте утилиту LIB из поставки Microsoft Visual Studio, кото&
рую можно найти в каталоге ...\Microsoft Visual Studio\VC98\BIN\LIB\ или
...\MSVNET\VC7\BIN\.
Стр. 111
Работа с файлами
Функция создания файла
Функция CreateFile или создает новый файл, или открывает уже существующий.
При успешном выполнении операции возвращается дескриптор открытого файла, в про&
тивном случае возвращается константа INVALID_HANDLE_VALUE. Ниже показан прото&
тип функции.
CreateFile PROTO,
pFilename:PTR BYTE, ; Указатель на имя файла.
desiredAccess:DWORD, ; Режим доступа.
shareMode:DWORD, ; Режим совместной работы.
IpSecurity:DWORD, ; Указатель на атрибуты защиты.
creationDisposition:DWORD, ; Опции создания файла.
flagsAndAttributes:DWORD, ; Атрибуты файла.
htemplate:DWORD ; Дескриптор шаблона файла.
Первый параметр указывает на строку с нулевым окончанием, содержащую или частич&
ное, или полностью определенное имя (drive:\path\filename). Параметр desiredAccess
указывает, как файл будет использоваться (чтение или запись). Параметр shareMode
определяет возможность доступа к файлу из нескольких программ, когда файл открыт.
Параметр creationDisposition указывает, какие действия необходимо предприни&
мать, если файл уже существует или не существует. Параметр flagsAndAttributes со&
стоит из отдельных флагов, которые определяют такие атрибуты файла, как архивный, шиф&
рованный, скрытый, обычный, системный и временный. Параметр htemplate включает
дополнительный дескриптор для временного файла, который содержит атрибуты файла
и расширенные атрибуты для создания файла. Несмотря на то что этот параметр не ис&
пользуется, он должен передаваться в функцию со значением 0.
Параметр DesiredAccess
Устанавливая этот параметр, можно получить режимы только для чтения, только для
записи, для записи/чтения или для устройства последовательного доступа. В табл. 4.3
приведено краткое описание всех режимов.
Параметр CreationDisposition
Это параметр действия, которое будет выполняться в зависимости от того, существует
или не существует файл с заданным именем. Параметр может иметь одно из приведенных
в табл. 4.4 значений.
Стр. 112
отсутствует. 113
Стр. 113
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0
Создание нового файла с нормальными атрибутами, удаление существующего файла
с указанным именем.
INVOKE CreateFile,
ADDR filename,
GENERIC_WRITE,
DO_NOT_SHARE,
NULL,
CREATE_ALWAYS, ; Переписать существующий файл
FILE_ATTRIBUTE_NORMAL,
0
Создать файл, если файла с указанным именем еще не существует.
INVOKE CreateFile,
ADDR filename,
GENERIC_WRITE,
DO_NOT_SHARE,
NULL,
CREATE_NEW, ; Не удалять существующий файл
FILE_ATTRIBUTE_NORMAL,
0
При этом в подключаемом файле должны быть определены следующие константы:
DO_NOT_SHARE = 0
NULL = 0
Функция CloseHandle
Эта функция используется для открытия и закрытия дескриптора файла. Ее прото&
тип такой:
CloseHandle PROTO, handle:DWORD
Функция ReadFile
Эта функция считывает текст из входного файла. Функция ReadFile может исполь&
зоваться в асинхронном режиме. Это означает, что программе не нужно ожидать, когда
считывание всего файла будет завершено. Прототип функции следующий:
ReadFile PROTO, ; Считывание в буфер из входного файла.
handle:DWORD, ; Дескриптор файла.
pBuffer:PTR BYTE, ; Указатель на буфер.
nBufsize:DWORD, ; Количество байт для считывания.
pBytesRead:PTR DWORD, ; Количество реально считанных байт.
pOverlapped:PTR DWORD ; Указатель на информацию асинхронного режима.
Параметр handle является дескриптором открытого файла, возвращаемого функци&
ей CreateFile. Параметр pBuffer указывает на буфер, в который будут записываться
данные из файла. Параметр nBufsize определяет максимальное число байт для считы&
вания из файла. Параметр pBytesRead указывает на целочисленную переменную, кото&
рая содержит число реально считанных байт. Дополнительный параметр pOverlapped
указывает на структуру, которая определяет, как функция может считывать файл в асин&
хронном режиме. Для оператора синхронизации по умолчанию передается указатель на
нулевое значение.
Стр. 114
отсутствует. 115
Функция WriteFile
Эта функция записывает данные в файл, используя дескриптор выходного устройст&
ва. Дескриптором может быть и дескриптор экранного буфера или дескриптор одного из
текстовых файлов. Функция начинает запись в файл с позиции, определяемой внутрен&
ним указателем файла. По окончании записи указатель устанавливается на позицию по&
сле последнего записанного байта. Прототип функции следующий:
WriteFile PROTO,
fileHandle:DWORD, ; Дескриптор выходного устройства.
pBuffer:PTR BYTE, ; Указатель на буфер.
nBufsize:DWORD, ; Размер буфера.
pBytesWritten:PTR DWORD, ; Число записанных байт.
pOverlapped:PTR DWORD ; Указатель на информацию асинхронного режима.
Стр. 115
0 ; Флаг перекрытия исполнения.
INVOKE CloseHandle, fileHandle
QuitNow:
INVOKE ExitProcess,0 ; Конец программы.
main ENDP
END main
Стр. 116
отсутствует. 117
Стр. 117
INVOKE WaitMsg
INVOKE ExitProcess,0 ; Конец программы.
main ENDP
END main
Управление окном
Интерфейс Win32 API предоставляет некоторые возможности по управлению окном кон
соли и экранным буфером, который содержит отображаемые на экране данные. На рис. 4.2
показано соответствие экранного буфера и отображаемого на консоли текста. Информа
ция, сохраняемая в экранном буфере, значительно превышает размер отображаемого
текста, который является небольшой частью экранного буфера.
Стр. 118
отсутствует. 119
Функция SetConsoleTitle
Эта функция позволяет изменить название окна консоли. Например, можно назвать
окно ‘‘Моя программа’’.
.DATA
titleStr BYTE "Моя программа",0
.CODE
INVOKE SetConsoleTitle, ADDR titleStr
Функция GetConsoleScreenBufferlnfo
Эта функция возвращает информацию о текущем состоянии окна консоли. Функция
содержит два параметра: дескриптор экранного буфера консоли и указатель на структуру,
которая заполняется функцией.
GetConsoleScreenBufferlnfo PROTO,
outHandle:DWORD, ; Дескриптор экранного буфера консоли.
pBufferlnfo:PTR CONSOLE_SCREEN_BUFFER_INFO
Структура CONSOLE_SCREEN_BUFFER_INFO, с которой работает функция, имеет
следующий вид:
CONSOLE_SCREEN_BUFFER_INFO STRUCT
dwSize COORD <>
dwCursorPos COORD <>
wAttributes WORD ?
srWindow SMALL_RECT <>
maxWinSize COORD <>
CONSOLE_SCREEN_BUFFER_INFO ENDS
Параметр dwSize возвращает размер экранного буфера в количествах символов по длине
и ширине. Параметр dwCursorPos возвращает положение курсора. Оба поля имеют тип COORD.
Параметр wAttributes возвращает цвета символов и фона символов, записанных функ&
циями, такими как WriteConsole. Параметр srWindow возвращает координаты отобра&
жаемого текста относительно экранного буфера. Параметр maxWinSize возвращает мак&
симально допустимый размер окна консоли на основе размера экранного буфера, размера
шрифта и отображаемых размеров. Ниже показан пример использования функции.
.DATA
consolelnfo CONSOLE_SCREEN_BUFFER_INFO <>
.CODE
INVOKE GetConsoleScreenBufferInfo, outHandle, ADDR consoleInfo
Стр. 119
Функция SetConsoleWindowlnfo
Эта функция позволяет установить размеры и положение окна консоли относительно
экранного буфера. Ее прототип следующий:
SetConsoleWindowInfo PROTO, ; Установить положение окна консоли.
nStdHandle:DWORD, ; Дескриптор экранного буфера.
bAbsolute:DWORD, ; Тип координат.
pConsoleRect:PTR SMALL_RECT ; Указатель на границы окна.
Параметр bAbsolute определяет, на что указывают координаты структуры, на кото&
рую указывает параметр pConsoleRect. Если значение bAbsolute равно true, то ко&
ординаты определяют верхний левый и нижний правый углы окна, в противном случае
координаты должны быть добавлены к текущим оконным координатам.
Ниже показана программа, которая записывает пятьдесят строк текста в экранный
буфер. Затем размеры их изменяются и строки перемещаются в зону отображения в окне
консоли. Используется функция SetConsoleWindowInfo.
Листинг 4.11. Прокрутка окна консоли
TITLE Прокрутка окна консоли (Scroll.asm)
.386
.MODEL flat, stdcall
.DATA
STD_OUTPUT_HANDLE EQU -11
message BYTE ": Эта строка будет записана "
BYTE "в экранный буфер",0dh,0ah
messageSize = ($-message)
outHandle DWORD 0 ; Дескриптор стандартного выхода.
bytesWritten DWORD ? ; Число записанных байт.
lineNum DWORD 0
windowRect SMALL_RECT <0,0,60,11> ; Левый, верхний, правый, нижний.
.CODE
main PROC
INVOKE GetStdHandle, STD_OUTPUT_HANDLE
MOV outHandle,EAX
.REPEAT
MOV EAX,lineNum
CALL WriteDec ; Отобразить каждый номер.
INVOKE WriteConsoleA,
outHandle, ; Дескриптор консоли.
ADDR message, ; Указатель на строку.
messageSize, ; Длина строки.
ADDR bytesWritten, ; Количество записанных байт.
0 ; Зарезервировано.
inc lineNum ; Следующий номер строки.
.UNTIL lineNum > 50
; Изменяет размеры и положение окна консоли относительно
; экранного буфера.
INVOKE SetConsoleWindowInfo,
outHandle,
TRUE,
ADDR windowRect ; Размеры окна.
CALL Readchar ; Ожидание нажатия клавиши.
CALL Clrscr ; Очистка экранного буфера.
CALL Readchar ; Ожидание нажатия клавиши.
INVOKE WaitMsg
INVOKE ExitProcess,0
main ENDP
END main
Стр. 120
отсутствует. 121
Функция SetConsoleScreenBufferSize
Эта функция позволяет установить размеры экранного буфера с количеством колонок X
и количеством строк Y. Прототип функции следующий:
SetConsoleScreenBufferSize PROTO,
outHandle:DWORD, ; Дескриптор экранного буфера.
dwSize:COORD ; Новые размеры буфера.
Управление курсором
Интерфейс Win32 API предоставляет несколько функций для установки размера
курсора, видимости и размещения. Необходимые данные собраны в структуре
CONSOLE_CURSOR_INFO.
CONSOLE_CURSOR_INFO STRUCT
dwSize DWORD ?
bVisible BYTE ?
CONSOLE__CURSOR_INFO ENDS
Поле dwSize устанавливает процент (1...100) символьных ячеек, заполняемых курсо&
ром. Если значение поля bVisible равно true, то курсор видим.
Функция GetConsoleCursorlnfo
Эта функция возвращает текущие размеры и видимость курсора консоли. Возвраща&
ется указатель на структуру CONSOLE_CURSOR_INFO.
GetConsoleCursorlnfo PROTO,
outHandle:DWORD, ; Дескриптор выходного устройства.
pCursorInfo:PTR CONSOLE_CURSOR_INFO ; Информация о курсоре.
По умолчанию размер курсора имеет значение 25 (т.е. 25% символьных ячеек запол&
нено курсором).
Функция SetConsoleCursorlnfo
Эта функция устанавливает текущие размеры и видимость курсора консоли. Переда&
ется указатель на структуру CONSOLE_CURSOR_INFO.
SetConsoleCursorlnfo PROTO,
outHandle:DWORD, ; Дескриптор выходного устройства.
pCursorlnfo:PTR CONSOLE_CURSOR_INFO ; Информация о курсоре.
Функция SetConsoleCursorPosition
Эта функция устанавливает координаты положения курсора X, Y. Передается струк&
тура COORD и дескриптор входного устройства.
SetConsoleCursorPosition PROTO,
outHandle:DWORD, ; Дескриптор.
coords:COORD ; Координаты X,Y.
Управление цветом
Устанавливать цвета окна консоли можно двумя путями. Можно изменять текущий
цвет символов с помощью функции SetConsoleTextAttribute, которая действует на
все последующие символы, выводимые на консоль, или устанавливать атрибуты отдель&
ных ячеек, вызывая функцию WriteConsoleOutputAttribute.
Стр. 121
Функция SetConsoleTextAttribute
Эта функция позволяет установить цвет символов и цвет фона для выводимого на
консоль текста. Ее прототип следующий:
SetConsoleTextAttribute PROTO,
outHandle:DWORD, ; Дескриптор консоли.
nColor:DWORD ; Атрибуты цвета.
Значения цвета сохраняются в младшем байте параметра nColor.
Функция WriteConsoleOutputAttribute
Эта функция копирует массив значений атрибутов последовательных ячеек экранного
буфера консоли начиная с указанной позиции. Ее прототип следующий:
WriteConsoleOutputAttribute PROTO,
outHandle:DWORD, ; Дескриптор выхода.
pAttribute:PTR WORD, ; Записываемые атрибуты.
nLength:DWORD, ; Количество ячеек.
xyCoord:COORD, ; Координаты первой ячейки.
IpCountr:PTR DWORD ; Число записанных ячеек.
Параметр pAttribute указывает на массив атрибутов, где каждый младший байт
включает информацию о цвете. Параметр nLength определяет длину массива. Параметр
xyCoord определяет начальную ячейку. Параметр IpCountr указывает на переменную,
которая содержит число записанных ячеек.
Стр. 122
отсутствует. 123
На рис. 4.3 показан результат работы программы, где все 20 символов отображаются
различным цветом.
Стр. 123
Окончание табл. 4.6
Функция Описание
LocalFileTimeToFileTime Преобразовывает локальное время в характеристику файла
на основе UTC
SetFileTime Устанавливает дату и время создания файла, время
последнего обращения и время последней модификации
SetLocalTime Устанавливает текущие локальные дату и время
SetSystemTime Устанавливает текущую системную дату и время
SetSystemTimeAdjustment Разрешение или запрет периодической настройки
внутреннего системного генератора времени
SetTimeZoneInformation Устанавливает текущие параметры временной зоны
SystemTimeToFileTime Преобразовывает системное время в характеристику файла
SystemTimeToTzSpecificLocalTime Преобразовывает время в формате UTC для определенной
временной зоны в соответствующее локальное время
Стр. 124
отсутствует. 125
Функция GetTickCount
Эта функция возвращает число миллисекунд, прошедших со времени старта компьютера.
GetTickCount PROTO ; Значение возвращается в регистре EAX
Поскольку возвращаемое значение имеет тип двойного слова, то значение будет пе&
риодически обнуляться с периодом 49,7 дней. Эту функцию можно использовать для
контроля прошедшего времени и выполнять определенные действия после превышения
порогового времени. Например, в следующей программе каждые 100 миллисекунд на эк&
ране отображается точка и время контролируется каждые 5000 миллисекунд. Этот код
можно использовать в различных программах.
Функция Sleep
Эта функция приостанавливает выполнение текущей программы на заданное число
миллисекунд.
Sleep PROTO,
dwMilliseconds:DWORD
Стр. 125
Вызовите функцию, подобную GetLocalTime, для заполнения структуры
SYSTEMTIME.
Преобразуйте структуру SYSTEMTIME в структуру FILETIME с помощью функции
SystemTimeToFileTime.
Скопируйте структуру FILETIME в учетверенное слово.
Структура FILETIME формирует 64&разрядное значение из двух 32&разрядных значений.
FILETIME STRUCT
loDateTime DWORD ?
hiDateTime DWORD ?
FILETIME ENDS
Процедура принимает указатель на 64&разрядную переменную и сохраняет текущие
дату и время в формате FILETIME.
GetDateTime PROC,
pStartTime:PTR QWORD
LOCAL sysTime:SYSTEMTIME, flTime:FILETIME
; Получить и сохранить текущее локальное время и дату как
; 64-разрядное целочисленное значение (формат FILETIME Win32)
; Получить системное локальное время
INVOKE GetLocalTime,
ADDR sysTime
; Преобразовать формат SYSTEMTIME в формат FILETIME
INVOKE SystemTimeToFileTime,
ADDR sysTime,
ADDR flTime
; Скопировать FILETIME в 64-разрядное целочисленное значение
MOV ESI,pStartTime
MOV EAX,flTime.loDateTime
MOV DWORD PTR [ESI],EAX
MOV EAX,flTime.hiDateTime
MOV DWORD PTR [esi+4],EAX
ret
GetDateTime ENDP
Создание секундомера
Функцию GetTickCount можно использовать при разработке двух процедур, кото&
рые совместно будут действовать как секундомер. Одна из процедур, назовем ее Timer-
Start, записывает текущее время. Вторая процедура TimerStop возвращает число мил&
лисекунд, прошедших с момента последнего вызова процедуры TimerStart.
Программа Timer.asm использует обе процедуры и создает некоторую задержку, вы&
зывая функцию Sleep.
Стр. 126
отсутствует. 127
TimerStop PROTO,
pSavedTime: PTR DWORD
.DATA
msg BYTE " milliseconds have elapsed",0dh,0ah,0
timerl DWORD ?
.CODE
main PROC
INVOKE TimerStart, ; Запуск таймера ADDR timerl.
ADDR timerl
INVOKE Sleep, 5000 ; Задержка 5 секунд.
INVOKE TimerStop, ; Количество миллисекунд timerl в EAX
ADDR timerl
CALL WriteDec ; Отобразить время.
MOV EDX,OFFSET msg
CALL WriteString
INVOKE WaitMsg
exit
main ENDP
Резюме
В данной главе описаны возможности создания программ для Win32 на языке ассемб&
лера. Все программы рассчитаны на работу с консолью, но несмотря на это точно так же
можно обращаться и к графическим окнам Windows, о чем будет сказано в дальнейшем.
Большое количество демонстрационных программ способствует лучшему пониманию
специфики разработки программ для Windows и позволит создать собственную библио&
теку необходимых процедур и функций. Основное отличие разработки программ для
Стр. 127
Windows от программ для DOS связано с графическим интерфейсом пользователя. Это
значит, что все команды и директивы ассемблера те же самые (о чем речь пойдет далее),
а изменения связаны с необходимостью обмена информацией с системой через интерфейс
Windows, для чего используются функции операционной системы Windows. Также необ&
ходимо четко представлять, что в защищенном режиме, в котором работает Windows NT,
можно использовать только плоскую модель памяти и нельзя непосредственно обра&
щаться к отдельным устройствам.
Контрольные вопросы
1. Какую опцию компоновщика необходимо использовать, чтобы получить консоль&
ное приложение для Win32?
2. Чем отличаются названия функций для работы с 16&разрядными символами, та&
кими как Unicode?
3. В каких операционных системах набор символов Unicode является встроенным?
4. Что такое тип данных? Какие типы данных вы можете назвать?
5. Что такое дескриптор?
6. С помощью какой функции можно получить дескриптор стандартного входа?
7. Что такое прототип функции?
8. Что записывается в подключаемом файле?
9. Какие структуры данных можете описать?
10. С помощью какой функции можно произвести считывание данных с консоли?
11. Что такое библиотека загрузочных модулей?
12. Какие расширения может иметь файл библиотеки загрузочных модулей?
13. Что такое инкапсуляция?
14. Как можно создать файл с помощью функций Win32?
15. Какие данные необходимы для отображения символа в окне консоли?
16. Какие форматы времени вы можете назвать?
Стр. 128
Глава 5
Фундаментальные понятия
языка ассемблера
В этой главе...
В данной главе будут даны понятия о типах и операциях, которые над ними можно
производить. Также будут рассмотрены директивы ассемблера, т.е. те команды, которые
должен выполнить ассемблер и которые не преобразовываются в машинные коды.
Стр. 129
DB, DW и DD, остальные будут описаны в последующих главах. В табл. 5.1 представлены
все директивы с кратким описанием. В скобках приводятся синонимы для данных директив.
При использовании последних версий MASM лучше писать директивы как BYTE,
WORD и т.д. Это расширенные директивы и с их помощью можно задавать отрицательные
значения, например использовать директиву SBYTE (знаковый байт). Эти директивы
можно трактовать как директивы задания типа. Базовые типы, которые можно использо&
вать для распределения данных, перечислены в табл. 5.2.
Объявление байтов
Директива объявления байта DB выделяет память для одного или нескольких 8&раз&
рядных значений. Из определения синтаксиса видно, что имя не является обязательным,
но инициализация, по крайней мере, одного значения должна быть проведена. Если зна&
чений больше одного, они разделяются запятыми.
[имя] DB инициализатор[, инициализатор] . . .
Каждое значение может быть константным выражением, включающим цифровые ли&
тералы, определенные символы и заключенные в кавычки символы или строки символов.
Стр. 130
отсутствует. 131
Если значение представлено числом со знаком, то оно может изменяться от -128 до +127;
если числом без знака &&&& от 0 до 255. В следующем фрагменте листинга приведено не&
сколько объявлений байт.
char1 DB 'A' ; Символ в кодировке ASCII.
char2 DB 'A'-10 ; Выражение.
signed1 DB -128 ; Наименьшее значение числа со знаком.
signed2 DB +127 ; Наибольшее значение числа со знаком.
unsigned1 DB 255 ; Наибольшее значение числа без знака.
Значение переменной можно не указывать, поставив знак вопроса на месте инициа&
лизатора.
myval DB ?
Множественная инициализация
Иногда имя переменной указывает на начало последовательности байтов. В таком
случае множественная инициализация используется для присвоения значений отдельным
байтам. В следующем примере переменная с именем list имеет смещение 0000. Это
означает, что для значения 10 смещением будет 0000, для 20 &&&& 0001, для 30 &&&& 0002
и для 40 &&&& 0003.
list DB 10,20,30,40
Символы и целочисленные значения могут присваивать одно и то же значение. Пере&
менные получат одинаковое значение в любом из случаев, приведенных в листинге ниже.
char DB 'A' ; Символ (ASCII 41h).
hex DB 41h ; Шестнадцатеричное число.
dec DB 65 ; Десятичное число.
bin DB 010000001b ; Двоичное число.
oct DB 101q ; Восьмеричное число.
Каждый инициализатор может использовать любое основание системы счисления
при последовательном присвоении значений. Цифры, символы и строковые константы
могут находиться в одном ряду. Если шестнадцатеричное значение начинается с буквы (A-F),
перед ней необходимо поставить нуль (0), чтобы ассемблер не принял это число за метку.
Например, переменные list1 и list2 получат одинаковые значения.
list1 DB 10, 32, 41h,00l000l0b
list2 DB 0Ah,20h,'A',22h
Стр. 131
LongString DB "Это пример длинной последовательности "
DB "символов, которые занимают несколько "
DB "строк при их объявлении",0
Ассемблер может автоматически подсчитать пространство памяти, занятое перемен&
ной, вычитая смещение переменной из смещения следующей переменной. Оператор ‘‘$’’
возвращает текущее состояние счетчика, как показано в примере.
0000 mystring DB "Это заданная строка"
0010 mystring_len = ($ - mystring)
В данном примере значение mystring_len будет равно 10h или 16.
Оператор DUP
Оператор DUP используется после таких директив, как DB или DW. С помощью этого
оператора можно продублировать одно или несколько значений для размещения их в па&
мяти. Это особенно эффективно при размещении строк символов или массивов.
DB 20 DUP(0) ; 20 байт, все равны нулю.
DB 20 DUP(?) ; 20 байт, инициализированы.
DB 4 DUP("ABC") ; 12 байт: "ABCABCABCABC".
DB 4096 DUP(0) ; Буфер размером 4096 байт, все нули.
Операторы DUP можно вкладывать друг в друга. В первом примере ниже показано
создание в памяти строки символов 000ХХ000ХХ000ХХ000ХХ. Второй пример можно
рассматривать как таблицу, состоящую из трех строк и четырех колонок.
aTable DB 4 DUP(3 DUP(0),2 DUP('X'))
aMatrix DW 3 DUP(4 DUP(0))
Объявление слов
Директива определения слова DW выделяет пространство памяти для одного или не&
скольких 16&разрядных значений. Ее синтаксис такой:
[имя] DW инициализатор[, инициализатор] . . .
Каждый целочисленный инициализатор может принимать значения от 0 до 65535
(FFFFh). Если инициализатор выражается числом со знаком, то его значения будут нахо&
диться в диапазоне от -32768 (8000h) до +32767 (7FFFh). Символьная константа мо&
жет быть сохранена в нижней половине слова. В качестве инициализатора можно ис&
пользовать знак вопроса (?), как показано в приведенных примерах.
DW 0,65535 ; Наименьшее/наибольшее число без знака.
DW -32768,+32767 ; Наименьшее/наибольшее число со знаком.
DW 256 * 2 ; Рассчитываемое выражение.
DW 1000h,4096,'AB',0 ; Множественная инициализация.
DW ? ; Инициализировано неопределенным значением.
DW 5 DUP(1000h) ; Инициализировано 5 слов, каждое равно 1000h.
DW 5 DUP(?) ; 5 слов, значение не определено.
Указатели
Смещение переменной или подпрограммы может быть сохранено в другой перемен&
ной, называемой указателем. В следующем примере ассемблер инициализирует указа&
тель P смещением переменной list.
list DW 256,257,258,259 ; Объявлено 4 слова.
P DW list ; P указывает на переменную list.
Стр. 132
отсутствует. 133
Символические константы
Директивы установления адреса позволяют присваивать символические имена кон&
стантам и литералам. Константа может быть определена в начале программы и в некото&
рых случаях позже переопределена.
Директива равенства
Директива равенства (=) создает константу, присваивая имени числовое значение.
имя = выражение
В отличие от директив DB и DW, директива равенства не распределяет память. Во вре&
мя трансляции программы все соответствующие имена будут заменены выражениями.
В результате вычисления выражения должно получиться 32&разрядное число со знаком
или без него (для процессоров 386 и более старших моделей), как показано в примере.
prod = 10 * 5 ; Вычисление выражения.
maxInt = 7FFFh ; Максимальное значение 16-разрядного числа со знаком.
minInt = 8000h ; Минимальное значение 16-разрядного числа со знаком.
maxUInt = 0FFFFh ; Максимальное значение 16-разрядного числа без знака.
Стр. 133
string = 'XV' ; Допустимо до двух символов.
count = 500
endvalue = count + 1 ; Можно использовать предопределенное имя.
maxLong = FFFFFFFh ; Максимальное значение 32-разрядного числа со знаком.
minLong = 8000000h ; Минимальное значение 32-разрядного числа со знаком.
maxULong = 0FFFFFFFFh ; Максимальное значение 32-разрядного числа без знака.
Символическое имя, определенное с помощью директивы равенства, может быть переоп&
ределено сколь угодно много раз. В следующем примере имя count изменяет значение не&
сколько раз. С правой стороны показано, как ассемблер использует эту константу (табл. 5.3).
Директива EQU
Директива EQU присваивает символическое имя строке символов или цифровой кон&
станте. Это повышает удобочитаемость программ и позволяет изменить многочисленные
вхождения констант в одном месте. Однако директива EQU имеет одно важное ограниче&
ние &&&& определенное один раз имя нельзя переопределить.
Выражения, включающие целочисленные значения, определяют цифровую констан&
ту, но выражения с вещественными числами интерпретируются как строка символов.
Строки символов могут быть заключены в угловые скобки (<...>) для подтверждения
того, что это строка символов. Таким образом исключается неоднозначность при опреде&
лении имен, как показано в примере ниже (табл. 5.4).
Директива TEXTEQU
Можно присвоить строке символов имя и потом использовать строку под этим име&
нем. Синтаксис присваивания может быть следующим.
имя TEXTEQU <текст>
имя TEXTEQU макроопределение текста
имя TEXTEQU %константное выражение
Стр. 134
отсутствует. 135
; Исходный текст:
move BX,address value1
move AL,20
; Ассемблируется как:
MOV BX,OFFSET value1
MOV AL,20
В следующем примере TEXTEQU используется для определения указателя p1 на стро&
ку. Позже p1 будет определять символ '0'.
.DATA
myString DB "A string",0
.code
p1 TEXTEQU <OFFSET myString>
MOV BX,p1 ; BX = OFFSET myString.
p1 TEXTEQU <0>
MOV SI,p1 ; SI = 0.
Модели памяти
С помощью директивы .MODEL можно выбрать одну из стандартных моделей памяти
для программ на языке ассемблера. Модель памяти можно рассматривать как конфигура&
цию, которая определяет, каким образом надо комбинировать используемые сегменты.
Каждая модель имеет свои ограничения по максимальному размеру памяти, выделяемой
для команд и данных. Здесь важно понимать, как в каждой модели происходит вызов
подпрограмм и данных.
При программировании для 32&разрядного режима используется только плоская модель
памяти.
Выбирая модель памяти, обычно делают выбор между скоростью, гибкостью и разме&
рами программы. Модель памяти, которая ограничивает размеры всех данных одним
сегментом в 64 Кбайт, гарантирует, что все вызовы будут только ближними и, соответст&
венно, потребуются только 16&разрядные значения. Таким образом, доступ к данным бу&
дет более быстрым, потому что 16&разрядный адрес может быть загружен значительно
Стр. 135
быстрее, чем 32&разрядный адрес ‘‘сегмент&смещение’’. Модель памяти, которая разме&
щает подпрограммы в различных сегментах, также требует для их вызова использования
двух регистров CS и IP.
Различные модели памяти используют различное число байтов для команд и данных.
Например, когда длина сегмента кодов ограничена объемом в 64 Кбайт, то нет необходимо&
сти в командах использовать большие числа. В табл. 5.2 показано различие между всеми
моделями памяти. При использовании всех моделей памяти, кроме тонкой, создаются вы&
полняемые программы типа .exe. При использовании тонкой модели памяти создаются
программы .com. Все модели, исключая плоскую модель, рассчитаны на функционирова&
ние в реальном режиме процессора. Плоская модель памяти может использоваться только
для работы в защищенном режиме. Например, операционные системы Windows NT,
Windows 2000 и более поздние являются 32&разрядными и работают в защищенном режиме.
Выполняемые программы
Надо хорошо представлять себе, как выглядят выполняемые программы после загруз&
ки их в память. Сегмент кодов и сегмент памяти находятся по разным адресам памяти,
что можно увидеть с помощью отладчика.
На рис. 5.1 показано состояние процессора и распределение памяти для сегментов.
Три сегмента имеют совместное пересечение, однако пересечение физически зани&
маемой памяти отсутствует. Каждый программный сегмент (иногда называемый логиче%
ским сегментом) получается очень небольшим и не заходит в пространство другого сег&
мента. Регистр IP способен принимать и большие значения, это может случиться, когда
программист забудет поставить заключительную команду окончания кодовой последова&
тельности. Если будет превышена граница сегмента данных при реальном режиме работы
процессора, то разрушится стек. В защищенном режиме произойдет прерывание при по&
пытке выйти за границы сегмента данных.
Стр. 136
отсутствует. 137
Обратите внимание, что при программировании для плоской модели памяти в сег&
ментных регистрах сохраняются не адреса сегментов, а элементы таблицы дескрипторов,
которые и указывают на адреса сегментов. Это необходимо для того, чтобы операцион&
ная система могла заниматься распределением памяти при многозадачном режиме работы.
Стр. 137
Окончание табл. 5.6
Директива Описание
.286 Доступны непривилегированные команды для 80286, команды для более поздних
процессоров не используются
.386 Доступны непривилегированные команды для 80386, команды для более поздних
процессоров не используются
.486 Доступны непривилегированные команды для 80486, команды для процессоров Pentium
не используются
.586 Доступны непривилегированные команды для процессоров Pentium
.287 Используются команды вычислений с плавающей запятой для математического
сопроцессора 80287
.387 Используются команды вычислений с плавающей запятой для математического
сопроцессора 80387
.686 Доступны непривилегированные команды для процессоров Pentium Pro
.686P Доступны привилегированные команды для процессоров Pentium Pro
Стр. 138
отсутствует. 139
Необходимо отметить, что в команде MOV нельзя использовать два операнда памяти.
Для этого приходится применять регистры для копирования байта, слова или двойного
слова из одного места памяти в другое. Следующие команды скопируют слово из пере&
менной var1 в var2.
MOV AX,var1
MOV var2,AX
Ниже приведены примеры команды MOV со всеми тремя типами операндов.
.DATA
count DB 10
total DW 4126h
bigVal DD 12345678H
.code
MOV AL,BL ; 8-разрядный регистр в регистр.
MOV BL,count ; 8-разрядный блок памяти в регистр.
MOV count,26 ; 8 разрядов непосредственно в память.
MOV BL,1 ; 8 разрядов непосредственно в регистр.
MOV DX,CX ; 16-разрядный регистр в регистр.
MOV BX,8FE2h ; 16 разрядов непосредственно в регистр.
MOV total,1000h ; 16 разрядов непосредственно в память.
MOV EAX,EBX ; 32-разрядный регистр в регистр.
MOV EDX,bigVal ; 32-разрядный блок памяти в регистр.
Ошибка несоответствия типов возникает даже при перемещении значения меньшего типа
в больший.
При необходимости можно использовать директиву LABEL для создания нового име&
ни с тем же смещением, но другими атрибутами, благодаря чему те же самые данные уже
не вызовут ошибки.
.DATA
countB LABEL byte ; Атрибут байт.
countW DW 20h ; Атрибут слово.
.code
MOV AL,countB ; Извлекает младший байт из countB.
MOV CX,countW ; Извлекает все из countW.
Стр. 139
Использование дополнительного смещения в операторах
К имени операнда памяти можно добавить дополнительное смещение (сдвиг), ис&
пользуя метод, называемый адресацией с прямым смещением. Это позволяет обеспечить
доступ к значениям в памяти, у которых нет собственных имен (например, к массивам
байтов, слов и двойных слов).
arrayB DB 10h,20h
arrayW DW 100h,200h
arrayD DD 10000h,20000h
Обозначение arrayB+1 ссылается на место в памяти за первым байтом, на который
указывает идентификатор arrayB, обозначение arrayW+2 ссылается на третье двойное
слово от arrayW. Операнды можно также записывать в команде MOV, используя такой
вид обозначений для копирования данных из памяти. В следующем примере показано
значение регистра AL после каждого изменения переменной.
MOV AL,arrayB ; AL = 10h.
MOV AL,arrayB+1 ; AL = 20h.
Когда выполняются команды с 16&разрядными операндами, смещение каждого по&
следующего члена массива увеличивается на два байта.
MOV AX,arrayW ; AX = 100h.
MOV AX,arrayW+2 ; AX = 200h.
Члены массива учетверенных слов смещаются на четыре байта от предыдущего.
MOV EAX,arrayD ; EAX = 10000h.
MOV EAX,arrayD+4 ; EAX = 20000h.
Команда XCHG
Команда обмена значениями XCHG обменивает содержание двух регистров или со&
держание регистра и переменной. Она имеет следующий синтаксис.
XCHG reg, reg
XCHG reg, mem
XCHG mem, reg
Команда XCHG наиболее эффективна при обмене значений двух операндов, поскольку
нет необходимости использовать дополнительный регистр для временного значения. Эта
команда значительно повышает скорость работы в приложениях сортировки данных.
Один или оба операнда могут быть регистрами, регистр можно также скомбинировать
с операндом в памяти, но нельзя использовать два операнда памяти. Например:
XCHG AX,BX ; Обменивает два 16-разрядных регистра.
XCHG AH,AL ; Обменивает два 8-разрядных регистра.
XCHG var1,BX ; Обменивает 16-разрядный операнд в памяти с регистром BX.
XCHG EAX,EBX ; Обменивает два 32-разрядных регистра.
Если необходимо обменять содержание двух переменных, можно временно использо&
вать регистр для хранения операнда. В листинге 5.1 производится обмен содержимого
двух переменных.
Стр. 140
отсутствует. 141
INCLUDE G:\MyASM\MASM\INCLUDE\Study32.inc
.DATA
value1 DWORD 0Ah
value2 DWORD 14h
.code
main PROC
MOV EAX,value1 ; Загрузить в регистр EAX.
INVOKE WriteInt ; Отобразить содержимое EAX.
XCHG value2,EAX ; Обменять EAX с value2.
INVOKE Crlf ; Перевод каретки (курсора)
INVOKE WriteInt ; Отобразить содержимое EAX.
MOV value1,EAX ; Сохранить EAX в value1.
INVOKE Crlf
INVOKE WaitMsg ; Ожидание нажатия <Enter>.
main ENDP
END main
Арифметические команды
Набор команд процессора 8086 включает команды для выполнения целочисленных
арифметических операций с 8&разрядными и 16&разрядными операндами. Для арифме&
тических операций с вещественными числами используется специальное программное
обеспечение или дополнительная микросхема арифметического сопроцессора 8087. Про&
цессор 80386 может выполнять целочисленные операции с 32&разрядными целыми чис&
лами. Процессор Intel 486 содержит встроенный блок сопроцессора для вычислений с
вещественными числами, что характерно и для всех последующих моделей процессоров.
В этой главе будут рассмотрены только операции сложения и вычитания. Операции
умножения и деления будут подробно описаны в дальнейшем.
Команда ADD
Команда ADD складывает операнд&отправитель и операнд&получатель одинакового
размера. Команда имеет следующий синтаксис.
ADD операнд-получатель, операнд-отправитель
Стр. 141
Исходный операнд&отправитель не изменяется в процессе выполнения команды,
а целевому операнду&получателю присваивается значение суммы. Размеры операндов
должны быть одинаковыми, и только один операнд может быть операндом памяти. Ре&
гистр сегмента не может быть целевым операндом. Используются все флаги состояния.
ADD CL,AL ; Суммирует два 8-разрядных регистра.
ADD EAX,EDX ; Суммирует 32-разрядные регистры.
ADD BX,1000h ; Суммирует непосредственное значение с
; 16-разрядным регистром.
ADD var1,AX ; Суммирует 16-разрядный регистр с операндом в памяти.
ADD DX,var1 ; Суммирует 16-разрядный операнд в памяти с регистром.
ADD varl,10 ; Суммирует непосредственную величину с операндом
; в памяти.
ADD DWORD PTR memVal, ECX
Оператор DWORD PTR определяет 32&разрядный операнд в памяти.
Команда SUB
Команда вычитания SUB вычитает операнд&отправитель из операнда&получателя.
Синтаксис этой команды следующий:
SUB операнд-получатель, операнд-отправитель
Размеры обоих операндов должны быть одинаковыми, и только один операнд может
быть операндом памяти. В процессоре исходный операнд сначала инвертируется, а затем
складывается с целевым операндом. Например, 4 – 1 будет выполняться как 4 + (–1).
Вспомните, что двоичное дополнение используется для отрицательных чисел, поэтому –1
будет представлено как 11111111. Приведем следующий пример вычитания:
00000100 ( 4 )
+11111111 (-1 )
---------
00000011 ( 3 )
Стр. 142
отсутствует. 143
Знаковое переполнение
Флаг переполнения устанавливается, когда в результате арифметической операции
получается число со знаком, превышающее максимально допустимое для операнда&
получателя. Установленный флаг переполнения говорит о том, что операнд&получатель
содержит некорректное число.
MOV AL,01111110b ; +126.
ADD AL,00000010b ; 126 + 2 => 10000000b, OF = 1.
MOV AL,10000000b ; -128.
ADD AL,11111110b ; -128 + (-2) => 01111110b, OF = 1.
Знаковое переполнение возникает, если суммируются два положительных числа, а ре&
зультат получается отрицательный, а также если суммируются два отрицательных числа,
а в результате получается положительное число. Процессор сравнивает значение флага
переноса со значением, которое переносится в знаковый бит операнда&получателя. Если
они не равны, устанавливается флаг переполнения. Например, при сложении двоичных
Стр. 143
76543210 чисел 10000000 и 1111111 нет переноса из бита 6 в бит 7,
CF = 1 10000000 но производится перенос из бита 7 во флаг переноса и, соот
Нет переноса + 11111110 ветственно, устанавливается флаг переноса (рис. 5.2).
из бита 6 = 01111110
в бит 7
Рис. 5.2. Пример знакового переполнения
Tипы операндов
Существует три основных типа операндов: непосредственный операнд, регистр
(регистровый операнд) и значение памяти (память). Непосредственный операнд является
константой. Регистровый операнд представляет значение одного из регистров процессо
ра. Операнд ‘‘память’’ ссылается на значение в памяти.
В системе команд Intel используется много способов представления операндов для
значений памяти, чтобы облегчить управление массивами и сложными структурами дан
ных. Шесть типов операндов памяти показаны в табл. 5.7. В этой главе будут рассмотрены
первые три типа операндов: прямой, прямое смещение и косвенный регистр.
Регистровые операнды
Регистровым операндом может быть любой регистр. Обычно режим регистровой адре
сации наиболее эффективный, потому как регистры находятся непосредственно в процес
соре и нет необходимости обращаться к памяти. Ниже приведены некоторые примеры ис
пользования команды MOV с регистровыми операндами.
Стр. 144
отсутствует. 145
MOV EAX,EBX
MOV CL,20h
MOV SI,OFFSET var1
Непосредственные операнды
Непосредственные операнды являются константным выражением, таким как число,
символьная константа, арифметическое выражение или символическая константа. Ас&
семблер определяет значение непосредственного операнда во время трансляции. Это
значение вставляется непосредственно в машинные команды. Пример использования
непосредственных операндов:
MOV AL,10
MOV EAX,12345678h
MOV DL,'X'
MOV AX,(40 * 50)
Оператор OFFSET
Оператор смещения OFFSET возвращает 16&разрядное значение смещения перемен&
ной. Ассемблер определяет смещение каждой переменной во время трансляции про&
граммы. В следующем примере переменная aWord имеет смещение 0000, команда MOV
перемещает 0 в регистр BX.
.DATA
aWord DW 1234h
.code
MOV BX,OFFSET aWord ; BX = 0000
Операнды со смещением
Особенно удобно использовать операторы сложения и вычитания (+,-) для доступа
к списку переменных. Оператор + добавляет значение к смещению переменной. В пред&
ставленной последовательности команд первый байт массива array перемещается в ре&
гистр AL, второй &&&& в регистр BL, третий &&&& в CL и четвертый &&&& в DL. Значение каждого
регистра после перемещения показано в комментарии.
Стр. 145
.DATA
array DB 0Ah,0Bh,0Ch,0Dh
.code
MOV AL,array ; AL = 0Ah
MOV BL,array+l ; BL = 0Bh
MOV CL,array+2 ; CL = 0Ch
MOV DL,array+3 ; DL = 0Dh
Можно вычитать из смещения метки. В следующем примере значение метки endlist раз&
мещается сразу за последним байтом списка list. Чтобы переместить последний байт из
list в регистр AL, необходимо написать следующий код. Напомню использование ди&
рективы LABEL. Если она расположена после объявления переменной, как показано ни&
же, то эта переменная будет ссылаться на то место в памяти, где предполагается разме&
щать переменную. Тип переменной определяется в соответствии с указанным типом.
.DATA
list byte 1,2,3,4,5
endlist LABEL byte
.code
MOV AL,endlist-1 ; переместить 5 в AL
Если в списке имеются 16&разрядные числа, то к смещению каждого последующего
элемента добавляется значение 2, что и показано в следующем примере.
.DATA
wvals DW 1000h,2000h,3000h,4000h
.code
MOV AX,wvals ; AX = 1000h
MOV BX,wvals+2 ; BX = 2000h
MOV CX,wvals+4 ; CX = 3000h
MOV DX,wvals+6 ; DX = 4000h
Операторы и выражения
В языке ассемблера выражением является комбинация операторов и операндов, кото&
рые ассемблер преобразует в числовую константу. Ассемблер выполняет арифметические
и логические операции во время трансляции, а не во время выполнения программы.
Выражения для числовых констант вычисляются во время трансляции, а не во время вы&
полнения программы.
Если выражение связано с регистром или операндом памяти, его значение должно
быть не больше, чем размер операнда&получателя. Например, разрешены следующие
присваивания.
.386
.code
MOV DL,3 * 5
MOV AX,100h + 500h
MOV EAX,(1000h * 50h) + 26
В табл. 5.8 приведен полный набор операторов, распознаваемых большинством ас&
семблеров, с их кратким описанием. (Более полное описание для некоторых операторов
будет приведено в соответствующих разделах.) Из этого набора видно, что ассемблер &&&&
достаточно мощный инструмент даже для профессионального программирования.
Стр. 146
отсутствует. 147
Стр. 147
Продолжение табл. 5.8
Оператор Описание
MAKEFLAG Оптимизированная форма команды AND, которая сбрасывает биты максимально
быстро. Применяется в случае, если флаги не используются
MOD Возвращает остаток от деления двух выражений
NE Возвращает значение истина, если одно выражение не равно другому
NEAR Преобразует адресное выражение в ближний указатель
NEAR PTR Преобразует адресное выражение в ближний указатель
NOT Выполняет поразрядное дополнение (инвертирование) выражения
OFFSET Возвращает смещение выражения в текущем сегменте (а также в группе, которой
принадлежит сегмент, если используются упрощенные директивы определения
сегментов)
OR Выполняет поразрядную логическую операцию ИЛИ
PROC PTR Преобразует адресное выражение в ближний или дальний указатель
PTR Устанавливает размер операнда, особенно в тех случаях, когда это неясно из контекста
PWORD PTR Преобразует адресное выражение в 32&разрядный дальний указатель
QWORD PTR Преобразует адресное выражение в размер учетверенного слова
SEG Возвращает сегмент выражения, независимо от того, является ли оно переменной,
именем сегмента или группы, меткой или другим символом
SETFIELD Генерирует код, присваивающий значение полю записи. Устанавливает значение поля
(целевой регистр или адрес памяти) в соответствии с содержимым регистра&источника
SETFLAG Оптимизированная форма команды OR, которая устанавливает биты максимально
быстро. Применяется в случае, если флаги не используются
SHL Сдвигает выражение влево на число бит, заданных счетчиком. Отрицательное
значение счетчика задает сдвиг данных в противоположном направлении
SHORT Преобразует выражение в указатель короткого типа (не больше &&128 или +127 байт
от текущего адреса программы). Часто используется в командах JMP, например,
JMP SHORT Label1
SHR Сдвигает значение выражения вправо на число бит, заданных значением счетчика.
Отрицательное значение приводит к тому, что данные будут сдвигаться
в противоположную сторону
SIZE Возвращает общее число байтов, выделенных для переменной. В режиме MASM
оператор SIZE рассчитывается как длина (LENGTH), умноженная на тип (TYPE)
SMALL Задает для смещения выражения размер 16 бит
TBYTE PTR Преобразует адресное выражение в размер 10 байт
TESTFLAG Оптимизированная форма инструкции TEST, которая проверяет биты
максимально быстро
THIS Создает операнд, адресом которого будет текущий сегмент и счетчик адреса.
Тип описывает размер операнда и то, представляет ли он собой код или данные
.TYPE Возвращает байт, который определяет тип и область видимости выражения.
Результирующие биты различают метку и переменную в программе, определяют
данные, внутренний и внешний идентификатор
TYPE Применяет тип существующей переменной или элемента структуры к другой
переменной или элементу структуры
Стр. 148
отсутствует. 149
Арифметические операторы
Арифметические операторы могут использоваться только с целыми числами, за исклю&
чением унарных плюс (+) и минус (-), которые также могут функционировать и с реальны&
ми числами. Примеры использования арифметических операторов показаны в табл. 5.9.
Стр. 149
Таблица 5.10. Старшинство операторов
Оператор Уровень Описание
( ) 1 Круглые скобки
+, - 2 Знаки плюс и минус (унарные)
*, / 3 Умножение, деление
mod 4 Деление по модулю
+, - 5 Сложение, вычитание
Стр. 150
отсутствует. 151
Оператор SEG
Оператор SEG возвращает сегментную часть адреса метки или переменной. Это его
свойство обычно используется, когда переменная находится в сегменте, отличном от того,
на который указывает регистр DS. В следующем примере текущее значение сегмента DS
помещается в стек, значение изменяется на значение сегмента, в котором находится пе&
ременная array, а затем восстанавливается исходное значение DS.
PUSH DS ; Сохранение DS.
MOV AX,seg array ; В DS помещается значение сегмента массива array.
MOV DS,AX
MOV BX,OFFSET array ; Получение смещения array.
... ; Работа с массивом array.
POP DS ; Восстановление DS.
Оператор PTR
Оператор PTR изменяет размер операнда, принятый по умолчанию. В командах, где
размер операнда не определен однозначно, оператор PTR может явно установить размер. PTR
может использоваться в сочетании со стандартными типами данных: BYTE, SBYTE, WORD,
SWORD, DWORD, SDWORD, FWORD, QWORD и TBYTE. Например:
MOV AL,BYTE PTR count
MOV AX,WORD PTR newVal
MOV EAX,DWORD PTR listPointer ; Требует директивы .386.
Часто размер операнда нельзя установить, исходя из контекста команды. Рассмотрим
следующую команду, которая приводит к ошибке ‘‘операнд должен иметь размер’’ (Operand
must have size).
INC [BX] ; Косвенный операнд.
Ассемблер не может определить, является ли указатель BX байтом или словом. В дан&
ной ситуации оператор PTR поможет внести ясность.
INC BYTE PTR [BX]
Оператор PTR также полезен, когда необходимо изменить размеры операнда, уста&
новленные по умолчанию. Предположим, есть 32&разрядное двойное слово и необходимо
загрузить старшую половину в регистр DX, а младшую &&&& в регистр AX (для двойного сло&
ва младшая часть имеет меньший адрес). В приведенном ниже примере будет зафиксиро&
вана синтаксическая ошибка.
.DATA
val32 DD 12345678h
.code
MOV AX,val32 ; Получить младшую часть слова (Ошибка).
MOV DX,val32+2 ; Получить старшую часть слова (Ошибка).
Однако в следующем случае ассемблер сочтет все правильным, так как оператор PTR
изменит значение по умолчанию переменной val32.
MOV AX,WORD PTR val32 ; AX = 5678h.
MOV DX,WORD PTR val32+2 ; DX = 1234h.
Директива LABEL
Директива LABEL разрешает установить метку и присвоить атрибут размера без раз&
мещения в памяти. Любой из стандартных атрибутов размера (например, BYTE, WORD,
DWORD, QWORD и т.д.) может использоваться с LABEL.
Стр. 151
В общем случае LABEL создает альтернативное имя и атрибут размера для сущест&
вующих переменных в сегменте данных. Это весьма удобный путь удовлетворения требо&
ваний языка ассемблера для переменных, которые должны соответствовать другим опе&
рандам в команде. В следующем примере метка val16 объявлена перед переменной
val32 и имеет атрибуты слова.
.DATA
val16 LABEL WORD
val32 DD 12345678h
.code
MOV AX,val16 ; AX = 5678h.
MOV DX,vall6+2 ; DX = 1234h.
Обратите внимание, что команда MOV ссылается на val16, которая является только
псевдонимом для того же пространства памяти, в котором находится переменная val32.
Оператор TYPE
Оператор TYPE возвращает размер в байтах одного элемента переменной. Например,
8&разрядная переменная имеет размер, равный 1, а 32&разрядная переменная возвратит
размер 4, массив байтов возвратит 1, а массив из 16&разрядных целых чисел возвратит 2.
Тип ближней метки будет определять значение FFFFh, а тип удаленной &&&& FFFEFFFFh.
На примерах показано типичное использование этих операторов.
.DATA
varl DB 20h
var2 DW 1000h
Стр. 152
отсутствует. 153
var3 DD ?
var4 DB 10,20,30,40,50
msg DB 'Файл не найден',0
.code
L1: MOV AX,TYPE var1 ; AX = 0001.
MOV AX,TYPE var2 ; AX = 0002.
MOV AX,TYPE var3 ; AX = 0004.
MOV AX,TYPE var4 ; AX = 0001.
MOV AX,TYPE msg ; AX = 0001.
MOV AX,TYPE L1 ; AX = FFFF.
Оператор LENGTH
Оператор LENGTH подсчитывает число отдельных элементов в переменной, которая
может быть определена с помощью оператора DUP. Если DUP не используется, то опера&
тор LENGTH возвращает значение 1. Если используются вложенные операторы DUP, то
учитывается только внешний оператор DUP, как это показано ниже.
.DATA
var1 DW 1000h
var2 DB 10,20,30
array DW 32 DUP(0)
array2 DW 5 DUP(3 DUP(0))
message DB 'Файл не найден',0
.code
MOV AX,LENGTH var1 ; LENGTH = 1.
MOV AX,LENGTH var2 ; LENGTH = 1.
MOV AX,LENGTH array ; LENGTH = 32.
MOV AX,LENGTH array2 ; LENGTH = 5.
MOV AX,LENGTH message ; LENGTH = 1.
Оператор SIZE
Оператор SIZE возвращает значение, получаемое после умножения значений LENGTH
и TYPE. Например, массив из 16&разрядных значений имеет TYPE, равный 2, и LENGTH &&&& 32.
Тогда значение SIZE будет 64.
intArray DW 32 DUP(0) ; SIZE = 64
Следующая строка имеет SIZE, равный 1, так как ее LENGTH и TYPE оба равны 1.
message DB 'Это сообщение',0
Стр. 153
Команды условного перехода. Программа начинает выполнение с новой ветви
только в том случае, если определенное условие является истиной. Intel предос&
тавляет широкий диапазон команд условного перехода, с помощью которых мож&
но создать сложные логические структуры. Процессор определяет условия
ложь/истина исходя из анализа регистра CX и регистра флагов.
Команда JMP
Команда JMP заставляет процессор продолжать выполнение с нового места програм&
мы. Новое место может быть отмечено меткой, которую процессор преобразовывает
в адрес. Например, с помощью команды JMP легко создавать циклические конструкции:
top:
...
...
jmp top
Команда JMP может совершать переход из текущей процедуры, от одной процедуры
к другой, от одного сегмента к другому, полностью выходя за пределы текущей програм&
мы или в любое место памяти RAM или ROM. Принципы структурного программирования
не поддерживают такие переходы, но они иногда необходимы при прикладном програм&
мировании на системном уровне.
При программировании для 32&разрядного режима нет необходимости думать о сег&
ментах, из которых состоит программа, что упрощает написание кода. Но при програм&
мировании для 16&разрядного режима необходимо учитывать расстояние, на которое
может происходить переход. (Подробнее об этом читайте в главе 11.)
Команда LOOP
Команда LOOP &&&& самый удобный способ, с помощью которого осуществляется по&
вторение блока команд определенное количество раз. Регистр CX автоматически исполь&
зуется как счетчик, который декрементируется при каждом повторении блока команд.
Синтаксис команды будет такой:
LOOP пункт назначения
При использовании команды LOOP сначала из регистра CX вычитается единица. Если
результат не равен нулю, контроль передается в пункт назначения. При программирова&
нии для 16&разрядного режима пункт назначения должен находиться на расстоянии от
-128 до +127 от текущего положения (ассемблер использует внутренний счетчик для
определения расстояния). В следующем примере цикл повторяется пять раз.
MOV CX,5 ; В регистре CX считаются циклы.
start:
.
LOOP start ; Переход к метке start.
Если значение CX становится равным нулю, то блок циклических команд не выпол&
няется и управление передается команде, следующей сразу за блоком. Флаг нуля не ис&
пользуется, хотя CX = 0.
В следующем примере к регистру AX добавляется 1 при каждом цикле. Когда цикл за&
канчивается, будут получены значения AX = 5 и CX = 0.
MOV AX,0 ; Установить AX в 0.
MOV CX,5 ; Счетчик циклов.
Стр. 154
отсутствует. 155
top:
INC AX ; Добавить 1 к AX.
LOOP top ; Повторять до CX = 0.
Следующий фрагмент программы выдает на печать букву А 960 раз, используя коман&
ду LOOP. Значение, помещенное в счетчик CX, является произведением 12 строк экрана
с шириной 80 символов.
MOV EX,12*80 ; Установить в счетчике значение 960.
next:
MOV AH,2 ; Функция: отобразить символ.
MOV DL,'A' ; Отображение на экране буквы A.
INT 21h ; Вызов функции DOS.
LOOP next ; Декремент CX и повторение.
Начинающие программисты часто забывают установить значение счетчика CX, в итоге
там оказывается значение 0. В результате этой ошибки цикл повторяется 65535 раз, так
как после декремента нуля в CX получится значение FFFFh.
Будьте внимательны и не модифицируйте регистр CX во время исполнения цикла. В сле&
дующем примере CX инкрементируется внутри повторяющегося блока команд. Значение
счетчика никогда не станет равным нулю, и цикл никогда не закончится.
MOV AH,2 ; Функция DOS: отображение символа.
MOV CX,10 ; Счетчик циклов.
MOV DL,'*' ; Отображение символа.
top:
INT 21h ; Вызов функции DOS.
INC СX ; Добавляем 1 к CX (Этого делать нельзя!).
LOOP top ; Повторение до CX = 0.
MOV ECX,A0000000h
L1:
.
LOOPD L1 ; Использование ECX как счетчика циклов.
Если программа транслируется в 32&разрядном режиме, команда LOOP автоматически
использует регистр ECX в качестве счетчика циклов. Команду LOOPW можно также ис&
пользовать для отмены установок по умолчанию с применением регистра CX.
Косвенная адресация
Косвенные операнды
Косвенный операнд &&&& это регистр, который содержит смещение данных в памяти.
Когда смещение переменной помещено в регистр, оно становится указателем. Для пере&
менной, имеющей одно значение, это не дает никаких преимуществ, однако для массивов
Стр. 155
указатель можно инкрементировать, выбирая последовательные элементы. 16&разрядные
регистры SI, DI, BX и BP можно использовать в качестве косвенных операндов. Директи&
вы .386, .486 или .586 позволяют использовать любой 32&разрядный регистр общего
назначения в качестве косвенного операнда (исключение составляет ESP, о котором речь
пойдет далее).
Например, если в памяти создается строка по адресу 0200, то, помещая в регистр BX
ее смещение, можно обеспечить доступ к любому элементу строки. Символ F в следую&
щем примере имеет смещение 5 от начала строки.
.DATA
aString DB "ABCDEFG"
.code
MOV BX,OFFSET aString ; BX = 0200.
ADD BX,5 ; BX = 0205.
MOV DL,[BX] ; DL = 'F'.
Сегменты по умолчанию
Смещение, получаемое с использованием косвенных операндов, отсчитывается от
начала сегмента данных (при 16&разрядном программировании его значение находится
в регистре DS), за исключением ситуации, когда регистры BP и EBP являются частью кос&
венного операнда. В этом случае используется сегмент стека (регистр SS). Полагая, что
сегмент данных и сегмент стека занимают в памяти различные места, третья и вторая ко&
манды MOV будут иметь доступ к различным местам памяти.
MOV SI,BP ; SI и BP равны.
MOV DL,[SI] ; Смотрим в сегменте данных.
MOV DL,[BP] ; Смотрим в сегменте стека.
Стр. 156
отсутствует. 157
Стр. 157
.code
...
MOV СX,COUNT ; Счетчик циклов.
MOV SI,OFFSET string
L1:
MOV AH,2 ; Вывод символа на экран.
MOV DL,[SI] ; Получить символ.
INT 21h
INC SI ; Указать на следующий символ.
LOOP L1 ; Декремент CX, повторение.
В листинге 5.4 цикл используется для вывода каждого символа, составляющего стро&
ку. Эта же программа для 32&разрядного режима будет выглядеть следующим образом.
Стр. 158
отсутствует. 159
На рис. 5.3 показано окно CPU отладчика, где можно увидеть исходный текст, со&
стояние регистров процессора и дамп памяти.
Стр. 159
Базовые и индексные операнды
Базовый и индексный операнды по существу аналогичны. Значение регистра (базового
или индексного) добавляется к смещению для получения эффективного адреса перемен&
ной. Постоянное значение дополнительного смещения или сдвига может быть, напри&
мер, смещением переменной. Различие между базовым и индексным операндом состоит
в том, что для базового операнда используются регистры базы EBX или EBP, а для ин&
дексного &&&& ESI или EDI. Ассемблер допускает различные формы записи, как показано
в табл. 5.12 для 16&разрядных регистров.
32*разрядные регистры
Если команды для процессора 386 доступны (применяется директива .386), то мож&
но использовать любой регистр общего назначения как базовый или индексный операнд.
.386 ; Доступны 32-разрядные регистры.
MOV AX,[EBX+3] ; 16-разрядный операнд.
MOV DL,string[EDX] ; 8-разрядный операнд.
MOV ECX,6[ESI] ; 32-разрядный операнд.
MOV EAX,myList[EDI] ; 32-разрядный операнд.
При программировании в 16&разрядном режиме будьте внимательны и не используйте
32&разрядные значения индексных регистров, которые могут превысить допустимый
размер сегмента (0 -FFFFh).
MOV ESI,10000h
MOV AL,[ESI] ; выход за диапазон 16-разрядного режима
Базо*индексные операнды
Базо&индексные операнды добавляют значение регистра базы к значению индексного
регистра для получения смещения. Такой тип операнда часто используется при необхо&
димости доступа к двумерным массивам, когда базовый регистр содержит смещение
строки, а индексный &&&& смещение столбца. Программа, представленная в листинге 5.7,
использует массив 8&разрядных целых чисел. Если этот массив размещен по адресу 0150
Стр. 160
отсутствует. 161
array BX = 5
Стр. 161
rowsize = 5
MOV BX,rowsize ; Выбираем вторую строку.
MOV SI,2 ; Выбираем третий столбец.
MOV AL,array[BX+SI] ; Получаем величину смещения 0157.
На рис. 5.5 показаны значения регистров BX и SI для двумерного массива.
0150 0155 0157
10 20 30 40 50 60 70 80 90 A0
[BX] [BX+SI]
Стр. 162
отсутствует. 163
Команда XADD
Команда XADD для процессора 80486 суммирует значения операндов отправителя
и получателя и сохраняет значение в операнде&приемнике. В то же время исходное зна&
чение операнда&приемника перемещается в операнд&получатель. Используются флаги
переполнения, знака, нуля, дополнительного переноса, четности и переноса. Например,
можно добавить значение регистра BX к AX, сохранив значение, которое было в AX.
MOV AX, 1000h
MOV BX, 2000h
XADD AX, BX ; AX = 3000h, BX = 1000h.
Одной командой XADD можно заменить следующие три команды.
PUSH AX
ADD AX,BX
POP BX
Процедуры
Представленные в книге примеры содержали последовательности команд, выполняв&
шиеся один раз для решения определенной задачи. Однако если возникает необходимость
выполнить ту же последовательность команд в другом месте программы, то придется или
продублировать последовательность команд, или использовать обращение к процедурам.
Обычно дублирование неэффективно и значительно удлиняет программу, поэтому лучше
оформить повторяющуюся последовательность команд как процедуру. Процедура (или под&
программа) представляет собой совокупность команд, которая написана один раз, но при
необходимости может быть использована в любом месте программы.
Процесс передачи управления из основной части программы в процедуру называется
вызовом. При вызове процедуры микропроцессор исполняет ее команды, а затем воз&
вращается к тому месту, откуда был сделан вызов.
Команды, обеспечивающие исполнение процедур, должны выполнить три функции:
сохранить содержимое указателя команд IP. Когда процедура исполнена, находя&
щееся в этом указателе значение (адрес возврата) используется микропроцессором
для возврата к месту вызова;
заставить микропроцессор начать исполнение процедуры;
использовать сохраненное содержимое указателя команд IP для возврата в про&
грамму и обеспечить продолжение ее исполнения с этого места.
Все эти функции выполняются двумя командами: CALL (вызвать процедуру) и RET (возв&
ратиться из процедуры). Команда CALL осуществляет функции запоминания адреса возвра&
та и передачи управления процедуре.
Команда CALL имеет следующий формат.
CALL имя,
где имя &&&& имя вызываемой процедуры (т.е. адрес ее начала).
После сохранения адреса возврата команда CALL загружает смещение адреса метки
имя в указатель команд IP.
Команда RET заставляет микропроцессор возвратиться из процедуры в программу,
вызвавшую эту процедуру, восстанавливая все, что сохранила команда CALL. Команда
RET обязательно должна быть последней командой процедуры, исполняемой микропро&
цессором, но это не значит, что команда RET должна стоять в конце процедуры &&&& она
должна исполняться последней. Команда RET извлекает из стека адрес возврата.
Стр. 163
Например, для вызова процедуры my_proc из некоторого места вашей программы
может использоваться следующая последовательность команд.
04F0 CALL my_proc ; Вызвать процедуру.
04F3 NEXT: MOV AX,BX ; Вернуться из процедуры.
0500 my_proc PROC ; Начало процедуры.
0500 MOV CL,6 ; Первая команда процедуры.
О51Е RET ; Вернуться в вызвавшую программу.
051F my_proc ENDP ; Конец процедуры.
При исполнении команды CALL микропроцессор помещает в стек смещение адреса
метки NEXT (04F3H), затем загружает смещение адреса процедуры my_proc (0500H) в ука&
затель команд IP.
Содержимое регистра IP изменилось, поэтому микропроцессор продолжит исполне&
ние команды с новым смещением адреса. В нашем примере такой командой будет MOV CL,6.
Когда микропроцессор обнаруживает команду RET, он извлекает адрес возврата из
стека и помещает его в указатель команд IP. Это заставляет его возобновить исполнение
команды, имеющей метку NEXT.
Помимо прямого вызова команды CALL, о котором говорилось выше, можно делать
косвенные вызовы процедуры через регистр или ячейку памяти. При косвенных вызовах
через ячейку памяти микропроцессор извлекает значение указателя команд IP для про&
цедуры из сегмента данных, если только не используется регистр ВР или не указана заме&
на сегмента. Если для адресации ячейки памяти используется регистр ВР, то микропро&
цессор извлекает значение указателя команд IP из сегмента стека.
Процедуру можно вызвать косвенно, используя переменную размером в двойное сло&
во, например:
CALL DWORD PTR[BX]
CALL mem_dword
CALL DWORD PTR SS:variable_mame[SI]
Здесь первые две команды CALL извлекают адреса процедур из сегмента данных, а по&
следняя &&&& из сегмента стека.
Процедура может сама вызывать другие процедуры. Например, процедура чтения сим&
вола с клавиатуры может его декодировать и в зависимости от результата вызвать одну из
дополнительных процедур. Вызов одной процедуры из другой называется вложением проце%
дур. Так как каждая команда CALL помещает в стек два или четыре байта адреса, то число
уровней вложения ограничено только размером сегмента стека, но поскольку сегмент стека
может иметь размер до 64 Кбайт, то возможности вложения практически не ограничены.
Помимо команды CALL можно использовать директиву INVOKE, которая расширяет воз&
можности этой команды и позволяет удобным способом передавать в процедуру аргументы.
Более подробно использование процедур будет рассмотрено в следующей главе.
Резюме
В данной главе рассмотрены основные понятия языка ассемблера. Необходимо четко
представлять, какие бывают типы операндов, знать все операторы, основные директивы
и команды, без которых нельзя составить даже простейшую программу. В главе также
описаны базовые принципы построения программы и приведено несколько примеров
полностью работоспособных программ, внимание акцентируется на тех моментах, кото&
рые обычно не учитывают начинающие программисты.
Стр. 164
отсутствует. 165
Контрольные вопросы
1. Что такое переменная и как ее можно интерпретировать?
2. Для чего используются директивы размещения данных?
3. Сколько значений можно инициализировать с помощью одной директивы раз!
мещения данных?
4. Как можно представить строку символов?
5. Для чего используется оператор DUP?
6. Что такое указатель?
7. Как ассемблер сохраняет данные в памяти?
8. Что такое константа и как ее можно создать?
9. Можно ли использовать имя для строки?
10. Что определяет директива .MODEL?
11. Назовите шесть различных моделей памяти.
12. Для чего нужно разбивать программу на сегменты?
13. При какой модели памяти сегменты не используются?
14. Каково назначение оператора SIZE?
15. Зачем нужны директивы установки типа процессора?
16. Предположим, что указатель SP инициализирован значением 0100. Какой будет
абсолютный адрес первого слова, помещенного в стек, если сегмент кодов загру!
жен по абсолютному адресу 18400h?
17. Когда возникает ошибка несоответствия типов?
18. Что такое знаковое переполнение?
19. Какие типы операндов используются в ассемблере?
20. Что такое косвенный операнд?
21. Что такое выражение?
22. Какие группы операторов вы знаете?
23. Для каждой из приведенных переменных укажите значения TYPE, LENGTH и SIZE.
24. Почему оператор WORD PTR необходим в команде ADD WORD PTR [SI],5?
Стр. 165
Глава 6
Процедуры и прерывания
В этой главе...
Операции со стеком
Процедуры
Параметры процедур
Прерывания
Ввод на уровне BIOS
Видеоконтроль на уровне BIOS
Резюме
Контрольные вопросы
В предыдущей главе уже было кратко рассказано о процедурах и показан пример их ис&
пользования. Необходимость использования процедур возникает, как только программа
становится большой и трудно понимаемой, и поэтому ее приходится разбивать на от&
дельные логические фрагменты. В хорошо разработанной программе каждая процедура
выполняет отдельную задачу независимо от остальной части программы. Каждая проце&
дура подобна отдельной прикладной программе, которую можно использовать в разных
ситуациях и вызывать много раз.
Программу любого размера сложно писать как единую последовательность команд от
начала до конца, поэтому необходимо разбить ее на отдельные, компактные и хорошо пони&
маемые модули, что значительно облегчит работу по написанию, отладке и сопровождению.
Язык ассемблера не навязывает создание структурированных программ, но использование
принципов структурного программирования при написании программ на языке ассемблера
позволяет создавать простые для понимания и легко модернизируемые программы.
Большая часть этой главы посвящена специальному типу процедур, или подпро&
грамм, позволяющих программе взаимодействовать с другими устройствами. Эти под&
программы оказывают существенную помощь при работе с консолью, диском, принте&
ром при вводе&выводе или контроле времени и размещении памяти. Эти подпрограммы
позволяют не вдаваться в детали программирования на уровне аппаратуры и обеспечи&
вают работу программы на любых устройствах с процессорами архитектуры x86.
Операции со стеком
Стек &&&& это специальный буфер в оперативной памяти, который используется для вре&
менного хранения адресов и данных. Стек размещается в сегменте стека. К каждой 16&раз&
рядной (32&разрядной) ячейке стека можно обратиться с помощью регистра SP (ESP),
Стр. 166
отсутствует. 167
называемого указателем стека. Указатель стека содержит адрес последнего элемента, по
мещенного в стек. Последнее значение, добавленное в стек, является также первым значе
нием, которое может быть извлечено из стека. Для обозначения таких структур используют
аббревиатуру LIFO (Last In First Out), что означает ‘‘последним зашел первым вышел’’.
Команда PUSH
Эта команда копирует значение в стек. Когда новое значение помещается в стек, указа
тель стека (E)SP декрементируется до того, как значение будет помещено в стек (SP всегда
должен указывать на последнее помещенное значение). Использование команды PUSH
показано в следующем примере.
MOV AX,0A5h
PUSH AX
Команда PUSH не изменяет содержимое регистра AX, она только копирует значение
AX в стек.
Предположим, что значения регистров BX и CX равны 0001 и 0002. Ниже приведены
команды помещения их в стек.
PUSH BX ; Помещает BX в стек.
PUSH CX ; Помещает CX в стек.
Значения 0001 и 0002 будут помещены в стек, как показано на рис. 6.1.
Старшие адреса
006A
00B8
0001
0002 SP (указатель стека)
Младшие адреса
Команда PUSH декрементирует указатель стека (E)SP и копирует 16 или 32разряд
ные регистры или операнды памяти в стек по адресу, на который указывает указатель
стека. При использовании процессора 80286 и более поздних моделей в стек можно по
местить непосредственное значение. Ниже приведено несколько примеров.
PUSH AX ; Поместить в стек 16-разрядный регистр.
PUSH ECX ; Поместить в стек 32-разрядный регистр.
PUSH memval ; Поместить в стек 16-разрядный операнд памяти.
PUSH 1000h ; Поместить в стек значение: только 80286 и выше.
Команда POP
Данная команда извлекает значение из стека и помещает его в регистр или перемен
ную. После извлечения значения из стека указатель его инкрементируется и будет указы
вать на предыдущий элемент стека. Для приведенного выше примера указатель стека пе
реместится вверх и будет указывать на значение 0001. Пространство стека ниже
указателя становится неопределенным.
Стр. 167
Команда POP копирует содержимое ячейки, на которую указывает указатель стека
(E)SP, и инкрементирует значение указателя. Регистры CS и IP использоваться в каче&
стве операндов не могут.
POP CX ; Извлечение из стека в 16-разрядный регистр.
POP memval ; Извлечение из стека в 16-разрядный операнд памяти.
POP EDX ; Извлечение из стека в 32-разрядный регистр.
Приведем несколько правил использования стека в программах.
Стек отлично подходит для временного хранения значений всех регистров. Реги&
стры можно использовать как оперативное пространство в программе, а затем бы&
стро восстановить их состояние.
При вызове подпрограмм процессор сохраняет адрес возврата в стеке (место в прог&
рамме, из которого была вызвана подпрограмма).
Когда вызывается процедура, можно разместить аргументы в стеке. Аргументы (зна&
чения параметров) могут быть извлечены из стека вызванной процедурой. В язы&
ках высокого уровня параметры обрабатываются именно так.
Языки высокого уровня создают пространство в стеке для подпрограмм, назы&
ваемое стековым фреймом. В этом пространстве размещаются временные пере&
менные, создаваемые подпрограммой. При возврате из подпрограммы все перемен&
ные удаляются.
Далее показано, когда возникает необходимость в многократном использовании ре&
гистров. В следующем примере строки символов отображаются на экране. Предполагает&
ся, что регистры EDX и EAX содержат важные значения, которые должны быть восстанов&
лены после отображения строки на экране. Стек является структурой LIFO, поэтому
последний сохраненный регистр будет восстановлен первым.
.DATA
message DB “Это сообщение.”,0
.CODE
PUSH EAX ; Сохранение EAX.
PUSH EDX ; Сохранение EDX.
MOV EDX,OFFSET message ; DX указывает на строку.
CALL Writestring ; Отображение строки.
POP EDX ; Восстановление EDX.
POP EAX ; Восстановление EAX.
Стр. 168
отсутствует. 169
регистры в обратном порядке. Команда PUSHAD (Intel 386 и поздние модели) сохраняет
в стеке регистры EAX, ECX, EDX, EBX, ESP, EBP, EDI, а восстанавливаются они коман&
дой POPAD.
Процедуры
Сначала остановимся на общей терминологии. В языках высокого уровня термином
функция обозначают подпрограмму, которая возвращает значение, а слово процедура
обычно используется для подпрограмм, которые не должны возвращать значение. Под&
программа &&&& более широкое понятие и обычно означает блок команд, которые могут
быть вызваны из другого места памяти. Вызов подпрограммы подразумевает, что обяза&
тельно будет совершен возврат из подпрограммы. Это не переход, возврат из которого не
требуется. В языке ассемблера термины подпрограмма, процедура и функция обычно име&
ют один и тот же смысл, хотя в некоторых случаях учитывается их смысловое различие.
mySub PROC
.
.
RET
mySub ENDP
Простая программа
Ниже приведена небольшая рабочая программа, в которой производится вызов трех
процедур. Процедура KeyCode получает символ с клавиатуры и помещает его характери&
стики в регистры AL, AH и DX.
Процедура KeyChar просто считывает символ нажатой клавиши и отображает его на
экране, а процедура Zapros используется для отображения на экране приглашения для
ввода символа.
В данном случае возникает несогласованность между кодировками Windows и DOS,
которые используются при компиляции и отображении в окне консоли. Поэтому вводите
текст английскими буквами. Для перекодировки текста можно использовать процедуру
CharToOemA. Например:
PUSH OFFSET STR1
PUSH OFFSET STR1
CALL CharToOemA
Стр. 169
Листинг 6.2. Демонстрация вызовов подпрограмм
TITLE Демонстрация вызовов подпрограмм.
; В этой программе используется три процедуры: две для
: ввода с клавиатуры и одна для вывода приглашения.
.386
include Study32.inc
.data
str1 byte "ascii = ",0
str2 byte "scan = ",0
str3 byte "key = ",0
ascii byte ?
scan byte ?
key word ?
.code
main PROC
call Zapros
call KeyChar
call KeyCode
invoke WaitMsg
exit
main endp
KeyCode PROC
LookForKey:
mov eax,50 ; Время задержки.
call Delay ; Задержка для ОС.
call ReadKey ; Считывание клавиши.
jz LookForKey ; Нет нажатия.
Стр. 170
отсутствует. 171
Zapros PROC
.data
zapr byte "Press any key ",0
.code
mov edx, offset zapr ; Вывод приглашения.
invoke WriteString
invoke Crlf
ret
Zapros endp
KeyChar PROC
invoke ReadChar
invoke WriteChar
invoke Crlf
ret
KeyChar endp
end main
Разберем подробнее эту программу. Хорошо видно, что сама программа занимает
всего несколько строчек между ее началом (main PROC) и окончанием (main endp).
Все остальное &&&& это процедуры, написанные специально для этой программы, но кото&
рые можно использовать и в других программах. Поэтому в конечном итоге их необходи&
мо будет вынести в отдельную библиотеку и применять при необходимости. При этом не
надо будет вновь писать код процедуры.
Также обратите внимание на повторяющуюся три раза последовательность из строк:
mov edx, offset str2 ; Вывод str2.
invoke WriteString
mov eax,0
mov al,scan
invoke WriteInt ; Вывод скан-кода.
invoke Crlf
Очевидно, что такое повторение увеличивает размеры листинга и затрудняет понима&
ние всей программы. Лучше повторяющуюся последовательность строк оформить как
макроопределение с соответствующим информативным именем.
mToScreen MACRO subject:REQ, value:REQ
mov edx, offset subject ; Вывод обозначения.
invoke WriteString
movzx ax,value
invoke WriteInt ; Вывод значения.
invoke Crlf
endm
Теперь программа будет выглядеть следующим образом.
.386
include Study32.inc
.data
str1 byte "ascii = ",0
Стр. 171
str2 byte "scan = ",0
str3 byte "cod = ",0
ascii word ?
scan word ?
key word ?
.code
main PROC
call Zapros
call KeyChar
call KeyCode
invoke WaitMsg
exit
main endp
KeyCode PROC
LookForKey:
mov eax,50 ; Время задержки.
call Delay ; Задержка для ОС.
call ReadKey ; Считывание клавиши.
jz LookForKey ; Нет нажатия.
mToScreen str1,ascii
mToScreen str2,scan
mToScreen str3,key
ret
KeyCode endp
Zapros PROC
.data
zapr byte "Press any key ",0
.code
mov edx, offset zapr ; Вывод приглашения.
invoke WriteString
invoke Crlf
ret
Zapros endp
KeyChar PROC
invoke ReadChar
invoke WriteChar
invoke Crlf
ret
KeyChar endp
end main
Стр. 172
отсутствует. 173
Стр. 173
Параметры процедур
Передача аргументов в регистры
В языке ассемблера наиболее общим способом для передачи аргументов в подпро&
граммы является размещение аргументов в регистрах. Этот метод эффективен, так как
вызванная подпрограмма может непосредственно использовать переданные значения,
и при этом обращение к регистрам выполняется значительно быстрее обращения к памя&
ти. Например, если некоторая процедура writeint требует, чтобы регистр EAX содержал
32&разрядное целое число, а EBX включал основание системы счисления, то вызов дол&
жен производиться так, как показано в примере ниже.
.DATA
aNumber DWORD ?
.CODE
MOV EAX,aNumber
MOV EBX,10
CALL writeint
Процедура должна отвечать за защиту значений, размещенных в регистрах, от слу&
чайного изменения. Это гарантирует, что вызывающая программа не столкнется с не&
ожиданным изменением состояния регистров. Всегда необходимо соблюдать следующее
важное правило: при написании процедур следует сохранять и восстанавливать все реги&
стры, которые могут изменяться в процедуре. Не пытайтесь игнорировать это правило
в интересах повышения скорости работы программы из&за того, что в подпрограмме нет
явной необходимости использовать некоторые регистры &&&& при необходимости модифи&
кации подпрограммы придется использовать дополнительные регистры, которые могут
непредвиденно изменяться в процедуре.
При написании процедур следует сохранять и восстанавливать все регистры, которые мо&
гут изменяться в процедуре.
Прерывания
Обобщающий термин прерывание используется для двух случаев: аппаратное прерыва%
ние &&&& это сигнал от любого устройства системы для процессора, который по этому сиг&
налу должен обслужить данное устройство, и программное прерывание, которое создается
программами BIOS или DOS для вызова сервисных подпрограмм.
Стр. 174
отсутствует. 175
Команда INT
Команда прерывания INT используется только при программировании в 16&разрядном
режиме и вызывает подпрограммы операционной системы. В 32&разрядном режиме необ&
ходимо использовать соответствующие процедуры и функции операционной системы, ко&
торые и обращаются напрямую к отдельным прерываниям. Эти прерывания имеют но&
мера в диапазоне от 0 до FFh. Перед тем как вызвать команду INT, в регистр обычно
помещают номер функции, который определяет необходимую подпрограмму. Другие
регистры тоже могут использоваться в прерывании. Синтаксис прерывания такой:
INT номер.
Обычно команды INT используются для консольного ввода&вывода, управления фай&
лами и выводом видеоизображения, а также во многих других случаях.
Стр. 175
Вызывающая Память Обработчик
программа прерывания
STI
CLD
MOV ...
PUSH ES
INT 10 F000:F065
ADD ...
IRET
Стр. 176
отсутствует. 177
Клавиша Insert
Клавиша CapsLock
Клавиша NumLock
Клавиша ScrollLock
Клавиша Alt
Клавиша Ctrl
7 6 5 4 3 2 1 0
Стр. 177
Они используются для управления выводом на экран, принтер или асинхронные устрой&
ства связи. Описание некоторых управляющих символов приведено в табл. 6.2.
08h 08 Возврат
09h 09 Горизонтальная табуляция
0Ah 10 Пропуск строки
0Ch 12 Подача страницы (для принтера)
0Dh 13 Возврат каретки (клавиша <Enter>)
1Bh 27 Переход
Символы 0Dh и 0Ah должны находиться в конце каждой строки текстового файла, так
как они управляют возвратом каретки и пропуском строки. При выводе на экран символ
возврата каретки перемещает курсор в левую сторону экрана, а символ пропуска строки пе&
ремещает курсор на одну строку вниз. Этот символ необходим, когда происходит последо&
вательный вывод строк (при его отсутствии строки будут накладываться друг на друга).
Стр. 178
отсутствует. 179
не были очень высокими. С 1991 года все компьютеры стали поставляться с различными
типами графических дисплеев VGA, которые с высоким разрешением воспроизводили
как текст, так и графику.
Видеорежимы
В основном используются два типа режимов: текстовый и графический. Текстовый ре&
жим применяется при работе в DOS, компьютер может отображать только символы в соот&
ветствии с расширенным набором символов. При этом можно использовать специальные
графические символы, которые позволяют изображать простейшие графические образы.
В режиме графики компьютер может управлять отдельными пикселями (точками), позво&
ляя рисовать линии, окружности и отображать сложные графические образы.
Видеоатрибуты
При работе в консольном режиме каждая позиция экрана может содержать отдельный
символ с собственными атрибутами. Для хранения атрибутов используется отдельный байт,
называемый байтом атрибутов. В прерывании 10h есть функция, которая позволяет ус&
тановить атрибуты (например, атрибут цвета, негативного отображения, мигания, под&
черкивания и яркости).
Видеорежим 7 отображает монохромный текст. Доступные атрибуты режима 7 пока&
заны в табл. 6.3. Например, обычное мигание записывается как 87h, яркое мигание &&&&
как 8Fh, негативное изображение &&&& как 0F0h и т.д. В то же время сам символ можно
сделать ярким, установив в третьем бите единицу.
07 Нормальный
87 Мигание
0F Яркий (подсветка)
70 Негатив
01 Подчеркивание
09 Яркое подчеркивание
Стр. 179
0 0 0 1 0 1 1 1
000 00 черный
001 01 синий
010 02 зеленый
011 03 голубой
100 04 красный
101 05 вишневый
110 06 коричневый
111 07 белый
Стр. 180
отсутствует. 181
Видеостраницы
Все графические адаптеры с использованием цвета могут сохранять в памяти несколько
изображений экрана, называемых страницами. Монохромные адаптеры могут отобра&
жать только одну страницу. Графический адаптер с использованием цвета может про&
изводить запись в одну страницу, в то время как на экран выводится другая страница,
и мгновенно переключаться между страницами. Страницы имеют номера от 0 до 7, и ко&
личество страниц зависит от текущего видеорежима (табл. 6.6).
0 07h Mono
0&&7 00h, 01h CGA
0&&3 02h, 03h CGA, EGA, VGA
0&&7 02h, 03h CGA, EGA, VGA
0&&7 0Dh EGA, VGA
0&&3 0Eh EGA, VGA
0&&1 0Fh, 10h EGA, VGA
Стр. 181
Таблица 6.7. Список функций прерывания INT 10h
Номер функции (AH) Описание
Стр. 182
отсутствует. 183
03h (получить позицию курсора). Функция возвращает номер строки и номер столбца
курсора на заданной видеостранице. Это же касается начальной и конечной линий, оп&
ределяющих размер курсора. Необходимо установить 3 в регистре AH, а номер видеостра&
ницы в регистре BH. Возвращаемые значения приведены в табл. 6.8.
Стр. 183
MOV AH,5 ; Установить видеостраницу.
MOV AL,1 ; К странице 1.
INT 10h
MOV AH,9 ; Отобразить сообщение.
MOV DX,OFFSET page1
INT 21h
MOV AH,1 ; Нажатие клавиши.
INT 21h
to_page_0:
MOV AH,5 ; Установить видеостраницу.
MOV AL,0 ; К странице 0.
INT 10h
MOV AX,4C00h ; Возврат в DOS.
INT 21h
main ENDP
END main
06h, 07h (прокрутка окна вверх и вниз). С помощью этой функции можно прокручивать
окно экрана. Термин прокрутка окна означает перемещение данных на дисплее вверх или
вниз. Если, например, изображение на дисплее прокручивается вверх, то нижние линии
заменяются пустым пространством.
Размер окна зависит от используемого количества строк и столбцов. Строки опреде&
ляются номерами 0-24 от верхней границы, а столбцы определяются номерами 0-79 от
левой границы. Координаты окна, покрывающего весь экран, будут иметь значения для
верхнего левого угла (0, 0) и нижнего правого (24, 79). При прокрутке одной или не&
скольких строк перемещаются их номера. Если прокрутить все строки, экран будет чис&
тым. Строки, вышедшие за пределы окна, не восстанавливаются. Входные параметры
для вызова функций 6 и 7 показаны в табл. 6.9.
08h (чтение символа и атрибутов). Функция возвращает символ и его атрибуты в те&
кущей позиции курсора на выбранной видеостранице. Для вызова этой функции в ре&
гистр AH помещается значение 8, а в BH &&&& номер видеостраницы. Следующие команды
помещают курсор в строку 8 и столбец 1, после чего считывают символ.
locate:
MOV AH,2 ; Установить курсор
MOV BH,0 ; на видеостранице 0
MOV DX,0501h ; в позицию 5,1.
INT 10h
getchar:
MOV AH,8 ; Считать атрибуты/символ
MOV BH,0 ; на видеостранице 0.
INT 10h
MOV CHAR,AL ; Сохранить символ.
MOV attrib,AH ; Сохранить атрибуты.
Стр. 184
отсутствует. 185
09h (записать символ и атрибуты). Функция используется для записи одного или не&
скольких символов с текущей позиции курсора. Эта функция может отобразить любой
символ в кодировке ASCII, включая специальные графические символы для кодов 1-31.
Ни один из этих символов не интерпретируется как управляющий код, что характерно
для прерывания INT 21h в DOS. Входные параметры приведены в табл. 6.10.
Фактор повторения определяет, сколько раз символ должен быть повторен. (Символ
не должен выходить за границу текущей строки экрана.) После записи символа необхо&
димо вызвать функцию 02h для перемещения курсора.
В следующих командах 32 раза записывается графический символ, определяемый ко&
дом 0Ah с атрибутами мигания. Символ 0Ah в кодировке ASCII определяет пропуск
строки, но в данном случае он отображается как белая точка на черном фоне.
MOV AH,9 ; Запись символа и атрибутов.
MOV AL,0Ah ; Символ 0Ah в кодировке ASCII.
MOV BH,0 ; Видеостраница 0.
MOV BL,87h ; Атрибуты мигания.
MOV CX,32 ; Отобразить 32 раза.
INT 10h
OAh (записать символ). Функция 0Ah выводит символ на экран в текущей позиции курсора
без изменения текущих атрибутов экрана. Эта функция подобна функции 09h, но без уста&
новки атрибутов. Следующие команды записывают символ A в текущую позицию курсора.
MOV AH,0Ah ; Записать только символ.
MOV AL,'A' ; Символ 'A'.
MOV BH,0 ; Видеостраница 0.
MOV CX,1 ; Отобразить один раз.
INT 10h
OCh (записать графический пиксель). Функция 0Ch рисует один пиксель на экране,
присваивая пикселю значение, номер видеостраницы, строку и столбец.
MOV AH,0Ch
MOV AL,pixelValue
MOV BH,videoPage
MOV EX,column ; Координата X.
MOV DX,row ; Координата Y.
INT 10h
ODh (читать графический пиксель). Функция 0Dh считывает графический пиксель
с экрана в определенной позиции строки и столбца, после чего возвращает его значение
в регистр AL.
MOV AH,0Dh
MOV BH,videoPage
MOV EX,column
Стр. 185
MOV DX,row
INT 10h
MOV pixelValue,AL
OFh (получить видеорежим). Функция 0Fh возвращает число столбцов в регистр AH,
текущий режим дисплея &&&& в AL, активную страницу &&&& в BH. (Список общих видеоре&
жимов приведен в подразделе ‘‘Видеорежимы’’.) Эта функция должна вызываться перед
выполнением любых графических функций или специфических операций с цветом. В сле&
дующих командах определяется текущий видеорежим и активная страница.
MOV AH,0fh ; Получить видеорежим.
INT 10h
MOV vmode,AL ; Сохранить режим.
MOV page,BH ; Сохранить страницу.
11h (загрузка шрифтов по умолчанию). Усовершенствованные EGA& и VGA&дисплеи,
позволяют загружать и отображать различные шрифты в текстовом режиме. С дисплеем
VGA можно использовать 25 строк с символами размером 8×16 и 28 строк с символами
размером 8×14, а также 50 строк с символами 8×8.
Для загрузки шрифтов используется функция 11h из прерывания INT 10h и под&
функции 11h, 12h или 14h для установки шрифта. Подфункция шрифтов загружается
в регистр AL.
eightByFourteen EQU 11h ; 28 строк/экран.
eightByEight EQU 12h ; 50 строк/экран.
eightBySixteen EQU 14h ; 25 строк/экран.
Резюме
В данной главе рассмотрены принципы построения программ с использованием проце&
дур и прерываний. Основное внимание уделено 16&разрядному режиму, так как только
в этом режиме можно непосредственно обращаться к ресурсам компьютера и лучше понять
работу прерываний и графики. При программировании для 32&разрядного режима обраще&
ние к прерываниям выполняется через процедуры Windows. Необходимо уметь грамотно
разбивать программу на отдельные функциональные блоки и научиться разрабатывать
программы по частям, отлаживая отдельные процедуры и используя их в дальнейшем. На&
личие достаточного количества отлаженных процедур позволит быстро и без ошибок пи&
сать довольно сложные программы. В главе также кратко рассмотрены функции работы
с консолью, с помощью которых можно не только выполнять ввод с клавиатуры или вывод
на дисплей, но и производить графическое оформление интерфейса пользователя.
Стр. 186
отсутствует. 187
Контрольные вопросы
1. Для чего нужны подпрограммы сервисных прерываний?
2. На какую ячейку указывает указатель стека?
3. Чем отличается программное прерывание от аппаратного?
4. Какими могут быть наименьший и наибольший номера прерываний?
5. На что указывают значения, помещенные в таблицу векторов прерываний?
6. Почему прикладные программы непосредственно не управляют аппаратными
устройствами?
7. Какое прерывание обслуживает клавиатуру? Какие возможности при этом пре&
доставляются?
8. Что такое скан&код клавиатуры? Чем он отличается от кода ASCII?
9. Какой управляющий символ в кодировке ASCII перемещает курсор в левую сто&
рону экрана?
10. Назовите, по крайней мере, четыре атрибута, которые можно использовать с
адаптером для цветного дисплея.
11. Какая функция из прерывания INT 10h отображает символ на экране без изме&
нения атрибутов экрана?
12. Какая функция прерывания INT 10h устанавливает видеорежим?
13. Когда значение помещается в стек, размер стека в памяти увеличивается. В сто&
рону каких адресов увеличивается размер стека: старших или младших?
14. Почему структура стека называется LIFO?
Стр. 187
Глава 7
Логические вычисления
В этой главе...
Стр. 188
отсутствует. 189
Операторы AND, OR, NOT и XOR выполняют поразрядные операции для целых чисел во
время трансляции. Операнд может быть целой константой или символом. Например, до&
пустимы следующие выражения:
X = 1101101b
Y = X AND 00001111b
Z = X OR 11110000b
Q = NOT Z
W = Y XOR 0FFFFh
Команда AND
Команда AND выполняет логическое умножение (И) для каждого разряда двух операн&
дов и помещает результат в первый операнд. Разрешены следующие комбинации.
AND регистр, регистр
AND регистр, память
AND регистр, значение
AND память, регистр
AND память, значение
Регистр, память или значение могут иметь 8, 16 или 32 разряда, при этом операторы
должны иметь одинаковый размер. Для каждых соответствующих битов в двух операндах
применимо следующее правило: если оба бита равны 1, то результирующий бит равен 1,
в противном случае он равен 0. Используются следующие флаги: переполнения, знака,
нуля, паритета, переноса и дополнительного переноса. В следующем примере показан
результат операции AND для 4&разрядных чисел.
0 0 1 1
AND 0 1 0 1
Результат: 0 0 0 1
С помощью команды AND можно очищать отдельные биты в операнде, при этом за&
щищая или маскируя оставшиеся биты. В следующем примере значение 00001111 явля&
ется битовой маской.
MOV AL,00111011b
AND AL,00001111b ; AL = 00001011b
Операцию AND можно использовать для установки байта состояния клавиатуры, для DOS
который может размещаться по адресу 0040:0017. Установленный в единицу бит 5 оп&
ределяет состояние клавиши <NumLock> как включенное. Можно выключить клавишу
<NumLock>, просто сбросив бит 5 (установив в нуль):
MOV BX,17h ; Указать на байт клавиатуры.
AND BYTE PTR [BX],11011111b ; Выключить NumLock.
Здесь требуется оператор BYTE PTR, без которого ассемблер не может определить раз&
мер операнда памяти.
Команда OR
Команда OR выполняет операцию логического сложения (ИЛИ) между соответствую&
щими битами двух операндов и помещает результат в первый операнд. Следующие ком&
бинации операндов разрешены.
Стр. 189
OR регистр, регистр
OR регистр, память
OR регистр, значение
OR память, регистр
OR память, значение
Регистр, память или значение могут иметь 8, 16 или 32 разряда, кроме того, операторы
должны иметь одинаковый размер. Для каждых соответствующих битов в двух операндах
применимо следующее правило: если оба бита равны 0, то и результирующий бит равен 0,
в противном случае результат равен 1. Используются флаги переполнения, знака, нуля, па&
ритета, переноса и дополнительного переноса. В следующем примере показан результат
операции OR для 4&разрядных чисел.
0 0 1 1
OR 0 1 0 1
Результат: 0 1 1 1
Например, применим команду OR к значениям 3Bh и 0Fh. Нижние четыре бита уста&
новятся, а старшие биты не изменятся.
MOV AL,00111011b ; 3Bh.
OR AL,00001111b ; AL = 3Fh.
Такой способ можно использовать для преобразования десятичного числа в кодиров&
ку ASCII (табл. 7.2.), устанавливая биты 4 и 5. Например, AL=05h можно преобразовать
в символ 5 согласно кодировке ASCII (35h) с помощью команды OR с числом 30h.
Стр. 190
отсутствует. 191
Команда XOR
Команда XOR (исключающее ИЛИ) выполняет логические операции между соответст&
вующими битами двух операндов. Следующие комбинации операндов разрешены.
XOR регистр, регистр
XOR регистр, память
XOR регистр, значение
XOR память, регистр
XOR память, значение
Первый операнд содержит результат операции. Только один операнд может быть опе&
рандом памяти. Для каждых соответствующих битов в двух операндах применимо следую&
щее правило: если оба бита одинаковы, то и результирующий бит равен 0, в противном слу&
чае результат будет равен 1. Используются флаги переполнения, знака, нуля, паритета,
переноса и дополнительного переноса.
Ниже показан результат операции XOR для отдельных битов. Команда XOR устанавли&
вает бит в единицу, только если исходные биты различны:
0 0 1 1
XOR 0 1 0 1
Результат: 0 1 1 0
Инверсия бит
Отличительной особенностью команды XOR является то, что она может инвертиро&
вать саму себя, когда применяется дважды. Предположим, к числу x применяется коман&
да XOR с числом y, в результате получается число z. Затем, если применить операцию XOR
к числам z и y, получим x. Например:
1 1 0 1 0 1 0 1 x
XOR 1 1 1 1 0 0 0 0 y
Равно: 0 0 1 0 0 1 0 1 z = x XOR y
XOR 1 1 1 1 0 0 0 0 y
Равно: 1 1 0 1 0 1 0 1 x = z XOR y
Команда NOT
Команда NOT инвертирует все биты исходного операнда, изменяя нули на единицы
и наоборот. Результат называется дополнение до единицы. Следующие операнды разрешены.
NOT регистр
NOT память
Стр. 191
Например, дополнение до единицы числа F0h будет равняться 0Fh.
MOV AL,11110000b
NOT AL ; AL = 00001111b.
Команда NEG
Команда NEG инвертирует знак числа, преобразовывая число в его дополнение до двух.
Следующие команды разрешены.
NEG регистр
NEG память
Подсчет дополнения до двух от числа может быть выполнен путем инверсии всех его
битов и суммированием с единицей. Используются следующие флаги: переполнения, ну&
ля, знака, дополнительного переноса и переноса.
После выполнения команды NEG проверьте флаг переполнения для определения пра&
вильности результата. Например, если поместить в регистр AL отрицательное число -128,
то в результате операции получим -128 (неправильный результат) и флаг 0F = 1:
MOV al,-128 ; AL = 10000000b
NEG al ; AL = 10000000b, 0F = 1
С другой стороны, если инвертировать +127, то результат будет правильный и 0F = 0:
MOV al,+127 ; AL = 01111111b
NEG al ; AL = 10000001b, 0F = 0
Команда TEST
Команда TEST выполняет команду AND для двух операндов, но не сохраняет резуль&
тат, а только устанавливает флаги. Это особенно удобно для контроля отдельных битов.
Ни один из операндов не изменяется. Следующие команды разрешены.
TEST регистр, регистр
TEST регистр, память
TEST регистр, значение
TEST память, регистр
TEST память, значение
Если любые соответствующие биты установлены в обоих операндах, ZF = 0. Использу&
ются следующие флаги: переполнения, нуля, знака, дополнительного переноса и переноса.
Например, проверить состояние принтера в системе DOS можно с помощью преры&
вания INT 17h, которое проверяет состояние принтера и возвращает байт состояния в ре&
гистр AL. Если пятый бит равен единице, то в принтере нет бумаги. Следующие команды
выполняют проверку и сбрасывают флаг нуля, если установлен бит 5:
MOV AH,2 ; Функция: чтение состояния принтера.
INT 17h ; Вызов BIOS.
TEST AL,00100000b ; ZF = 0, нет бумаги.
Стр. 192
отсутствует. 193
Команда CMP
Команда сравнения CMP выполняет вычитание операнда&отправителя из операнда&
получателя, но при этом ни один из операндов не модифицируется. Результат отражается
в состоянии регистра флагов. Разрешены следующие комбинации операндов, где первый
операнд всегда является операндом&получателем, а второй &&&& операндом&отправителем.
CMP регистр, регистр
CMP регистр, память
CMP регистр, значение
CMP память, регистр
CMP память, значение
Стр. 193
Регистры сегментов не могут быть использованы. Применяются следующие флаги:
переполнения, нуля, знака, дополнительного переноса и переноса.
Когда сравниваются значения без знака, флаги нуля и переноса устанавливаются ко&
мандой CMP в соответствии с табл 7.4.
Команда CMPXCHG
Команда сравнения и изменения CMPXCHG для процессора 80486 сравнивает оператор&
получатель с аккумулятором (AL, AX или EAX). Если значения равны, значение оператора&
отправителя копируется в оператор&получатель, в противном случае оператор&получатель
копируется в аккумулятор. Используются все флаги состояния. Формат команды:
CMPXCHG получатель, отправитель
Стр. 194
отсутствует. 195
Логические структуры
Речь идет не о логических структурах высокого уровня, хотя с помощью системы ко&
манд ассемблера можно смоделировать любую логическую структуру, используя команды
сравнений и переходов. Логическая структура &&&& это лишь несколько логических ко&
манд, работающих совместно. Например, структура WHILE использует условие IF для
оценки условия, как показано в табл. 7.6.
Стр. 195
основе состояния флагов. В следующем примере команда JZ переходит на метку next,
если AL = 0.
CMP AL,0
JZ next ; Переход, если ZF = 1.
...
next:
Команда Jcond
Команда перехода по условию передает контроль по указанному адресу, когда при&
знак условия установлен. Синтаксис этой команды следующий:
Jcond метка
Суффикс cond определяет признак условия, идентифицируя состояние одного или
нескольких флагов. Например:
JC ; Переход, если флаг переноса установлен.
JNC ; Переход, если флаг переноса сброшен.
JZ ; Переход, если флаг нуля установлен.
JNZ ; Переход, если флаг нуля сброшен.
При каждом условном переходе проверяются один или несколько флагов, возвращая
результат (истина или ложь). Если результатом является истина, то переход совершается,
в противном случае программа продолжает исполнение со следующей команды.
Предположим, необходимо совершить переход на метку equal, когда значения реги&
стров AX и BX равны. В следующем примере CMP устанавливает флаг нуля в единицу, если
AX = BX. Команда JE делает переход, если ZF = 1.
1: CMP AX,BX ; Сравнение AX и BX.
2: JE equal
3: NOT_equal: ; Продолжение здесь, если AX <> BX.
4: ...
5: ...
6: JMP exit
7: equal: ; Переход, если AX = BX.
8: ...
9: exit: ; Окончание всегда здесь.
Для того чтобы лучше понять эту программу, используем различные значения для AX и BX.
Если AX = 5 и BX = 5, то команда CMP (строка 1) устанавливает флаг нуля, так как AX
и BX равны. Команда JE (строка 2) делает переход на метку equal в строке 7. Все команды
с этого места точки выполняются последовательно.
Если AX = 5 и BX = –6, то команда CMP сбрасывает флаг нуля, так как AX и BX не рав&
ны. Команда JE не выполняется, поэтому выполнение продолжается со строки 3, проходя
последовательно все команды до строки 6, а потом происходит переход на метку exit.
Стр. 196
отсутствует. 197
Стр. 197
Окончание табл. 7.9
Мнемокод Описание Флаги
JL Переход, если меньше (op1<op2) SF<>OF
JNGE Переход, если не больше или равно (op1 NOT>=op2) SF<>OF
JLE Переход, если меньше или равно (if op1<=op2) ZF=1 или SF<>OF
JNG Переход, если не больше (op1 NOT>op2) ZF=1 или SF<>OF
JS Переход, если отрицательное (op1 отрицательное) SF=1
JNS Переход, если положительное (op1 положительное) SF=0
JO Переход, если переполнение ОF= 1
JNO Переход, если нет переполнения ОF = 0
Стр. 198
отсутствует. 199
Стр. 199
; Процедура isAlpha устанавливает ZF = 1,
; если символ в AL является буквой.
isAlpha PROC
PUSH AX ; Сохранить AX.
AND AL,11011111b ; Преобразование в прописной символ.
CMP AL,'A' ; Проверка диапазона'A'...'Z'.
JB B1
CMP AL,'Z'
JA B1
TEST AX,0 ; ZF = 1.
B1:
POP AX ; Восстановление AX.
RET
isAlpha ENDP
END main
Стр. 200
отсутствует. 201
L3:
ADD EDI,4 ; Указать следующий номер.
LOOP L1 ; Повторять, пока CX не равно 0.
MOV EAX,largest
INVOKE WriteInt ; Вывод значения largest.
INVOKE Crlf
INVOKE WaitMsg
EXIT
main ENDP
END main
Команда SETcond
Команда SETcond для процессоров 80386 устанавливает в байт операнда 1, если усло&
вие истинно, и 0 &&&& если ложно. Синтаксис:
SETcond регистр8
SETcond память8
Слово cond здесь условно обозначает флаг, который процессор должен проверить.
Параметр регистр8 может быть любым 8&разрядным регистром, а память8 &&&& любой 8&
разрядный операнд памяти. Возможные значения для cond включают суффиксы команд
условных переходов, как показано в табл. 7.10 и 7.11.
В следующем примере регистр AL=1 после команды SETB, так как команда CMP уста&
навливает флаг переноса.
MOV BX,20h
CMP BX,30h
SETB AL ; AL = 1
Стр. 201
С другой стороны, AL=0 после SETE, так как команда CMP не устанавливает флаг нуля.
MOV BX,20h
CMP BX,30h
SETE AL ; AL = 0
Циклы с условием
Команды LOOPZ и LOOPE
Команды LOOPZ и LOOPE (повторять пока установлен флаг нуля и счетчик больше ну&
ля) разрешают повторение цикла, пока ZF=1 и CX>0. Синтаксис этих команд следующий.
LOOPZ метка
LOOPE метка
Сначала декрементируется CX. Затем, если CX>0 и ZF=1, процессор совершает переход
на метку, в противном случае начинает выполняться следующая команда.
В листинге 7.4