Пишемо Hello World на FASM

Одним млосним п'ятничним ввечері взбрела мені в голову божевільна ідея: а чому б мені не розім'яти мозок, і не написати 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'
.

Ну і наостанок сам ісходник:

binary format as 'class'
include 'java.inc'
; --- FILE ---
magic: u4 0xCAFEBABE
версія: 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 ; атрибутів немає

» Повний код можна подивитися тут.

Спасибі за увагу!
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.