Пишем 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'.

Ну и напоследок сам исходник:

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 ; атрибутов нет

» Полный код можно посмотреть здесь.

Спасибо за внимание!
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 25

    –3
    Пишем Hello world на WASM https://github.com/kpmy/tiss/blob/master/demo/bf/bf.go Извините, что не хватает воды на целую статью.
      0
      Вообще, язык макросов FASM настолько мощный, что на нем можно написать еще один язык, причем не одним способом.

      А можно написать макро для определения управляющих структур типа if/while? С вложенностью и генерацией меток?

        0
        Ну так в FASM и так есть while if else
          0

          Есть и "можно сделать" — все же немного разные вещи. Чтобы сделать if, нужно иметь таккую структуру, как стек.

            0
            Оно эквивалентно в данном случае, потому что оно «есть» сделано на макросах в виде инклюда. Например:
            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 ,
            }
            
            
              +4
              (ирония)

              Предлагаем вашему вниманию лучший макроязык над ассемблером — Си
                +1
                А где ирония-то? Как боженька сказанул.
                  +1
                  А если серьезно, то я щупал как-то ассемблер, причем именно FASM. И пытался писать на нем Win32-приложение. 20 строчек на один вызов MessageBox… В общем, я быстро пришел к необходимости писать макросы. И спустя полчаса поймал себя на том, что изобретаю Паскаль :) И на этом заглох.

                  Для программирования на Асме нужен особый склад ума, видимо, противоположный моему. Я, приступая к задаче, первым делом стараюсь собрать ее в как можно более высокоуровневые блоки, продумать уровни абстракции… В язык уровня Си или Асма я могу влезать либо из соображений оптимизации, либо из спортивного интереса.
                    0
                    И пытался писать на нем 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
                    
                    
                      0
                      Ну да, примерно так. А если надо эту строку сформировать…
                        0
                        Да тоже в принципе ничего особого. kernel32 экспортирует всякую мелочь типа конкатенации. Через тот же invoke и вызовется. На самом деле, макросы у fasm а очень богаты, сейчас это уже не просто голый мнемокод.
                          0
                          … что, собственно, возвращает нас к шутке про Си.

                          Мне с моей чисто «академической» колокольни интересно, для какой надобности нынче пишут именно на Ассемблере, если мнемокод мигом превращает его в более высокоуровневый язык (но с явно «протекающей» абстракцией, мешающей, например, отлаживаться)
                            0
                            Макрокод, видимо его вы имел в виду. Мнемокод и «язык ассемблера» — это синонимы.

                            Зачем? Есть специализированные задачи, шеллкоды какие-нибудь, например.

                            Да и разница между С и ассемблером с макросами все же есть. Вы возьмете FASM, используете макросные ifы, вызовы функций итд и в итоге получите крохотный файл в, скажем, бинарном формате. Без линкера, без заголовков.
                            Я использовал(ю периодически) для разработки своей ОС, как небольшое хобби.
                        0

                        Не, ну это не интересно. Где прерывания? Регистры? Оффсеты? Джампы в разные куски кода? А в таком виде никакой романтики…

                          0
                          А откуда прерывания в программировании под Win32? Начиная с XPSP2 используется sysenter.
                    0
                    И лучший макроязык над С — С++
                      +6
                      Ужасный. Но лучше пока не придумали, да :)
                        –1

                        Придумали, господа Брайт и Александреску (да, тот самый).

                          –1
                          Видел. Даже использовать пробовал. Обломался об отсутствие нормального кроссплатформенного компилятора, отладчика, IDE с чем-то большим, чем простая подсветка синтаксиса…

                          Короче, там еще «придумывать» и «придумывать»…

                          А идея неплоха, да. За мелкими ньюансами.
                +1
                Можно, эти макросы входят в стандартную поставку.
                +1
                Синтаксис fasm — няшка! Код просто читается, бо синтаксис лаконичный, в отличие от других ассемблеров. Макросы очень мощные. Еще работает под *nix, и есть порт на ARM.
                  0
                  Это вот сюда надо (вроде бы такого варианта там ещё нет :)
                  https://github.com/leachim6/hello-world
                    0
                    C# Randomyst HelloWorld
                    Ideone
                    • НЛО прилетело и опубликовало эту надпись здесь
                        0
                        Это не настоящая программа, это сам class в виде макросов. Если ее транслировать, получится class. Это описательная программа, без действий.

                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                      Самое читаемое