Пишем Hello World на FASM

в 15:37, , рубрики: fasm, java, ненормальное программирование

Одним томным пятничным вечером взбрела мне в голову безумная идея: а почему бы мне не поразмять мозг, и не написать HelloWorld на ассемблере. Однако это показалось слишком простым. А давайте соберем не x86 программу, а java class? Сказано — сделано.

Первым делом находим спецификацию 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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js