10. Кодирование команд.
Мы не будем детально изучать кодирование всех команд: лучше предоставить Ассемблеру работу по переводу мнемоники команд в их код. Однако полезно иметь ясное представление о принципах кодирования команд. Заметим, что мы уже изучили кодирование команд перехода.
10.1. Однооперандные команды.
Для примера возьмем команду inc dst — она увеличивает операнд на единицу. Имеется несколько вариантов кодирования этой команды.
10.1.1. Инкремент 16-разрядного регистра.
Код команды занимает один байт:
7 6 5 4 3 2 1 0
КОП reg16
Здесь КОП — код операции, для inc reg16 это 01000. 16-разрядные регистры кодируются в соответствии с табл. 10.1.
Таблица 10.1.
000 AX
001 CX
010 DX
011 BX
100 SP
101 BP
110 SI
111 DI
Тогда inc cx кодируется так: 01000 001= 41h.
Упражнение. Проверить это утверждение в TD.
10.1.2. Инкремент любого регистра (8- или 16-разрядного), байта или слова памяти.
Код команды занимает два байта (старший байт носит название постбайта):
постбайт
КОП w mod КОП r/m
КОП теперь содержится в двух полях: в битах 7:1 первого байта и 5:3 постбайта. Для команды inc содержимое этих полей 1111111 и 000.
w (word) — длина операнда. Если w = 1 (ДА) — операнд-слово, w = 0 (НЕТ) — операнд-байт.
mod (mode) — режим. При mod = 11 операнд находится в регистре, при mod = 00, 01, 10 операнд располагается в памяти.
r/m (register/memory) — если операнд находится в регистре, это поле содержит номер регистра.
10.1.2.1. Операнд в регистре.
Таблицу кодирования регистров придется расширить (табл. 10.2).
Таблица 10.2.
r/m w=1 w=0
000 AX AL
001 CX CL
010 DX DL
011 BX BL
100 SP AH
101 BP CH
110 SI DH
111 DI BH
Пример: Расшифруем код команды inc cl — FE C1
1 1 1 1 1 1 1 0 1 1 0 0 0 0 0 1
КОП w reg8 КОП CL
На первый взгляд парадокс! Код inc cl занимает два байта, а код inc cx — один байт! Конечно, inc cx можно было закодировать и в двух байтах (какой бит для этого надо изменить?), но для инкремента 16-разрядных регистров предусмотрен укороченный формат кода команды. Именно его и выбирает Ассемблер.
10.1.2.2. Операнд в памяти.
Содержимое поля mod расшифровывается так:
00 — смещения нет;
01 — D8 (расширяется со знаком до D16);
10 — D16.
Здесь D8 — число, занимающее байт, D16 — число, занимающее слово (D — сокращение от слова DATA — данные).
Содержимое поля r/m теперь означает комбинацию базового и индексного регистров (табл. 10.3).
Таблица 10.3.
000 BX+SI
001 BX+DI
010 BP+SI
011 BP+DI
100 SI
101 DI
110 D16
111 BX
Исключение: mod = 00, r/m = 110 — это не [BP]!
Пример. Дешифруем команду FF445C.
1 1 1 1 1 1 1 1 0 1 0 0 0 1 0 0 0 1 0 1 1 1 0 0
КОП 1111111 000 — команда inc; w = 1 — операнд-слово; mod = 01 — имеется 8-разрядное смещение, D8 = 5C, расширяем знак: 005С; r/m = 100 — соответствует индексному регистру SI.
Итак, мнемоника команды inc word ptr [si+5C].
Теперь посмотрим, как будет работать эта команда. Для этого сделаем допущения: SI = 0A086h, DS = 20F0h. Тогда эффективный адрес равен 0A086h + 005Ch = 0A0E2h — смещение в сегменте. Так как в адресаци не участвует BP, то сегментный адрес выбирается из DS. Физический адрес: 20F00h + 0A0E2h = 2AFE2h. Операнд — слово — расположен в байтах с адресами 2AFE2 и 2AFE3.
Предположим, что до операции [2AFE2] = 0FFh, [2AFE3] = 00h. Тогда после операции [2AFE2] = 00h, [2AFE3] = 01h.
10.1.3. Прямая адресация.
В команде непосредственно указывается адрес (точнее, смещение) операнда, например inc word ptr [200]. Разработчики процессора хотели закодировать прямую адресацию какой-либо комбинацией mod и r/m, но все сочетания оказались задействованы для регистровой и косвенной адресации. Пришлось привлечь редко используемую комбинацию mod = 00 и r/m = 110 (т.е. [BP]; далее мы увидим, что использовать этот метод адресации нет необходимости).
Смещение кодируется в двух байтах в самой команде:
постбайт
00 110
DISP-LO (младший байт смещения) DISP-HI (старший байт смещения)
Упражнение. Вручную закодируйте inc word ptr [200] и проверьте результат в отладчике.
Упражнение. Выясните, как мини-ассемблер отладчика закодирует команду inc word ptr [bp].
Составим сводную таблицу (табл. 10.4) методов адресации в зависимости от содержимого полей r/m, mod и w. Эта таблица не содержит для нас новой информации.
Таблица 10.4
Поле r/m Поле mod
00 01 10 11
w=0 w=1
000 BX+SI BX+SI+D8 BX+SI+D16 AL AX
001 BX+DI BX+DI+D8 BX+DI+D16 CL CX
010 BP+SI BP+SI+D8 BP+SI+D16 DL DX
011 BP+DI BP+DI+D8 BP+DI+D16 BL BX
100 SI SI+D8 SI+D16 AH SP
101 DI DI+D8 DI+D16 CH BP
110 D16 BP+D8 BP+D16 DH SI
111 BX BX+D8 BX+D16 BH DI
Еще раз заметим, что красоту этой таблицы портит прямая адресация при r/m = 110, mod = 00.
Может возникнуть вопрос: мы узнали, как кодируется регистровая, прямая и косвенная адресации. А как же непосредственная? Ответ: в однооперандных командах непосредственного операнда не бывает (команда не может изменить свой код — ведь он находится в очереди команд в центральном процессоре).
10.2. Двухоперандные команды.
На этот раз в качестве примера рассмотрим команду сложения add dst,src.
Поставим себя на место разработчиков системы команд процессора. Если приемник и источник оба находятся в памяти, то для кодирования каждого из них потребуются свои поля mod и r/m. Сосчитаем количество битов, необходимых для кодирования: на поле mod нужно 2 + 2 бита и на поле r/m — 3 + 3 бита — всего 10 битов. Получается, что постбайта не хватит, и ради двух битов придется в код команды включить дополнительный байт. Чтобы сократить длину команд, разработчики решили, что хотя бы один операнд должен обязательно находиться в регистре (мы пока не рассматриваем случай непосредственного операнда). Тогда для одного операнда потребуется поле reg — 3 бита, а для другого — mod и r/m — 2 + 3 = 5 битов. Всего 8 бит — ровно байт. Формат типичной команды имеет вид:
постбайт
КОП d w mod reg r/m
Для команды ADD, например, код операции 0000000. Назначение поля w уже известно. Новое поле d (destination — приемник) несет следующую информацию: если d = 0, то приемник определяется полями mod и r/m (т.е. этот операнд находится в памяти или регистре), если d = 1, то приемник определяется полем reg (т.е. заведомо находится в регистре).
Пример. Дешифруем код 02 EB.
0 0 0 0 0 0 1 0 1 1 1 0 1 0 1 1
КОП d w mod reg r/m
w = 0 — операнды-байты; d = 1 — приемник в регистре, определяемом полем reg; reg = 101 — это регистр CH (в соответствии с табл. 10.4); mod = 11 — источник также в регистре; r/m = 11 — это регистр BL (по табл. 10.4). Итак, код соответствует команде add ch,bl.
Упражнение. Реассемблировать вручную код 00 00. Проверить результат в отладчике.
10.3. Непосредственный операнд.
Напомним, что удобство использования непосредственного операнда определяется двумя причинами:
1) уменьшается требуемая память (иначе пришлось бы в команде хранить адрес константы, а саму константу хранить в памяти);
2) непосредственный операнд берется прямо из кода команды, поэтому не тратится время на обращение к памяти для выборки операнда.
Опять обратимся к примеру. Команда сложения с непосредственным операндом add dst, imm максимально кодируется шестью байтами.
постбайт
DISP-LO DISP-HI DATA-LO DATA-HI
присутствие зависит от поля mod имеется, если s:w=01
Байты, которые необязательно присутствуют в коде команды, показаны пунктиром.
Сначала посмотрим структуру первых двух байтов:
1 0 0 0 0 0 0 0 0
КОП s w mod КОП r/m
Сразу заметим, что код операции ADD изменился, увеличилась и его длина: вместо 6 бит стало 9. Бит приемника d исчез — понятно почему, ведь непосредственный операнд не может быть приемником. Поля mod и r/m нам уже знакомы. Соответственно, в зависимости от mod в коде может присутствовать смещение D8 или D16.
Зато появился новый бит s (sign — знак). Он занимает место d, т.к. бит d теперь не нужен. Смысл бита s таков. В арифметических операциях часто используются небольшие по величине непосредственные операнды и отводить для них целое слово слишком расточительно. При w = 0 (операнд-байт) значение s не играет роли. При w = 1 (операнд-слово): если s=0, то в команде присутствуют все 16 бит DATA; если же s = 1, то присутствуют 8 бит (DATA-LO), которые расширяются со знаком до 16-разрядного операнда. Благодаря биту s экономится один байт! Это — кодировка с расширением знака (sign-extended encodings).
Упражнение. Введите в TD в панели данных окна CPU по адресу ds:0100 (этот адрес совпадает с cs:0100 при входе в TD) последовательность байтов: 81,0C3,0FF,0,83,0C3,0FF. Объяснить результат, полученный в панели кода.
Казалось бы, мы полностью разобрались с командой ADD. Но есть еще один ее вариант! При выполнении команды сложения приемником, как правило, является аккумулятор (AX или AL). Для прибавления к аккумулятору введена специальная кодировка (укороченный формат ADD):
0 0 0 0 0 1 0 w DATA-LO DATA-HI
присутствует, если w=1
10.4. Общие замечания о кодировании команд.
Подведем итоги. Создатели микропроцессора 8086 приложили максимум усилий для компактного кодирования команд: для часто используемых вариантов команд имеются сокращенные форматы. В качестве одного из операндов полезно по возможности использовать аккумулятор, т.к. для него нередко генерируется меньшая по длине команда.
Очень много форматов используется для кодирования наиболее распространенной команды mov. Поэтому в примерах эта команда отсутствовала.
Длина команды может составлять от одного до шести байт (для процессора 8086).
Полученные сведения могут показаться излишними, но теперь вы должны четко понимать, например, почему нельзя использовать адресацию [CX]. Потому что разработчикам не хватило номеров для методов адресации. Стал ясным запрет на использование в команде двух операндов из памяти.
Еще одно упражнение, которое поможет понять нехитрые программистские трюки для сокращения длины кода программы.
Упражнение. 1) При обработке массива слов для перехода к следующему слову нужно увеличивать на два содержимое индексного регистра. Что короче: две команды inc si или одна команда add si,2 ?
2) Какая команда обнуления регистра короче: mov bx,0 или xor bx,bx ?
В процессоре 80386 набор методов адресации существенно увеличен. Естественно, увеличилась и возможная длина кода команды. Набор методов адресации 8086 входит в 80386 как подмножество.
Задание A5. Получить с помощью отладчика коды команд, выделить в этих кодах поля и расшифровать их.
1) dec ax; 2) dec al; 3) inc word ptr [bx+si+24h]; 4) inc byte ptr [bx+di–4A0h];
5) sub [bx+si],ah; 6) sub word ptr [bx+14h],3; 7) sub byte ptr [bx+14h],–3.
10.5. Префикс замены сегмента.
С помощью изученных методов адресации можно обращаться только к сегменту данных, адрес которого хранится в DS, или к стековому сегменту, адрес которого хранится в SS. А если нужно обратиться к данным, хранящимся в сегменте, адрес которого лежит в ES? Тогда перед кодом команды нужно вставить байт — префикс замены сегмента (segment override prefix). Формат этого байта:
7 6 5 4 3 2 1 0
0 0 1 seg 1 1 0
Здесь seg — номер сегментного регистра, кодируемый в соответствии с таблицей 10.5.
Таблица 10.5.
ES 00
CS 01
SS 10
DS 11
Может возникнуть вопрос: а к какому из операндов команды относится префикс замены сегмента. Ответ прост: в команде только один операнд может ссылаться на ячейку памяти, поэтому неоднозначности не возникает.
Пример. Для команды mov ax, es:[bx], или mov ax, es:[bx] (эти две формы эквивалентны) Ассемблер сгенерирует перед кодом команды префиксный байт 26h=00100110. Проверьте.
10.6. Команда загрузки исполнительного адреса.
С косвенной адресацией тесно связана команда загрузки адреса.
Загрузить исполнительный адрес lea r16,mem reg ← адрес mem
Load Effective Address флаги не изменяются
lea r16,m
В регистр загружается не сам операнд, а его адрес, точнее, смещение в текущем сегменте данных.
Пример. mov si,200
mov bx,10
lea di,[bx+si+4]
В результате выполнения этих команд DI = 214.
Упражнение. Результат выполнения команды lea di,[200]?
10. Кодирование команд. 10. 1. Однооперандные команды
Лекции по предмету «Информатика»