Первым делом находим спецификацию 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 ; атрибутов нет
» Полный код можно посмотреть здесь.
Спасибо за внимание!
Комментарии (25)
sshikov
13.09.2016 19:35Вообще, язык макросов FASM настолько мощный, что на нем можно написать еще один язык, причем не одним способом.
А можно написать макро для определения управляющих структур типа if/while? С вложенностью и генерацией меток?
shevmax
13.09.2016 19:43Ну так в FASM и так есть while if else
sshikov
13.09.2016 19:45Есть и "можно сделать" — все же немного разные вещи. Чтобы сделать if, нужно иметь таккую структуру, как стек.
MacIn
13.09.2016 20:27Оно эквивалентно в данном случае, потому что оно «есть» сделано на макросах в виде инклюда. Например:
macro .if [arg] { common __IF equ local ..endif __ENDIF equ ..endif local ..else __ELSE equ ..else JNCOND __ELSE,arg } macro .else { jmp __ENDIF __ELSE: restore __IF __IF equ , }
bigfatbrowncat
13.09.2016 21:28+4(ирония)
Предлагаем вашему вниманию лучший макроязык над ассемблером — СиMacIn
13.09.2016 21:40+1А где ирония-то? Как боженька сказанул.
bigfatbrowncat
13.09.2016 21:52+1А если серьезно, то я щупал как-то ассемблер, причем именно FASM. И пытался писать на нем Win32-приложение. 20 строчек на один вызов MessageBox… В общем, я быстро пришел к необходимости писать макросы. И спустя полчаса поймал себя на том, что изобретаю Паскаль :) И на этом заглох.
Для программирования на Асме нужен особый склад ума, видимо, противоположный моему. Я, приступая к задаче, первым делом стараюсь собрать ее в как можно более высокоуровневые блоки, продумать уровни абстракции… В язык уровня Си или Асма я могу влезать либо из соображений оптимизации, либо из спортивного интереса.MacIn
13.09.2016 22:01И пытался писать на нем Win32-приложение. 20 строчек на один вызов MessageBox…
Вообще-то, {уже} нет.
Вот код примера из стандартной поставки:
; example of simplified Windows programming using complex macro features include 'win32ax.inc' ; you can simply switch between win32ax, win32wx, win64ax and win64wx here .code start: invoke MessageBox,HWND_DESKTOP,"Hi! I'm the example program!",invoke GetCommandLine,MB_OK invoke ExitProcess,0 .end start
bigfatbrowncat
13.09.2016 22:19Ну да, примерно так. А если надо эту строку сформировать…
MacIn
13.09.2016 22:54Да тоже в принципе ничего особого. kernel32 экспортирует всякую мелочь типа конкатенации. Через тот же invoke и вызовется. На самом деле, макросы у fasm а очень богаты, сейчас это уже не просто голый мнемокод.
bigfatbrowncat
14.09.2016 21:46… что, собственно, возвращает нас к шутке про Си.
Мне с моей чисто «академической» колокольни интересно, для какой надобности нынче пишут именно на Ассемблере, если мнемокод мигом превращает его в более высокоуровневый язык (но с явно «протекающей» абстракцией, мешающей, например, отлаживаться)MacIn
14.09.2016 22:04Макрокод, видимо его вы имел в виду. Мнемокод и «язык ассемблера» — это синонимы.
Зачем? Есть специализированные задачи, шеллкоды какие-нибудь, например.
Да и разница между С и ассемблером с макросами все же есть. Вы возьмете FASM, используете макросные ifы, вызовы функций итд и в итоге получите крохотный файл в, скажем, бинарном формате. Без линкера, без заголовков.
Я использовал(ю периодически) для разработки своей ОС, как небольшое хобби.
VioletGiraffe
13.09.2016 21:50И лучший макроязык над С — С++
bigfatbrowncat
13.09.2016 21:53+6Ужасный. Но лучше пока не придумали, да :)
mwizard
13.09.2016 23:10-1Придумали, господа Брайт и Александреску (да, тот самый).
bigfatbrowncat
14.09.2016 21:44-1Видел. Даже использовать пробовал. Обломался об отсутствие нормального кроссплатформенного компилятора, отладчика, IDE с чем-то большим, чем простая подсветка синтаксиса…
Короче, там еще «придумывать» и «придумывать»…
А идея неплоха, да. За мелкими ньюансами.
farwayer
13.09.2016 21:08+1Синтаксис fasm — няшка! Код просто читается, бо синтаксис лаконичный, в отличие от других ассемблеров. Макросы очень мощные. Еще работает под *nix, и есть порт на ARM.
frog
13.09.2016 22:31Это вот сюда надо (вроде бы такого варианта там ещё нет :)
https://github.com/leachim6/hello-world
pengyou
Пишем Hello world на WASM https://github.com/kpmy/tiss/blob/master/demo/bf/bf.go Извините, что не хватает воды на целую статью.