-
Notifications
You must be signed in to change notification settings - Fork 4
Из ассемблера в байт код
Чтобы разобраться каким правилам подчиняется процесс трансляции исходного кода на языке ассемблера в байт-код, нужно рассмотреть структуру файла с расширением .cor
.
Для этого мы транслируем данного чемпиона с помощью предоставленной заданием программы asm
:
.name "Batman"
.comment "This city needs me"
loop:
sti r1, %:live, %1
live:
live %0
ld %0, r2
zjmp %:loop
Полученный в результате работы файл будет иметь следующую структуру:
Первые 4 байта в файле занимает «магическое число».
Оно определено с помощью константы COREWAR_EXEC_MAGIC
, значение которой в файле-примере op.h
равно 0xea83f3
.
Magic header это такое специальное число, задача которого — сообщать, что данный файл является бинарным.
Если в файле не будет такого «сообщения», он может быть интерпретирован как текстовый.
Следующие 128 байт в файле занимает имя чемпиона. Почему 128? Это значение константы PROG_NAME_LENGTH
, которая определяет максимальную длину строки с именем.
Имя нашего чемпиона гораздо короче. Но в байт-коде оно всё равно занимает все 128 байт.
Потому, что по правилам трансляции, если длина строки с именем меньше установленного ограничения, то недостающие символы компенсируются нулевыми байтами.
В общем, каждый символ имени превращается в ASCII-код размером в 1 байт, записанный в шестнадцатеричной системе исчисления:
Символ | B |
a |
t |
m |
a |
n |
---|---|---|---|---|---|---|
ASCII-код | 0x42 |
0x61 |
0x74 |
0x6d |
0x61 |
0x6e |
А вместо недостающих символов мы записываем нулевые байты.
Следующие 4 байта в структуре файла отведены для некой контрольной точки — четырех нулевых октетов.
Никакой информационной нагрузки они не несут. Их задача состоит в том, чтобы просто находиться в нужном месте.
Эти 4 байта содержат довольно важную информацию — размер исполняемого кода чемпиона в байтах.
Как мы помним, виртуальная машина должна удостовериться, что размер исходного кода не превышает ограничение заданное в константе CHAMP_MAX_SIZE
. В предоставленном файле op.h
оно равно 682
.
Следующие 2048 байтов заняты комментарием чемпиона. И по своей сути эта часть полностью аналогична части «Champion name».
Правда за исключением того, что теперь ограничение на максимальную длину устанавливает константа COMMENT_LENGTH
.
И снова 4 нулевых октета.
Последнюю часть файла занимает исполняемый код чемпиона.
В отличие от имени или комментария, нулевыми байтами он не дополняется.
Для того, чтобы разобраться как работает кодирование операций нам понадобятся две таблицы.
В первую очередь нам нужна обновленная таблица операций с колонками «Код типов аргументов» и «Размер T_DIR
».
Код | Имя | Аргумент #1 | Аргумент #2 | Аргумент #3 | Код типов аргументов | Размер T_DIR
|
---|---|---|---|---|---|---|
0x01 | live |
T_DIR |
— | — | Нет | 4 |
0x02 | ld |
T_DIR / T_IND
|
T_REG |
— | Есть | 4 |
0x03 | st |
T_REG |
T_REG / T_IND
|
— | Есть | 4 |
0x04 | add |
T_REG |
T_REG |
T_REG |
Есть | 4 |
0x05 | sub |
T_REG |
T_REG |
T_REG |
Есть | 4 |
0x06 | and |
T_REG / T_DIR / T_IND
|
T_REG / T_DIR / T_IND
|
T_REG |
Есть | 4 |
0x07 | or |
T_REG / T_DIR / T_IND
|
T_REG / T_DIR / T_IND
|
T_REG |
Есть | 4 |
0x08 | xor |
T_REG / T_DIR / T_IND
|
T_REG / T_DIR / T_IND
|
T_REG |
Есть | 4 |
0x09 | zjmp |
T_DIR |
— | — | Нет | 2 |
0x0a | ldi |
T_REG / T_DIR / T_IND
|
T_REG / T_DIR
|
T_REG |
Есть | 2 |
0x0b | sti |
T_REG |
T_REG / T_DIR / T_IND
|
T_REG / T_DIR
|
Есть | 2 |
0x0c | fork |
T_DIR |
— | — | Нет | 2 |
0x0d | lld |
T_DIR / T_IND
|
T_REG |
— | Есть | 4 |
0x0e | lldi |
T_REG / T_DIR / T_IND
|
T_REG / T_DIR
|
T_REG |
Есть | 2 |
0x0f | lfork |
T_DIR |
— | — | Нет | 2 |
0x10 | aff |
T_REG |
— | — | Есть | 4 |
Зачем нужен «Размер
T_DIR
» для операций, которые не принимают аргументы этого типа?На данном этапе это действительно бесполезные сведения. Но они будут необходимы во время работы виртуальной машины.
Что и будет рассмотрено в соответствующем разделе.
Полную таблицу операций можно просмотреть на сервисе Google Sheets.
Вторая необходимая нам таблица содержит информацию о кодах типов аргументов и их размерах.
Тип | Знак | Код | Размер |
---|---|---|---|
T_REG |
r |
01 |
1 байт |
T_DIR |
% |
10 |
Размер T_DIR
|
T_IND |
— | 11 |
2 байта |
Полную таблицу аргументов можно просмотреть на сервисе Google Sheets.
О регистрах и его размерах
Важно различать две характеристики размера касающиеся регистров.
Название регистра (
r1
,r2
...) в байт-коде занимает 1 байт. Но сам регистр вмещает в себя 4 байта, как указано в константеREG_SIZE
.
О размерах аргументов типа
T_DIR
Как можно было увидеть в таблице операций, размер аргументов типа
T_DIR
не фиксирован и зависит от операции. Но в файлеop.h
есть константа препроцессора, которая говорит, что размерT_DIR
равен размеру регистра — 4 байтам:# define IND_SIZE 2 # define REG_SIZE 4 # define DIR_SIZE REG_SIZE
В связи с этим возникает вопрос «А где здесь логика?».
Логика здесь есть, но довольно призрачная.
Как мы уже говорили, для записи числа типа
T_DIR
в регистр и для выгрузки этого числа в память размеры регистров и аргументов типаT_DIR
должны совпадать.С этим утверждением все хорошо. Оно корректно. Если посмотреть на операции, которые загружают значение в регистр, для них размер
T_DIR
равен4
.Также размер
T_DIR
равен4
для операцииlive
.Что касается операций, для которых размер
T_DIR
равен2
, то в этих случаях аргумент данного типа принимает участие только в формировании адреса. И для такой задачи 4 байта являются избыточными. Ведь объем памяти составляет всего 4096 байт, как указано в константеMEM_SIZE
. И любое число-адрес, превышающее это ограничение будет урезано по модулюMEM_SIZE
, если это еще не было сделано с помощьюIDX_MOD
.В данной ситуации аргумент типа
T_DIR
играет роль относительного адреса. То есть аргумента типаT_IND
. И поэтому его размер равенIND_SIZE
(2 байта).
Каждая операция представленная в байт-коде имеет следующую структуру:
- Код операции — 1 байт
- Код типов аргументов (Нужен не для всех операций) — 1 байт
- Аргументы
Код типов аргументов
Как было указано выше в структуре закодированной операции может отсутствовать второй компонент — код типов аргументов.
Его наличие зависит от конкретной операции. Если она принимает всего один аргумент и его тип однозначно определен как
T_DIR
, то код с информацией о типах аргументов не нужен. Для всех остальных операций этот компонент обязателен.Проверить нужен ли этот код для конкретной операции можно в столбце «Код типов аргументов» таблицы операций.
Давайте рассмотрим как мы кодируем инструкции исполняемого кода:
loop:
sti r1, %:live, %1
live:
live %0
ld %0, r2
zjmp %:loop
Первая инструкция, которую необходимо транслировать это:
loop:
sti r1, %:live, %1
Для начала установим размеры каждого компонента.
Код операции | Код типов аргументов | Аргумент #1 | Аргумент #2 | Аргумент #3 |
---|---|---|---|---|
1 байт | 1 байт | 1 байт | 2 байта | 2 байта |
Теперь давайте рассмотрим, как мы узнаём байт-код каждой части инструкции.
Код операции
Код каждой операции указан в таблице операций. Для sti
он равен 0x0b
.
Код типов аргументов
Для того, чтобы сформировать этот код, нужно представить 1 байт в двоичной системе исчисления. Первые два бита слева займет код типа аргумента #1. Следующие два отойдут коду типа второго аргумента. И так далее. Последняя четвертая пара всегда будет равна 00
.
Коды для каждого типа указаны в таблице аргументов.
Аргумент #1 | Аргумент #2 | Аргумент #3 | — | Итоговый код |
---|---|---|---|---|
T_REG |
T_DIR |
T_DIR |
— | — |
01 |
10 |
10 |
00 |
0x68 |
Аргумент типа T_REG
В этом случае в шестнадцатеричный код переводится номер регистра. Для регистра r1
это 0x01
.
Аргумент-метка типа T_DIR
Как нам уже известно метка должна превратится в число, которое содержит относительный адрес в байтах.
Поскольку метка live
указывает на следующую инструкцию, а размер в байтах текущей инструкции нам уже известен, можно легко посчитать необходимое расстояние — 7 байтов.
Полученное число-адрес должно будет разместиться на 2 байтах — 0x0007
.
Аргумент-число типа T_DIR
В этом случае все еще проще. Нужно просто записать число, полученное в десятичной системе исчисления, в виде шестнадцатеричного байт-кода — 0x0001
.
Итоговый байт-код инструкции — 0b 68 01 0007 0001
.
Для следующей инструкции все аналогично:
live:
live %0
Единственное существенное изменение состоит в том, что для данной операции не нужен код типов аргументов:
Код операции | Аргумент #1 |
---|---|
1 байт | 4 байта |
Итоговый байт-код для второй инструкции — 01 00000000
.
Третья инструкция:
ld %0, r2
Код операции | Код типов аргументов | Аргумент #1 | Аргумент #2 |
---|---|---|---|
1 байт | 1 байт | 4 байта | 1 байт |
Здесь также нет никаких сюрпризов — 02 90 00000000 02
.
zjmp %:loop
Код операции zjmp
равен 0x09
.
Код типов аргументов для этой операции не нужен.
Код операции | Аргумент #1 |
---|---|
1 байт | 2 байта |
Метка loop
указывает на 19 байтов назад. Вот только как нам представить в шестнадцатеричной системе число -19
?
Для этого нужно записать число 19
в двоичной системе исчисления в прямом коде:
0000 0000 0001 0011
Из числа 19
, записанного в прямом коде, мы сможем получить число -19
в дополнительном коде.
Именно в дополнительном коде принято представлять целые отрицательные числа в компьютерах.
Для того, чтобы получить дополнительный код числа -19
, мы должны выполнить следующие действия:
- Инвертировать все разряды
То есть изменить единицу на ноль, и наоборот:
1111 1111 1110 1100
- Добавить единицу к числу
1111 1111 1110 1101
Готово. Вот мы и получили число -19
в дополнительном коде.
Переведем его из двоичной в шестнадцатеричную систему — 0xffed
.
Итоговый байт-код для инструкции #5 — 09 ffed
.
Весь исполняемый код рассматриваемого чемпиона будет выглядеть так:
0b68 0100 0700 0101 0000 0000 0290 0000
0000 0209 ffed