Регистровый операнд всегда начинается с символа «%»:
// xor edx,edx xorl %eax,%eax
Непосредственный операнд всегда начинается с символа «$»:
// mov edx,offset variable movl $variable,%edx
Косвенная адресация использует немодифицированное имя переменной:
// push dword ptr variable pushl variable
Более сложные способы адресации удобнее рассматривать как варианты максимально сложного способа — по базе с индексированием, и сдвигом:
// mov eax,base_addr[ebx+edi*4] (наиболее общий случай) movl base_addr(%ebx,%edi,4),%еах // lea eax,[eax+eax*4] leal (%еах,%еах,4),%еах // mov ax,word ptr [bp-2] movw -2(%ebp),%ax // mov edx,dword ptr [edi*2] movl (,%edi,2),%edx
Повторить блок программы указанное число раз:
.rept число повторов .endr
Повторить блок программы для всех указанных значений символа:
.irp симол, значение... .endr
Повторить блок программы столько раз, сколько байт в строке, устанавливая символ равным каждому байту по очереди:
.irpc символ, строка .endr
Внутри блока повторения на символ можно ссылаться, начиная его с обратной косой черты, то есть как \символ, например такой блок:
.irp param,1,2,3 movl %st(0),%st(\param) . endr
как и такой:
.irpc param,123 movl %st(0),%st(\param) .endr
ассемблируется в:
movl %st(0),%st(1) movl %st(0),%st(2) movl %st(0),%st(3)
Все директивы ассемблера в UNIX всегда начинаются с символа «.» (точка). Из-за большого числа отличающихся операционных систем и ассемблеров для них возникло много часто встречающихся директив. Рассмотрим только наиболее полезные.
Эти директивы эквивалентны директивам db, dw, dd, df и т.п., применяющимся в ассемблерах для DOS/Windows. Основное отличие здесь состоит в том, чтобы дать имя переменной, значение которой определяется такой директивой; в ассемблерах для UNIX обязательно надо ставить полноценную метку, заканчивающуюся двоеточием.
Байты:
.byte выражение...
Слова:
.word выражение... или .hword выражение... или .short выражение...
Двойные слова:
.int выражение... или .long выражение...
Учетверенные слова (8-байтные переменные):
.quad выражение...
16-байтные переменные (окта-слова):
. octa выражение...
32-битные числа с плавающей запятой:
.float число... или .single число...
64-битные числа с плавающей запятой:
.double число...
80-битные числа с плавающей запятой:
.tfloat число...
Строки байтов:
.ascii строка...
Строки байтов с автоматически добавляемым нулевым символом в конце:
.asciz строка... или .string строка
Блоки повторяющихся данных:
.skip размер,значение или .space размер,значение
Заполняет области памяти указанного размера байтами с заданным значением
.fill повтор, размер, значение
Заполняет область памяти значениями заданного размера (0 – 8 байт) указанное число раз. По умолчанию размер принимается равным 1, а значение — 0.
Неинициализированные переменные:
.lcomm символ, длина, выравнивание
Зарезервировать указанное число байт для локального символа в секции .bss.
Текст программы делится на секции — кода, данных, неинициализированных данных, отладочных символов и т.д. Секции также могут делиться далее на подсекции, располагающиеся непосредственно друг за другом, но это редко используется.
.data подсекция
Следующие команды будут ассемблироваться в секцию данных. Если подсекция не указана, данные ассемблируются в нулевую подсекцию.
.text подсекция
Следующие команды будут ассемблироваться в секцию кода.
.section имя, флаги, @тип или .section "имя", флаги
Общее определение новой секции:
флаги (для ELF):
w или #write — разрешена запись;
х или #execinstr — разрешено исполнение;
а или #alloc — разрешено динамическое выделение памяти (.bss);
тип (для ELF):
©progbits — содержит данные;
@nobits — не содержит данные (только занимает место).
Включить текст другого файла в программу:
.include файл
Ассемблировать блок, если выполняется условие или определен или не определен символ:
.if выражение .ifdef символ .ifndef символ или .ifnotdef символ .else .endif
Выдать сообщение об ошибке:
.err
Немедленно прекратить ассемблирование:
.abort
Запретить листинг:
.nolist
Разрешить листинг:
.list
Конец страницы:
.eject
Размер страницы (60 строк, 200 столбцов по умолчанию):
.psize строки, столбцы
Заголовок листинга:
.title текст
Подзаголовок:
.sbttl текст
.align выражение, выражение, выражение
Выполняет выравнивание программного указателя до границы, указанной первым операндом. Второе выражение указывает, какими байтами заполнять пропускаемый участок (по умолчанию — ноль для секций данных и 90h для секций кода). Третье выражение задает максимальное число байт, которые может пропустить эта директива.
В некоторых системах первое выражение — не число, кратным которому должен стать указатель, а число бит в указателе, которые должны стать нулевыми (в нашем примере это было бы 4).
.org новое значение, заполнение
Увеличивает программный указатель до нового значения в пределах текущей секции. Пропускаемые байты заполняются указанными значениями (по умолчанию — нулями).
.code16
Следующие команды будут ассемблироваться как 16-битные.
.code32
Отменяет действие .code 16.
Присвоение значений символам:
.equ символ, выражение
Присваивает символу значение выражения.
.equiv символ, выражение
То же, что и .equ, но выдает сообщение об ошибке, если символ определен.
.set символ, выражение
То же, что и .equ, но можно делать несколько раз. Обычно, впрочем, бывает удобнее написать просто «символ = выражение».
Управление внешними символами:
.globl символ или .global символ
Делает символ видимым для компоновщика, а значит, и для других модулей программы.
.extern символ
Директива .extern обычно игнорируется — все неопределенные символы считаются внешними.
.comm символ, длина, выравнивание
Директива эквивалентна .lcomm, но, если символ с таким именем определен при помощи .lcomm в другом модуле, будет использоваться внешний символ.
Описание отладочных символов:
.def символ .endef
Блок описания отладочного символа.
Мы не коснемся описания отладочных символов, так как их форматы сильно различаются между разнообразными операционными системами и разными форматами объектных файлов.
Высшего приоритета:
* — умножение
/ — целочисленное деление
% — остаток
< или << — сдвиг влево
> или >> — сдвиг вправо
Среднего приоритета:
| — побитовое «ИЛИ»
& — побитовое «И»
^ — побитовое «исключающее ИЛИ»
! — побитовое «ИЛИ-НЕ» (логическая импликация)
Низшего приоритета:
+ — сложение
– — вычитание
Начало макроопределения:
.macro имя, аргументы
Конец макроопределения:
.endm
Преждевременный выход из макроопределения:
.exitm
Внутри макроопределения обращение к параметру выполняется аналогично блокам повторения, начиная его с обратной косой черты.
Хотя стандартные директивы и включают в себя такие вещи, как блоки повторений и макроопределения, их реализация достаточно упрощена, и при программировании для UNIX на ассемблере часто используют дополнительные препроцессоры. Долгое время было принято использовать С-препроцессор или М4, и многие ассемблеры даже могут вызывать их автоматически, но в рамках проекта GNU был создан специальный препроцессор для ассемблера — gasp. Gasp включает различные расширения вариантов условного ассемблирования, построения циклов, макроопределений, листингов, директив определения данных и так далее. Мы не будем заниматься реализацией таких сложных программ, которым может потребоваться gasp, мы даже не воспользуемся и половиной перечисленных директив, но существование этого препроцессора следует иметь в виду.
Как и в ассемблерах для DOS, ассемблеры для UNIX могут вычислять значения выражений в момент компиляции, например:
// поместить в ЕАХ число 320 * 200 movl $320*$200, %еах
В этих выражениях встречаются следующие операторы.
Итак, в ассемблере AT&T в качестве допустимых символов в тексте программы рассматриваются только латинские буквы, цифры и символы «%» (процент) «$» (доллар), «*» (звездочка) , «.» (точка), «,» (запятая) и «_» (подчеркивание). Помимо них существуют символы начала комментария, различные для разных ассемблеров и различные для комментария размером в целую строку или правую часть строки. Любые другие символы, кроме кавычек, двоеточия, пробела и табуляции, если они не часть комментария или не заключены в кавычки, считаются ошибочными.
Если последовательность допустимых символов, с которой начинается строка, не начинается со специального символа или цифры и не заканчивается двоеточием — это команда процессора:
// остановить процессор hlt
Если последовательность допустимых символов начинается с символа «%» — это название регистра процессора:
// поместить в стек содержимое регистра ЕАХ pushl %eax
Если последовательность начинается с символа «$» — это непосредственный операнд:
// поместить в стек 0, число 10h и адрес переменной variable pushl $0 pushl $0x10 pushl $variable
Если последовательность символов начинается с точки — это директива ассемблера:
.align 2
Если последовательность символов, с которой начинается строка, заканчивается двоеточием — это метка (внутренняя переменная ассемблера, значение которой соответствует адресу в указанной точке):
eternal_loop: jmp eternal_loop variable: .byte 7
Метки, состоящие из одной цифры от 0: до 9:, используются как локальные — обращение к метке 1f соответствует обращению к ближайшей из меток 1: вперед по тексту программы, обращение к метке 4b соответствует обращению к ближайшей из меток 4: назад по тексту программы.
Одни и те же метки могут использоваться без ограничений и как цель для команды перехода, и как переменные.
Специальная метка «.» (точка) всегда равна текущему адресу (в точности как «$» в ассемблерах для DOS/Windows).
Если последовательность символов начинается с символа «*» — это абсолютный адрес (для команд jmp и call), иначе — относительный.
– (минус) — отрицательное число
~ (тильда) — «логическое НЕ»
Может оказаться, что программа вынуждена многократно вызывать те или иные стандартные функции из libc в критическом участке, тормозящем выполнение всей программы. В этом случае стоит обратить внимание на то, что многие функции libc на самом деле всего лишь более удобный для языка С интерфейс к системным вызовам, предоставляемым самим ядром операционной системы. Такие операции, как ввод/вывод, вся работа с файловой системой, с процессами, с TCP/IP и т.п., могут выполняться путем передачи управления ядру операционной системы напрямую.
Чтобы осуществить системный вызов, надо передать его номер и параметры на точку входа ядра аналогично функции libc syscall(2). Номера системных вызовов (находятся в файле /usr/include/sys/syscall.h) и способ обращения к точке входа (дальний call по адресу 0007:00000000) стандартизированы SysV/386 ABI, но, например в Linux, используется другой механизм — прерывание 80h, так что получается, что обращение к ядру операционной системы напрямую делает программу привязанной к этой конкретной системе. Часть этих ограничений можно убрать, используя соответствующие #define, но в общем случае этот выигрыш в скорости оборачивается еще большей потерей переносимости, чем само использование ассемблера в UNIX.
Посмотрим, как реализуются системные вызовы в рассматриваемых нами примерах:
// hellolnx.s // Программа, выводящая сообщение "Hello world" на Linux // без использования libc // // Компиляция: // as -о hellolnx.o hellolnx.s // ld -s -o hellolnx hellolnx.o // .text .globl _start _start: // системный вызов #4 "write", параметры в Linux помещают слева направо, // в регистры %еах, %ebx, %ecx, %edx, %esi, %edi movl $4,%eax xorl %ebx,%ebx incl %ebx // %ebx = 1 (идентификатор stdout) movl $message,%ecx movl $message_l,%edx // передача управления в ядро системы - прерывание с номером 80h int $0x80
// системный вызов #1 "exit" (%еах = 1, %ebx = 0) xorl %eax,%eax incl %eax xorl %ebx,%ebx int $0x80 hlt
.data message: .string "Hello world\012" message_l = . - message
Операционная система MS-DOS, получившая дальнейшее развитие в виде Windows, долгое время была практически единственной операционной системой для персональных компьютеров на базе процессоров Intel. Но с течением времени мощность процессоров выросла настолько, что для них стало возможным работать под управлением операционных систем класса UNIX, использовавшихся обычно на более мощных компьютерах других компаний. В настоящее время существует более двадцати операционных систем для Intel, представляющих те или иные диалекты UNIX. Мы рассмотрим наиболее популярные из них.
Linux — бесплатно распространяемая операционная система, соединяющая в себе особенности двух основных типов UNIX-систем, System V и BSD приблизительно в равной мере. В ней достаточно много отличий и отступлений от любых стандартов, принятых для UNIX, но они более эффективны.
FreeBSD — бесплатно распространяемая операционная система, представляющая вариант BSD UNIX. Считается наиболее стабильной из UNIX-систем для Intel.
Solaris/x86 — коммерческая операционная система компании Sun Microsystems, представляющая вариант System V UNIX, изначально созданная для компьютеров Sun, существует в версии для Intel 80x86. Распространяется бесплатно для образовательных целей.
Несмотря на то что при программировании для UNIX обычно употребляется исключительно язык С, пользоваться ассемблером в этих системах можно, и даже крайне просто. Программы в UNIX выполняются в защищенном режиме с моделью памяти flat и могут вызывать любые функции из библиотеки libc или других библиотек точно так же, как это делают программы на С. Конечно, круг задач, для которых имеет смысл использовать ассемблер в UNIX, ограничен. Если вы не занимаетесь разработкой ядра операционной системы или, например, эмулятора DOS, практически все можно сделать и на С, но иногда встречаются ситуации, когда требуется что-то особенное. Написать процедуру, выполняющую что-то как можно быстрее (например, воспроизведение звука из файла в формате МР3), или программу, использующую память более эффективно (хотя это часто можно повторить на С), или программу, использующую возможности нового процессора, поддержка которого еще не добавлена в компилятор (если вы знаете ассемблер для UNIX), достаточно просто.
Все программы для UNIX, написанные на С, постоянно обращаются к различным функциям, находящимся в libc.so или других стандартных или нестандартных библиотеках. Программы и процедуры на ассемблере, естественно, могут делать то же самое. Вызов библиотечной функции выполняется обычной командой call, а передача параметров осуществляется в соответствии с С-конвенцией: параметры помещают в стек справа налево и очищают стек после вызова функции. Единственная сложность здесь состоит в том, что к имени вызываемой функции в некоторых системах, например FreeBSD, приписывается в начале символ подчеркивания, в то время как в других (Linux и Solaris) имя не изменяется. Если имена в системе модифицируются, имена процедур, включая main(), написанных на ассемблере, также должны быть изменены заранее.
Посмотрим на примере программы, выводящей традиционное сообщение «Hello world», как это делается:
// helloelf.s // Минимальная программа, выводящая сообщение "Hello world" // Для компиляции в формат ELF // // Компиляция: // as -о helloelf.o helloelf.s // Компоновка: // (пути к файлу crt1.o могут отличаться на других системах) // Solaris с SunPro С // ld -s -о helloelf.sol helloelf.o /opt/SUNWspro/SC4.2/lib/crt1.о -lс // Solaris с GNU С // ld -s -o helloelf.gso helloelf.o // /opt/gnu/lib/gcc-lib/i586-cubbi-solaris2.5.1/2.7.2.3.f.1/crt1.о -lс // Linux // ld -s -m elf_i386 -o helloelf.lnx /usr/lib/crt1.o /usr/lib/crti.o // -L/usr/lib/gcc-lib/i586-cubbi-linuxlibc1/2.7.2 helloelf.o -lc -lgcc // /usr/lib/crtn.o // .text // код, находящийся в файлах crt*.o, передаст управление на процедуру main // после настройки всех параметров .globl main main: // поместить параметр (адрес строки message) в стек pushl $message // вызвать функцию puts (message) call puts // очистить стек от параметров popl %ebx // завершить программу ret
.data message: .string "Hello world\0"
В случае FreeBSD придется внести всего два изменения — добавить символ подчеркивания в начало имен функций puts и main и заменить директиву .string на .ascii, так как версия ассемблера, обычно распространяемого с FreeBSD, .string не понимает.
// hellocof.s // Минимальная программа, выводящая сообщение "Hello world" // Для компиляции в вариант формата COFF, используемый во FreeBSD // Компиляция для FreeBSD: // as -о hellocof.o hellocof.s // ld -s -о hellocof.bsd /usr/lib/crt0.o hellocof.o -lc
Проблема в том, что ассемблер для UNIX кардинально отличается от того, что рассматривалось в этой книге до сих пор. В то время как основные ассемблеры для MS-DOS и Windows используют синтаксис, предложенный компанией Intel, изобилующий неоднозначностями, часть которых решается за счет использования поясняющих операторов типа byte ptr, word ptr или dword ptr, а часть не решается вообще (все те случаи, когда приходится указывать код команды вручную), в UNIX с самого начала используется вариант универсального синтаксиса AT&T, синтаксис SysV/386, который специально создавался с целью устранения неоднозначностей в толковании команд. Вообще говоря, существует и ассемблер для DOS/Windows, использующий АТ&Т-синтаксис, — это gas, входящий в набор средств разработки DJGPP, а также есть ассемблер, использующий Intel-синтаксис и способный создавать объектные файлы в формате ELF, применяемом в большинстве UNIX-систем, — это бесплатно распространяемый в сети Inetrnet ассемблер NASM. Мы будем рассматривать только ассемблеры, непосредственно входящие в состав операционных систем, то есть ассемблеры, которые вызываются стандартной командой as.
Названия команд, не принимающих операндов, совпадают с названиями, принятыми в синтаксисе Intel:
nop
К названиям команд, имеющих операнды, добавляются суффиксы, отражающие размер операндов:
b — байт;
w — слово;
l — двойное слово;
q — учетверенное слово;
s — 32-битное число с плавающей запятой;
l — 64-битное число с плавающей запятой;
t — 80-битное число с плавающей запятой.
// mov byte ptr variable,0 movb $0,variable // fild qword ptr variable fildq variable
Команды, принимающие операнды разных размеров, требуют указания двух суффиксов, сначала суффикса источника, а затем приемника:
// movsx edx,al movsbl %al,%edx
Команды преобразования типов имеют в AT&T названия из четырех букв — С, размер источника, Т и размер приемника:
// cbw cbtw // cwde cwtl // cwd cwtl // cdq cltd
Но многие ассемблеры понимают и принятые в Intel формы для этих четырех команд.
Дальние команды передачи управления (jmp, call, ret) отличаются от ближних префиксом l:
// call far 0007:00000000 lcall $7,$0 // retf 10 lret $10
Если команда имеет несколько операндов, операнд-источник всегда записывается первым, а приемник — последним, то есть в точности наоборот по сравнению с Intel-синтаксисом:
// mov ax,bx movw %bx,%ax // imul eax.ecx,16 imull $16,%ecx,%eax
Все префиксы имеют имена, которыми они задаются как обычные команды, — перед командой, для которой данный префикс предназначен. Имена префиксов замены сегмента — segcs, segds, segss, segfs, seggs, имена префиксов изменения разрядности адреса и операнда- addr16 и data 16:
segfs movl variable,%eax rep stosd
Кроме того, префикс замены сегмента будет включен автоматически, если используется оператор «:» в контексте операнда:
movl %fs:variable, %eax
Активационная запись (activation record) — область стека, заполняемая при вызове процедуры
Ассемблер (assembly language) — язык программирования низкого уровня
Ассемблер (assembler) — компилятор с языка ассемблера
Байт (byte) — тип данных, имеющий размер 8 бит, минимальная адресуемая единица памяти
Бит (bit) — минимальная единица измерения информации
«Всплывающая» программа (popup program) — резидентная программа, активирующаяся по нажатию определенной «горячей» клавиши
«Горячая» клавиша (hotkey) — клавиша или комбинация клавиш, используемая не для ввода символов, а для вызова программ и подобных необычных действий
Двойное слово (double word) — тип данных, имеющий размер 32 бита
Дескриптор (descriptor) — восьмибайтная структура, хранящаяся в одной из таблиц GDT, LDT или IDT и описывающая сегмент или шлюз
Директива (directive) — команда ассемблеру, которая не соответствует командам процессора
Драйвер (driver) — служебная программа, выполняющая функции посредника между операционной системой и внешним устройством
Защищенный режим (protected mode) — режим процессора, в котором действуют механизмы защиты, сегментная адресация с дескрипторами и селекторами и страничная адресация
Задача (task) — программа, модуль или другой участок кода программы, который можно запустить, выполнять, отложить и завершить
Идентификатор (handle или identifier) — число (если handle) или переменная другого типа, используемая для идентификации того или иного ресурса
Исключение (exception) — событие, при котором выполнение программы прекращается и управление передается обработчику исключения
Код (code) — исполнимая часть программы (обычная программа состоит из кода, данных и стека)
Компилятор (compiler) — программа, преобразующая текст, написанный на понятном человеку языке программирования, в исполнимый файл
Рис. 19. Кодировка IBM
Кодировка по умолчанию для первых компьютеров — этот набор символов хранится в постоянной памяти и используется BIOS (рис. 19).
Рис. 20. Кодировка cp866
Кодировка cp866 используется DOS-приложениями как основная кодировка и компьютерной сетью Fidonet как транспортная кодировка (рис. 20).
Рис. 21. Кодировка KOI8-R (RFC 1489)
Кодировка KOI8-R используется как транспортная кодировка в Internet и как основная кодировка в большинстве бесплатно распространяемых операционных систем (рис. 21).
Рис. 22. Кодировка ISO 8859-5
Кодировка ISO 8859-5 используется как основная в большинстве коммерческих UNIX-совместимых операционных систем (рис. 22).
Рис. 23. Кодировка cp1251
Кодировка cp1251 используется как основная в графических приложениях для Microsoft Windows (рис. 23).
Таблица 24. Расширенные ASCII-коды
Клавиша | Код | Клавиша | Код | Клавиша | Код | Клавиша | Код | Клавиша | Код |
F1 | 3Bh | Alt-R | 13h | Shift-F11 | 87h | Alt-Tab | A5h | Alt-I | 17h |
F2 | 3Ch | Alt-S | 1Fh | Shift-F12 | 88h | Ctrl-Tab | 94h | Alt-J | 24h |
F3 | 3Dh | Alt-T | 14h | Alt-0 | 81h | Alt-Del | A3h | Alt-K | 25h |
F4 | 3Eh | Alt-U | 16h | Alt-1 | 82h | Alt-End | 9Fh | Alt-L | 26h |
F5 | 3Fh | Alt-V | 2Fh | Alt-2 | 83h | Alt-Home | 97h | Ctrl-Right | 74h |
F6 | 40h | Alt-W | 11h | Alt-3 | 84h | Alt-Ins | A2h | Ctrl-End | 75h |
F7 | 41h | Alt-X | 2Dh | Alt-4 | 85h | Alt-PgUp | 99h | Ctrl-Home | 77h |
F8 | 42h | Alt-Y | 15h | Alt-5 | 86h | Alt-PgDn | A1h | Ctrl-PgDn | 76h |
F9 | 43h | Alt-Z | 2Ch | Alt-6 | 87h | Alt-Enter | 1Ch | Ctrl-PgUp | 84h |
F10 | 44h | Alt-\ | 2Bh | Alt-7 | 88h | Ctrl-F1 | 5Eh | Alt-Up | 98h |
F11 | 85h | Alt-, | 33h | Alt-8 | 89h | Ctrl-F2 | 5Fh | Alt-Down | A0h |
F12 | 86h | Alt-. | 34h | Alt-9 | 8Ah | Ctrl-F3 | 60h | Alt-Left | 9Bh |
Alt-F1 | 68h | Alt-/ | 35h | AltC | 8Bh | Ctrl-F4 | 61h | Alt-Right | 9Dh |
Alt-F2 | 69h | Alt-BS | 0Eh | Alt-= | 8Ch | Ctrl-F5 | 62h | Alt-K/ | A4h |
Alt-F3 | 6Ah | Alt-[ | 1Ah | NUL | 03h | Ctrl-F6 | 63h | Ctrl-K* | 37h |
Alt-F4 | 6Bh | Alt-] | 1Bh | Shift-Tab | 0Fh | Ctrl-F7 | 64h | Alt-K- | 4Ah |
Alt-F5 | 6Ah | Alt-; | 27h | Ins | 52h | Ctrl-F8 | 65h | Alt-K+ | 4Eh |
Alt-F6 | 6Dh | Alt-' | 28h | Del | 53h | Ctrl-F9 | 66h | Alt-KEnter | A6h |
Alt-F7 | 6Eh | Alt-` | 29h | SysRq | 72h | Ctrl-F10 | 67h | Ctrl-K/ | 95h |
Alt-F8 | 6Fh | Shift-F1 | 54h | Down | 50h | Ctrl-F11 | 89h | Ctrl-K* | 96h |
Alt-F9 | 70h | Shift-F2 | 55h | Left | 4Bh | Ctrl-F12 | 8Ah | Ctrl-K- | 8Eh |
Alt-F10 | 71h | Shift-F3 | 56h | Right | 4Dh | Alt-A | 1Eh | Ctrl-K+ | 90h |
Alt-F11 | 8Bh | Shift-F4 | 57h | Up | 48h | Alt-B | 30h | Ctrl-K8 | 8Dh |
Alt-F12 | 8Ch | Shift-F5 | 58h | Enter | 4Fh | Alt-C | 2Eh | Ctrl-K5 | 8Fh |
Alt-M | 32h | Shift-F6 | 59h | Home | 47h | Alt-D | 20h | Ctrl-K2 | 91h |
Alt-N | 31h | Shift-F7 | 5Ah | PgDn | 51h | Alt-E | 12h | Ctrl-K0 | 92h |
Alt-O | 18h | Shift-F8 | 5Bh | PgUp | 49h | Alt-F | 21h | Ctrl-K. | 93h |
Alt-P | 19h | Shift-F9 | 5Ch | Ctrl-Left | 73h | Alt-G | 22h | ||
Alt-Q | 10h | Shift-F10 | 5Dh | Alt-Esc | 01h | Alt-H | 23h |
Префикс «K» соответствует клавишам на цифровой клавиатуре.
Таблица 27. Команды
Команда | Код | 8088 8087 |
80186 | 80286 80287 |
80386 80387 |
80486 | P 5 | P 6 |
AAA | 37 | 8 | 8 | 3 | 4 | 3 | 3 NP | 1m |
AAD i8 | D5 ib | 60 | 15 | 14 | 19 | 14 | 10 NP | 3m |
AAM i8 | D4 ib | 83 | 19 | 16 | 17 | 15 | 18 NP | 4m |
AAS | 3F | 8 | 7 | 3 | 4 | 3 | 3 NP | 1m |
ADC ac,im | 14w im | 4 | 4 | 3 | 2 | 1 | 1 PU | 2m |
ADC r,im | 80sw /2im | 4 | 4 | 3 | 2 | 1 | 1 PU | 2m |
ADC m,im | 80sw /2im | 23+ea | 16 | 7 | 7 | 3 | 3 PU | 4m |
ADC r,r | 10dw /r | 3 | 3 | 2 | 2 | 1 | 1 PU | 2m |
ADC m,r | 10dw /r | 24+ea | 10 | 7 | 7 | 3 | 3 PU | 4m |
ADC r,m | 10dw /r | 13+ea | 10 | 7 | 6 | 2 | 2 PU | 3m |
ADD ac,im | 04w im | 4 | 4 | 3 | 2 | 1 | 1 UV | 1m |
ADD r,im | 80sw /0 im | 4 | 4 | 3 | 2 | 1 | 1 UV | 1m |
ADD m,im | 80sw /0 im | 23+ea | 16 | 7 | 7 | 3 | 3 UV | 4m |
ADD r,r | 00dw /r | 3 | 3 | 2 | 2 | 1 | 1 UV | 1m |
ADD m,r | 00dw /r | 24+ea | 10 | 7 | 7 | 3 | 3 UV | 4m |
ADD r,m | 00dw /r | 13+ea | 10 | 7 | 6 | 2 | 2 UV | 2m |
AND ac,im | 24w im | 4 | 4 | 3 | 2 | 1 | 1 UV | 1m |
AND r,im | 80sw /4 im | 4 | 4 | 3 | 2 | 1 | 1 UV | 1m |
AND m,im | 80sw /4 im | 23+ea | 16 | 7 | 7 | 3 | 3 UV | 4m |
AND r,r | 20dw /r | 3 | 3 | 2 | 2 | 1 | 1 UV | 1m |
AND m,r | 20dw /r | 24+ea | 10 | 7 | 7 | 3 | 3 UV | 4m |
AND r,m | 20dw /r | 13+ea | 10 | 7 | 6 | 2 | 2 UV | 2m |
ARPL r,r | 63 /r | 10 | 20 | 9 | 7 NP | C | ||
ARPL m,r | 63 /r | 11 | 21 | 9 | 7 NP | C | ||
BOUND r,m | 62 /r | 35 | 13 | 10 | 7 | 8 NP | C | |
BSF r16,r16 | 0F BC /r | 10+3n | 6..42 | 6..34 NP | 2m | |||
BSF r32,r32 | 0F BC /r | 10+3n | 6..42 | 6..42 NP | 2m | |||
BSF r16,m16 | 0F BC /r | 10+3n | 6..43 | 6..35 NP | 3m | |||
BSF r32,m32 | 0F BC /r | 10+3n | 6..43 | 6..43 NP | 3m |
Скорости выполнения команд для процессоров 8086 – Р5 даны в тактах (когда говорят, что тактовая частота процессора 100 MHz, это означает, что за секунду проходит 100 миллионов тактов).
Для процессоров Р5 (Pentium, Pentium MMX) помимо скорости указано, может ли команда выполняться одновременно с другими, и если да, то в каком конвейере (см. главу 9.3.2):
UV — может выполняться одновременно, в любом конвейере;
PU — может выполняться одновременно, в U-конвейере;
PV — может выполняться одновременно, в V-конвейере;
FX — может выполняться одновременно с командой FXCH;
NP — не может выполняться одновременно (для ММХ — не может выполняться одновременно с командой того же типа, который указан после буквы «n»).
Для процессоров Р6 (Pentium Pro, Pentium II) указано количество микроопераций, на которые декодируется команда. Буквой «С» отмечены команды со сложным строением (см. главу 9.3.3).
Во всех случаях даны минимально возможные скорости — если шина данных не заблокирована, операнды выровнены по границам двойных слов, операнды находятся в кэше данных, команды по адресу для перехода находятся в кэше кода, переходы угаданы процессором правильно, в момент выполнения команды не происходит заполнения кэша, страницы находятся в TLB (иначе для Р5 следует прибавить 13 – 28 тактов), не происходит исключений в момент выполнения команды, не происходят AGI и т.д.
Операнды обозначаются следующим образом:
im — непосредственный операнд;
i8, i16, i32 — непосредственный операнд указанного размера;
ас — ЕАХ, АХ, AL;
r — любой регистр общего назначения;
r8 — АН, AL, BH, BL, DH, DL, CH, CL;
r16 — АХ, ВХ, СХ, DX, ВР, SP, SI, DI;
r32 — ЕАХ, ЕВХ, ЕСХ, EDX, EBP, ESP, ESI, EDI;
sr — сегментный регистр;
m — операнд в памяти;
mm — регистр ММХ;
s0 — регистр ST(0);Команда может содержать до шести полей:
Префиксы— от нуля до четырех однобайтных префиксов.
Код — один или два байта, определяющие команду.
ModR/M — 1 байт (если он требуется), описывающий операнды:
биты 7 – 6: поле MOD — режим адресации;
биты 5 – 3: поле R/O — либо указывает регистр, либо является продолжением кода команды;
биты 2 – 0: поле R/M — либо указывает регистр, либо совместно с MOD - режим адресации.
SIB — 1 байт, если он требуется (расширение ModR/M для 32-битной адресации):
биты 7 – 6: S — коэффициент масштабирования;
биты 5 – 3: I — индексный регистр;
биты 2 – 0: В — регистр базы.
Смещение — 0, 1, 2 или 4 байта.
Непосредственный операнд — 0, 1, 2 или 4 байта — будем использовать /ib и /iw для указания этих операндов.
Все префиксы выполняются за 1 такт и имеют размер 1 байт:
F0h: LOCK
F2h: REPNE/REPNZ
F3h: REP/REPE/REPZ
2Eh: CS:
36h: SS:
3Eh: DS:
26h: ES:
64h: FS:
65h: GS:
66h: OS
67h: AS
Номера строк соответствуют первой цифре в шестнадцатеричном коде символа, номера столбцов — второй, так что, например, код большой латинской буквы A — 41h (см. рис. 18).
Рис. 18. Таблица символов ASCII
Таблица 25. Скан-коды
Клавиша | Код | Клавиша | Код | Клавиша | Код | Клавиша | Код |
Esc | 01h | Enter | 1Ch | K* | 37h | Ins | 52h |
1 ! | 02h | Ctrl | 1Dh | Alt | 38h | Del | 53h |
2 @ | 03h | A | 1Eh | SP | 39h | SysRq | 54h |
3 # | 04h | S | 1Fh | Caps | 3Ah | Macro | 56h |
4 $ | 05h | D | 20h | F1 | 3Bh | F11 | 57h |
5 % | 06h | F | 21h | F2 | 3Ch | F12 | 58h |
6 ^ | 07h | G | 22h | F3 | 3Dh | PA1 | 5Ah |
7 & | 08h | H | 23h | F4 | 3Eh | F13/LWin | 5Bh |
8 * | 09h | J | 24h | F5 | 3Fh | F14/RWin | 5Ch |
9 ( | 0Ah | K | 25h | F6 | 40h | F15/Menu | 5Dh |
0 ) | 0Bh | L | 26h | F7 | 41h | F16 | 63h |
- _ | 0Ch | ; : | 27h | F8 | 42h | F17 | 64h |
= + | 0Dh | ' " | 28h | F9 | 43h | F18 | 65h |
BS | 0Eh | ` ~ | 29h | F10 | 44h | F19 | 66h |
Tab | 0Fh | LShift | 2Ah | Num | 45h | F20 | 67h |
Q | 10h | \ | | 2Bh | Scroll | 46h | F21 | 68h |
W | 11h | Z | 2Ch | Home | 47h | F22 | 69h |
E | 12h | X | 2Dh | - | 48h | F23 | 6Ah |
R | 13h | C | 3Eh | PgUp | 49h | F24 | 6Bh |
T | 14h | V | 2Fh | K- | 4Ah | EraseEOF | 6Dh |
Y | 15h | B | 30h | 4Bh | Copy/Play | 6Fh | |
U | 16h | N | 31h | K5 | 4Ch | CrSel | 72h |
I | 17h | M | 32h | ® | 4Dh | Delta | 73h |
O | 18h | , < | 33h | K+ | 4Eh | ExSel | 74h |
P | 19h | . > | 34h | End | 4Fh | Clear | 76h |
[ { | 1Ah | / ? | 35h | I | 50h | ||
] } | 1Bh | RShift | 36h | PgDn | 51h |
Префикс «K» соответствует клавишам на цифровой клавиатуре.
Таблица 26. Служебные скан-коды
Код | Функция | Код | Функция |
00h | Буфер клавиатуры переполнен | FAh | ACK |
AAh | Самотестирование закончено | FCh | Ошибка самотестирования |
E0h | Префикс для серых клавиш | FDh | Ошибка самотестирования |
E1h | Префикс для клавиш без кода отпускания | FEh | RESEND |
F0h | Префикс отпускания клавиши | FFh | Ошибка клавиатуры |
EEh | Эхо |
ABI — Application Binary Interface — интерфейс для приложений на низком уровне
AGI — Address Generation Interlock — задержка для генерации адреса
AMIS — Alternative Multiplex Interrupt Specification — спецификация альтернативного мультиплексорного прерывания
API — Application Program Interface — интерфейс между приложением и программой
ASCII — American Standard Code for Information Interchange — американский стандартный код для обмена информацией
AT&T — American Telephone and Telegraph — американский телефон и телеграф (компания, которой принадлежала торговая марка UNIX)
BCD — Binary Coded Decimal — двоично-десятичный формат
BIOS — Basic Input/Output System — основная система ввода-вывода
BIT — BInary digiT — двоичная цифра
BPB — BIOS Parameter Block — блок параметров BIOS (для блочных устройств)
BRM — Big Real Mode — большой реальный режим (то же, что и нереальный режим)
BSD — Berkeley System Distribution — один из основных видов UNIX-систем
BSS — Block, Started by Symbol — участок программы, содержащий неинициализированные данные
CMOS — Complementary Metal Oxide Semiconductor — комплементарные металлооксидные пары
COFF — Common Object File Format — общий формат объектных файлов
CPL — Current Privilege Level — текущий уровень привилегий
CRT — Cathode Ray Tube — электронно-лучевая трубка
DAC — Digital to Analog Converter — цифро-аналоговый преобразователь
DDK — Drivers Development Kit — набор для создания драйверов
DLL — Dynamically Linked Library — динамическая библиотека
DMA — Direct Memory Access — прямой доступ к памяти
Таблица 23. Управляющие символы ASCII
Код | Имя | Ctrl-код | Назначение |
00 | NUL | ^@ | Пусто (конец строки) |
01 | SOH | ^A | Начало заголовка |
02 | STX | ^B | Начало текста |
03 | EOT | ^C | Конец текста |
04 | ENQ | ^D | Конец передачи |
06 | ACK | ^F | Подтверждение |
07 | BEL | ^G | Звонок |
08 | BS | ^H | Шаг назад |
09 | HT | ^I | Горизонтальная табуляция |
0A | LF | ^J | Перевод строки |
0B | VT | ^K | Вертикальная табуляция |
0C | FF | ^L | Перевод страницы |
0D | CR | ^M | Возврат каретки |
0E | SO | ^N | Выдвинуть |
0F | SI | ^O | Сдвинуть |
10 | DLE | ^P | Оставить канал данных |
11 | DC1/XON | ^Q | Управление устройством 1 |
12 | DC2 | ^R | Управление устройством 2 |
13 | DC3/XOFF | ^S | Управление устройством 3 |
14 | DC4 | ^T | Управление устройством 4 |
15 | NAK | ^U | Отрицательное подтверждение |
16 | SYN | ^V | Синхронизация |
17 | ETB | ^W | Конец блока передачи |
18 | CAN | ^X | Отмена |
19 | EM | ^Y | Конец носителя |
1A | SUB | ^Z | Замена |
1B | ESC | ^[ | Escape |
1C | FS | ^\ | Разделитель файлов |
1D | GS | ^] | Разделитель групп |
1E | RS | ^^ | Разделитель записей |
1F | US | ^_ | Разделитель полей |
20 | SP | Пробел | |
7F | DEL | ^? | Удаление |
В кодах некоторых команд мы будем встречать специальные биты и группы бит, которые обозначим w, s, d, reg, sreg и cond:
w = 0, если команда работает с байтами;
w = 1, если команда работает со словами или двойными словами;
s = 0, если непосредственный операнд указан полностью;
s = 1, если непосредственный операнд — младший байт большего операнда и должен рассматриваться как число со знаком;
d = 0, если код источника находится в поле R/O, а приемника — в R/M;
d = 1, если код источника находится в поле R/M, а приемника — в R/O.
Запись 10dw будет означать, что код команды — 000100dw.
Поле reg определяет используемый регистр и имеет длину 3 бита:
000 — AL/AX/EAX/ST(0)/MM0
001 — CL/CX/ECX/ST(1)/MM1
010 — DL/DX/EDX/ST(2)/MM2
011 — BL/BX/EBX/ST(3)/MM3
100 — AH/SP/ESP/ST(4)/MM4
101 — CH/BP/EBP/ST(5)/MM5
110 — DH/SI/ESI/ST(6)/MM6
111 — BH/DI/EDI/ST(7)/MM7
Запись C8r будет означать 11001reg.
Поле sreg определяет используемый сегментный регистр:
000 — ES
001 — CS
010 — SS
011 — DS
100 — FS
110 — GS
Поле cond определяет условие для команд Jcc, CMOVcc, SETcc, FCMOVcc.
Его значения для разных команд:
0000 — О
0001 — NO
0010 — C/B/NAE
0011 — NC/NB/AE
0100 — E/Z
0101 — NE/NZ
0110 — BE/NA
0111 — NBE/A
1000 — S
1001 — NS
1010 — Р/РЕ
1011 — NP/PO
1100 — L/NGE
1101 — NL/GE
1110 — LE/NG
1111 — LNE/G
Запись типа 4сс будет означать 0100cond.
Поле R/O (биты 5– 3) содержит либо дополнительные три бита кода команды, либо код операнда, который может быть только регистром. Будем обозначать второй случай reg, а в первом записывать используемые биты.
Поля MOD (биты 7 – 6) и R/M (биты 3 – 0) определяют операнд, который может быть как регистром, так и переменной в памяти:
MOD = 11, если используется регистровая адресация и R/M содержит код регистра reg;
MOD = 00, если используется адресация без смещения ([ВХ + SI] или [EDX]);
MOD = 01, если используется адресация с 8-битным смещением (variable[BX + SI]);
MOD = 10, если используется адресация с 16- или 32-битным смещением.
Значение поля R/M различно в 16- и 32-битных режимах.
R/M в 16-битном режиме:
000 — [ВХ + SI]
001 — [ВХ + DI]
010 — [BP + SI]
011 — [ВР + DI]
100 — [SI]
101 — [DI]
110 — [ВР] (кроме MOD = 00 — в этом случае после ModR/M располагается 16-битное смещение, то есть используется прямая адресация)
111 — [ВХ]
R/M в 32-битном режиме:
000 — [ЕАХ]
001 — [ЕСХ]
010 — [EDX]
011 — [ЕВХ]
100 — используется SIB
101 — [ЕВР] (кроме MOD = 00 — в этом случае используется SIB, после которого располагается 32-битное смещение)
110 — [ESI]
111 — [EDI]
Значение поля S:
00 — не используется;
01 — умножение на 2;
10 — умножение на 4;
11 — умножение на 8;
Значения полей I и В:
(I — регистр, используемый в качестве индекса, то есть умножающийся на S, В — регистр базы, который не умножается)
000 — ЕАХ
001 — ЕСХ
010 — EDX
011 — ЕВХ
100 — для I — индекса нет
для В — ESP
101 — для I — ЕВР
для В — ЕВР, только если MOD = 01 или 10, если MOD = 00 — базы нет
110 — ESI
111 — EDI
Поля ModR/M и SIB будут записываться как /r, если поле R/O содержит код регистра, или /0 – /7, если поле R/O содержит дополнительные три бита кода команды. В других случаях поля ModR/M и SIB отсутствуют только у команд без операндов, так что они не будут обозначаться дополнительно.