Одним томным пятничным вечером взбрела мне в голову безумная идея: а почему бы мне не поразмять
Первым делом находим спецификацию JVM. Как мы видим, файл класса Java состоит из:
- Магическое число, оно всегда равно 0xCAFEBABE
- Минорная и мажорная версии, для Java 7 они равны 0 и 51 соответственно.
- Количество элементов пула констант и сами элементы — об этом ниже.
- Флаги доступа, номер константы, указывающей на текущий класс, номер константы, указывающей на класс-родитель.
- Количество реализуемых интерфейсов и массив номеров их дескрипторов в пуле.
- Количество полей, массив дескрипторов полей.
- Количество методов, массив дескрипторов методов.
- Количество атрибутов, массив атрибутов.
В Java принят порядок big endian, в то время как FASM — ассемблер под x86, где принят little endian, поэтому сразу напишем макросы для перевода чисел из одного порядка в другой:
u1 equ db ; u1 - 1 байт, переворачивать нечего
macro u2 [data] { ; u2 - 2 байта
forward ; передаем в макрос несколько элементов через запятую, например u2 0x1234, 0x5678
; поэтому нужно указать порядок их следования
u1 (((data) shr 8) and 0xFF) ; сначала старший байт
u1 ((data) and 0xFF) ; потом младший
}
macro u4 [data] { ; u4 - 4 байта, по аналогии с u2
forward
u2 (((data) shr 16) and 0xFFFF)
u2 ((data) and 0xFFFF)
}
Вообще, язык макросов FASM настолько мощный, что на нем можно написать еще один язык, причем не одним способом.
Константный пул устроен довольно запутанно, один дескриптор ссылается на другой, он на третий, но разобраться можно — это же все-таки люди сочиняли.
Элементы константного пула в общем случае выглядят так:
cp_info {
u1 tag;
u1 info[];
}
Полный список тегов приведен в документации, подробно я их описывать не буду. Опишу трюк, который я использовал для автоматического подсчета размера пула (а так же методов, полей и т.д.).
Конструкция вида const#name — склеивает текст const и значение из константы name. Конструкция аналогична такой же из макросов языка C.
const_count = 0
macro const name* { ; макрос подсчета
const_#name: const_count = const_count + 1
name = const_count ; объявляем константу (FASM константу) и присваиваем ей значение счетчика
}
Хоть в терминологии FASM-a константы называются константами, на самом деле они ведут себя как переменные, и с ними можно производить многие манипуляции.
Дальше объявляем макросы для начала и для конца:
macro ConstantPool {
u2 const_count_end + 1 ; кладем количество констант в вывод
; +1 потому что 0 константа не существует, но учитывать ее надо
}
Но ведь такой переменной не существует, скажете вы. И будете правы. Пока не существует. FASM — многопроходной ассемблер, и что не было найдено при первом проходе, он запомнит и подключит при втором или дальнейших.
macro ConstantPoolEnd {
UTF8 code_attr, 'Code'
const_count_end = const_count ; а вот мы и присваиваем значение.
}
При следующем проходе ассемблер подставит вместо const_count_end ровно столько, сколько он насчитал констант.
Методы и поля организованы схожим образом. Приведу пример макроса, генерирующего константу Method
macro UTF8 name, text {
const name
u1 1 ; tag
u2 .end - .start
.start: u1 text
.end:
}
macro Class name, className {
UTF8 className_#name, className
const name
u1 7 ; tag
u2 className_#name
}
macro NameAndType name, nameText, typeText {
UTF8 name_#name, nameText
UTF8 type_#name, typeText
const name
u1 12 ; tag
u2 name_#name
u2 type_#name
}
macro Method name, className, methodName, methodType {
Class class_#name, className ; константа типа Class
NameAndType nameAndType_#name, methodName, methodType ; константа типа NameAndType
const name
u1 10 ; tag
u2 class_#name
u2 nameAndType_#name
}
Тут можно видеть как идут ссылки — Method ссылается на Class, Class ссылается на UTF8, так же и с NameAndType. В макрос передаются аргументы в виде строк, например Method printlnInt, 'java/io/PrintStream', 'println', '(I)V'
.
Ну и напоследок сам исходник:
format binary as 'class'
include 'java.inc'
; --- FILE ---
magic: u4 0xCAFEBABE
version: u4 51
ConstantPool
Class this, 'java'
Class super, 'java/lang/Object'
UTF8 main, 'main'
UTF8 main_sig, '([Ljava/lang/String;)V'
Field o, 'java/lang/System', 'out', 'Ljava/io/PrintStream;'
Method println, 'java/io/PrintStream', 'println', '(Ljava/lang/String;)V'
Method printlnInt, 'java/io/PrintStream', 'println', '(I)V'
String hello, 'Hello World!'
ConstantPoolEnd
u2 PUBLIC, this, super, 0, 0 ; интерфейсов нет, полей нет, поэтому по нулям
Methods
MethodStart PUBLIC or STATIC, main, main_sig, 2, 1
getstatic o
ldc hello ; указываем имя константы, объявленное выше
invokevirtual println
getstatic o
bipush 42
invokevirtual printlnInt
return
MethodEnd
MethodsEnd
u2 0 ; атрибутов нет
» Полный код можно посмотреть здесь.
Спасибо за внимание!
Автор: Pabloid