EuroAsm

Тема в разделе "EuroAsm", создана пользователем Mikl___, 29 апр 2026.

  1. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.249

    EuroAssembler ― Кроссплатформенный ассемблер с открытым исходным кодом

    Автор ― программист Pavel Šrubař из небольшого чешского городка Vítkov (город в Опавском районе Моравскосилезского края Чехии. Население около 5500 человек). Трудился в IT отделе Чешской Почты города Vítkov. Сейчас живет в Праге.
    Электронная почта pavel.srubar@post.cz,
    твиттер http://twitter.com/vitsoft
    Сайт
    Форум посвященный EuroAsm
    vitsoft.jpg
    Домашняя страница euroassembler.eu. Скачать EuroAssembler можно отсюда, прямая ссылка на актуальную версию . Ассемблер написан на себе самом, это даёт компактность и портабельность, но на него нервно реагируют эвристики некоторых антивирусов. После распаковки у вас в руках будет euroasm.exe размером в четыреста килобайт. Исходный код ассемблера открыт и доступен, его несложно пересобрать в случае необходимости.
     
  2. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.249
    Это переделанная и урезанная статья Об ассемблере EuroAssembler, о котором вы, возможно, не слышали Андрея Дмитриева (@AndreyDmitriev) с хабра.
    Если ограничиться Windows, тогда обнаружим, что под эту ОС существует несколько макро-ассемблеров. Если основным инструментом является Visual Studio, то будет логичен выбор ассемблера MASM. Он достаточно популярен и бесплатен. Из альтернатив можно упомянуть FASM (Flat Assembler) и NASM (Netwide Assembler). У каждого диалекта ассемблера (MASM, TASM, FASM, NASM, A386) есть свои плюсы и минусы. Есть и малоизвестные, например входящий в состав Pelles C. Эти ассемблеры могут отличаться синтаксисом и идеологией (кто-то может ассемблировать в объектный файл, а другие имеют встроенный линковщик) но речь не о них. Важно понимать, что на "чистом" ассемблере далеко не уехать, поэтому у них есть приставка "макро", когда можно упростить программирование при помощи макросов.

    Первая программа

    Единственный способ научиться программировать — это начать писать программы, для начала сложим два числа и выведем результат в консоль (чистый "Hello, World!" будет состоять из одного макроса и вообще не будет содержать ни строчки на ассемблере, так что разбавим пример хотя бы mov и add). Всё, что вам надо знать, это то, что у процессора есть Регистры (коих шестнадцать штук общего назначения) и Инструкции, которыми он оперирует.
    Структура минимальной программы на этом ассемблере очень проста, вот она полностью:
    Код (ASM):
    1. EUROASM
    2. AddWorld PROGRAM Format=PE, Entry=Start
    3. INCLUDE winapi.htm, cpuext32.htm
    4. Result D 8*B
    5. Start: nop
    6. mov eax, 17
    7. add eax, 29
    8. StoD Result
    9. StdOutput =B"17+29=", Result
    10. TerminateProgram
    11. ENDPROGRAM
    Давайте разберём все строчки, их тут десяток всего-то. Ссылки на документацию приводятся.
    Программа начинается с ключевого слова EUROASM, за которым вообще говоря могут идти опции, но в этой минимальной программе они не нужны (потому что выставлены по умолчанию).
    Следом идёт имя программы и ключевое слово (псевдоинструкция) PROGRAM, перед которой находится имя программы, за которым две опции — формат PE, что означает Portable Executable (если бы мы писали DLL, то было бы очевидно DLL), и точка входа (может быть любая строка — Start, Begin или main — всё, что хотите, вы видите эту метку чуть ниже). Кстати, в одном файле может быть несколько секций PROGRAM, тогда у них должны быть разные имена, и в этом случае компиляцией одного файла можно сразу получить несколько исполняемых файлов или библиотек.
    Затем следует INCLUDE — здесь мы включаем две библиотеки макросов, из одной мы возьмём макрос перевода числа в строку, а из второй — вывод в консоль. И расширение .htm — это не ошибка — да, вы можете хранить код в HTML. Включать можно не только библиотеки, но вообще любые файлы, обычно им даётся расширение *.inc.
    Result D 8*B резервирует восемь байт для результата (для короткого числа в этом примере нам больше и не надо).
    Следом идёт метка Start: , это входная точка программы, куда будет передано выполнение после загрузки в память, и инструкция nop. Наличие оператора nop помогает евроассемблеру отделить код от данных — так работает автосегментация. Это позволяет избавиться от явного указания секций [.text] и [.data]. Также в отладчике этот NOP удобно видеть как "метку" начала программы и начала отладки.
    mov eax, 17 заносит значение 17 в регистр EAX, а add eax, 29 добавляет туда 29. Регистр не важен — можете писать MOV EAX,17, но в основном используются строчные буквы.
    StoD Result берёт значение RAX (там 46) и переводит его в ASCII строку "46", копируя в буфер, на который указывает Result. Как бы аналог itoa(). На самом деле перевод числа в строку на чистом ассемблере — не такая уж тривиальная задача, загляните в исходник по ссылке выше, там больше полусотни инструкций.
    Ну а StdOutput =B"17+29=", Result выводит на экран 17+29=46. =B — это "синтаксический сахар", позволяющий "заинлайнить" строковую константу-литерал, кроме того макрос StdOutput может принимать переменное число аргументов. Для вывода в консоль надо вначале получить хендл через GetStdHandle(), затем писать через WriteConsole(), при этом строка может содержать Юникод, от всего этого и избавляет данный макрос.
    Программа завершается через TerminateProgram (тоже макрос, который вызывает ExitProcess() из kernel32.dll) и ключевое слово ENDPROGRAM (если у вас несколько программ в одном файле, то там понадобится имя программы). TerminateProgram не есть обязательная вещь — программа завершится и так, но формально пусть будет — ей можно передать код возврата ошибки.
    Создайте файл AddWorld.asm, можно в блокноте Windows и скопируйте туда код, что был выше, сохраните этот файл там же, где находится euroasm.exe (либо скопируйте весь евроассемблер в %APPDATA%\eurotool и добавьте путь в PATH). Сборка программы осуществляется с помощью команды
    euroasm.exe AddWorld.asm
    Если всё скопировано без ошибок, тогда после компиляции появится файл AddWorld.exe, который выведет сообщение
    17+29=46

    Маленький лайфхак — если вам лень ставить редактор с поддержкой ассемблер-синтаксиса, равно как и возиться с командным промптом, просто поставьте Far Manager, встроенный редактор (новый файл Shift+F4, редактирование F4) понимает ассемблер и раскрашивает код, а компиляцию можно упростить через пользовательское меню, всё вместе это будет выглядеть как-то так (как видите, все папки дистрибутива не нужны, достаточно maclib и objlib):
    le4u7tvqlnaowc_pqi_pbpzxte4.gif
    Нехитрый код выше оставляет широкий простор для экспериментов. Например, вы можете не хардкодить, а попросить пользователя ввести числа из консоли, давайте для разнообразия сделаем в 64 бит:
    Код (ASM):
    1. EUROASM CPU=x64
    2. %^SourceName PROGRAM Format=PE, Width=64, Entry=Start
    3. Buffer1   D 32*B
    4. Buffer2   D 32*B
    5. Result    D 32*B
    6. INCLUDE winabi.htm, cpuext64.htm
    7. Start: nop
    8. StdOutput =B"Enter 1st Operand ]"
    9. StdInput Buffer1
    10.          StdOutput =B"Enter 2nd Operand ]"
    11. StdInput Buffer2
    12. LodD Buffer1
    13. mov rbx, rax
    14. LodD Buffer2
    15. add rax, rbx
    16. StoD Result
    17. StdOutput =B"Result:", Result
    18.         TerminateProgram
    19. ENDPROGRAM
    Здесь несколько небольших изменений.
    Во-первых, имя программы заменено на переменную %^SourceName. Так удобнее работать в сценарии "один файл—одна программа", потому что имя будет браться из имени файла (и именно под этим именем будет создаваться исполняемый файл или библиотека DLL). Переменных там много — €ASM system %variables.
    Кроме того, мы переехали на 64 бита добавлением CPU=x64 и Width=64 и соответственно поправили INCLUDE, также вместо 32-битных регистров типа EAX используется 64-бит RAX.
    Затем добавлены два буфера для входных строк.
    StdOutput вы уже знаете, этот макрос выведет приглашающий промпт, а вот StdInput получит данные, введённые пользователем и запишет введённое значение в буфер, на который указывает Buffer1 (как ASCII символы, включая перевод строки). Затем всё повторяется для второго операнда. Таким образом, если мы введём "42", то в буфере будут 0x34, 0x32, 0x0D, 0x0A.
    Теперь нам надо конвертировать ASCII строку в значение, что-то типа atoi(), это делает макрос LodD, который возвращает значение в регистр RAX (теперь у нас 64 бит). Мы сохраним это значение в RBX, и повторим LodD для второго буфера. Теперь в RBX у нас первый операнд, а в RAX второй. Команда add RAX, RBX их складывает, и дальше всё как и выше. Важный момент — при таком использовании вы должны быть абсолютно уверены, что LodD Buffer2 не изменит значение RBX! (чтобы в этом убедиться, достаточно заглянуть в исходник макроса).
    Предположим, в качестве следующего упражнения мы хотим использовать стек для хранения первого введённого операнда, затолкаем RAX в стек и вытащим его в RBX:
    Код (ASM):
    1.  LodD Buffer1
    2. push rax
    3. LodD Buffer2
    4. pop rbx
    5. add rax, rbx
    6. StoD Result
    Кстати, в этом ассемблере поддерживаются множественные переменные при работе со стеком, то есть не надо писать отдельно push rax, push ebx, а можно одной командой push rax, rbx — это удобно.
    Либо можно завести временную переменную, зарезервировав восемь байт памяти:
    Код (ASM):
    1. TempVar   D Q      ; Reserve one qword.
    2. ; ...
    3. LodD Buffer1
    4. mov [TempVar], rax
    5. LodD Buffer2
    6. add rax, [TempVar]
    7. StoD Result
    Можно получить значения из аргументов командной строки, для этого есть макросы GetArgCount и GetArg. В общем не бойтесь экспериментировать. Ассемблер снабжён неплохой инструкцией, кроме того довольно большим количеством примеров и проектов, в конце есть несколько для Windows, от простого консольного приложения, поддерживающего юникод до заготовки оконного приложения.
    Как справедливо заметили в комментариях, этот ассемблер может "собрать" файл для Линукса в том числе, всё что нужно для этого — заменить формат PE на ELFX и включить linapi.htm вместо winapi.htm, вот минимальный код:
    Код (ASM):
    1. EUROASM
    2. HelloWorld PROGRAM Format=ELFX, Entry=Start
    3. INCLUDE linapi.htm
    4. Start: nop
    5. StdOutput =B"Hello, World!", Eol=yes
    6. TerminateProgram
    7. ENDPROGRAM
    после сборки которого вы получите файл HelloWorld.x, который можно тут же запустить под WSL:
    $ ./HelloWorld.x
    Hello, World!
    $ file HelloWorld.x
    HelloWorld.x: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, not stripped

    Более того, вы можете собирать исполняемые файлы одновременно для Windows и Linux (равно как и 32- и 64-бит версии) сложив общий код во включаемый файл и пользуясь тем фактом, что в одном файле можно иметь множественные секции PROGRAM, единственная хитрость — нужно сбросить макросы между программами:
    Код (ASM):
    1. EUROASM AutoSegment=Yes, CPU=X64, SIMD=AVX2
    2. HelloLinux PROGRAM Format=ELFX, Width=64, Entry=Start:
    3. INCLUDE linabi.htm, cpuext64.htm
    4. INCLUDE Code.asm ; < Your code
    5. ENDPROGRAM HelloLinux
    6. %DROPMACRO *  ; Forget macros defined in "linabi.htm".
    7. HelloWindows PROGRAM Format=PE, Width=64, Entry=Start:
    8. INCLUDE winabi.htm, cpuext64.htm
    9. INCLUDE Code.asm
    10. ENDPROGRAM HelloWindows
    Кстати, этот метод работает "в обе стороны", в том смысле, что вы можете не только собирать программы для Линукса из под Windows, но и наоборот из под Линукса под Windows, запуская euroasm.x.

    Отладчик

    Очень рекомендуется овладеть отладчиком. Можно использовать WinDbg, но многие предпочитают x64dbg, хотя он не без проблем (в смысле общей стабильности), но во многом удобнее.
    Как им пользоваться? Допустим, вы не очень уверенно понимаете, как работает стек. Пишете небольшое приложение, которое заносит значения в два регистра, заталкивает их в стек, обнуляет (xor rax, rax — стандартный способ, привыкайте) и вытаскивает обратно:
    Код (ASM):
    1. EUROASM CPU=x64
    2. %^SourceName PROGRAM Format=PE, Width=64, Entry=Start
    3. Start: nop
    4. mov rax, 0x17
    5. mov rbx, 0x29
    6. push rax, rbx
    7. xor rax, rax
    8. xor rbx, rbx
    9. pop rbx, rax
    10. jmp Start
    11. ENDPROGRAM
    Запустите отладчик x64dbg (для отладки 32 бит приложения надо будет запускать 32-бит отладчик, у нас же 64 бита), затем откройте файл приложения (F3), после чего однократно нажмите F9 для загрузки и перемещения на точку входа, вы должны остановиться на NOP, затем пройдите пошагово, нажимая F7. Вот что вы увидите:
    xegi0ccvby4g0qfnvielilsjibw.gif
    Хорошо видно, как уменьшается значение регистра RSP (указатель стека) на 8 байт при каждой инструкции push и как данные записываются в область памяти, на которую указывает указатель стека. Заметьте также, что ассемблер заменил инструкции mov rax, .. на mov eax, .., сэкономив вам несколько байт.
    Ещё полезная вещь — листинг компиляции, который для примера выше выглядит вот так:
    Код (Text):
    1. |                              |EUROASM CPU=x64
    2. |                              |%^SourceName PROGRAM Format=PE, Width=64,...
    3. |[.text]                       ::::Section changed.
    4. |00000000:                     |
    5. |00000000:90                   |Start: nop
    6. |00000001:B817000000           | mov rax, 0x17
    7. |00000006:48BB2900000000000000 | mov rbx, 0x29, IMM=Q
    8. |00000010:5053                 | push rax, rbx
    9. |00000012:4831C0               | xor rax, rax
    10. |00000015:4831DB               | xor rbx, rbx
    11. |00000018:5B58                 | pop rbx, rax
    12. |0000001A:EBE4                 | jmp Start
    13. |                              |ENDPROGRAM
    14. |   **** ListMap "StackTest.exe",model=FLAT,groups=0,segments=2,entry=Start
    15. | [.text],FA=0200h,VA=00401000h,size=28,width=64,align=0010h,purpose=CODE
    16. | [.rsrc],FA=0400h,VA=00402000h,size=13660,width=32,align=0010h,purpose=RES
    Тут в левой колонке показаны машинные коды, в которые будут преобразованы инструкции. NOP — это машинный код 0х90 (который знает наизусть каждый реверс-инженер). Эти же машинные коды вы видите и в отладчике выше. Для примера я указал, что хочу получить именно 64-бит код для второго mov, добавив модификатор IMM=Q, и вы видите появившийся префикс 48 и 64 бит константу в восьми байтах. Вся архитектура фон Неймана раскрывается во всей красе.
    Внизу вы видите виртуальный базовый адрес 00401000h (он складывается из стандартной базы 0х400000 и смещения), его же вы видите и в отладчике и секции файла, они выровнены на границу четырёх килобайт (0х1000), что составляет стандартный размер страницы памяти Windows. Всё просто.

    Эксперимент — WinAPI

    Вам никто не запрещает напрямую вызывать функции WinAPI прямо из Ассемблера, в 64-бит программе это делается следующим образом, для совсем тривиального примера два последовательных вызова GetTickCount64(), разделённых Sleep(1000):
    Код (ASM):
    1. EUROASM CPU=x64, SIMD=AVX2
    2. %^SourceName PROGRAM Format=PE, Width=64, Entry=Start
    3. Elapsed D 32*B
    4. INCLUDE winabi.htm, cpuext64.htm
    5. Start: nop
    6. WinABI GetTickCount64
    7. push rax
    8. WinABI Sleep, 1000
    9. WinABI GetTickCount64
    10. pop rbx
    11. sub rax, rbx
    12. StoD Elapsed
    13. StdOutput =B"Sleep(1000) - ", Elapsed, =B" ms", Eol=yes
    14. jmp Start
    15. ENDPROGRAM
    Макрос WinABI следует соглашениям о вызове 64-бит функций — результат GetTickCount64() возвращается в RAX, параметр 1000 передаётся в Sleep() через RCX. Как результат вы будете видеть разницу в диапазоне 1000...1016 миллисекунд — так работает таймер низкого разрешения. В принципе ИИ способен достаточно внятно объяснить этот код выше.
    Само собой, вы можете вызывать не только WinAPI, но и любые экспортированные функции из любой DLL, вот, к примеру, если рудиментарный вывод в консоль вас не устраивает, вы вполне можете воспользоваться стандартной printf(...) из msvcrt.dll:
    Код (ASM):
    1. EUROASM CPU=X64, SIMD=AVX2
    2. printf1 PROGRAM Format=PE, Width=64, Model=Flat, Entry=main:
    3. INCLUDE winabi.htm
    4. LINK msvcrt.lib ; for printf(...)
    5.  
    6. main: nop
    7. mov rax, 42
    8. WinABI printf, =B"printf: The Answer is %%d", rax
    9. ENDPROGRAM
    При этом поддерживается и динамический вызов при отсутствии *.lib файла, так тоже можно:
    Код (ASM):
    1. EUROASM CPU=X64, SIMD=AVX2
    2. printf2 PROGRAM Format=PE, Width=64, Model=Flat, Entry=main:
    3. INCLUDE winabi.htm
    4.  
    5. main: nop
    6. mov rax, 42
    7. WinABI printf, =B"printf: The Answer is %%d", rax, Lib=msvcrt.dll
    8. ENDPROGRAM
    И поскольку евроассемблер — это и ассемблер и линковщик "в одном флаконе", то существует удобный скрипт, генерирующий библиотеку импорта lib из динамической библиотеки — dll2lib.htm.
    Вообще использовать ассемблер для микробенчмаркинга очень хорошо, поскольку всё находится в ваших руках, позволяя спуститься на уровень машинного кода, но, конечно же не при помощи GetTickCount. Ниже ещё пара примеров. Обычно в этом месте дотошные читатели резонно замечают, мол всё тоже самое можно получить на Си/С++ заметно меньшими усилиями, и это в общем так, но в данном случае мы не зависим от компилятора, его версии и опций оптимизатора, и всё под контролем. Ниже ещё несколько примеров на ассемблере, они не настолько велики, чтобы ради них писать отдельные статьи, но в контексте изложения — вполне уместны.

    Эксперимент — работа предсказателя переходов

    Не так давно на хабре была статья Ловушка профилирования, где были получены довольно любопытные результаты. Суть там была в том, что используя google benchmark производились замеры времени исполнения кода с переходами и эквивалентного кода без них, и внезапно выяснилось, что результат зависим не только от кода, но и от данных, которые используются — при проходе по одному и тому же массиву результаты улучшались, а при изменении данных от прогона к прогону — ухудшались. Вот как можно провести поверку результатов на ассемблере.
    Для начала нам понадобится пара нехитрых макросов, которыми мы будем обкладывать наш код, время выполнения которого мы хотим измерить и макрос, который выведет результат. Предполагается, что мы вызовем код много раз в цикле и возьмём минимальное время прохода — это стандартный способ для синтетического бенчмаркинга:
    Код (ASM):
    1. Buffer  DB 80 * B
    2. StartBench %MACRO
    3. CPUID
    4. RDTSC
    5. shl rdx, 32
    6. or rax, rdx
    7. mov r8, rax
    8. %ENDMACRO
    9. EndBench %MACRO Min
    10. RDTSCP
    11. shl rdx, 32
    12. or rax, rdx
    13. sub rax, r8
    14. mov rbx, [%Min]
    15. cmp rax, rbx
    16. cmova rax, rbx
    17. mov [%Min], rax
    18. %ENDMACRO
    19. PrintBench %MACRO Message, Min
    20. mov rax, [%Min]
    21.     StoD Buffer
    22.     StdOutput =B%Message, Buffer, =B" Ticks", Eol=Yes, Console=Yes
    23. Clear Buffer, Size=80
    24. mov [%Min], -1, DATA=Q
    25. %ENDMACRO
    Здесь используется инструкция RDTSC для получения тиков тактового генератора, работающего на базовой частоте процессора. Пара CPUID/RDTSC...RDTSCP — это классический подход для того чтобы свести к минимум влияние конвейеризации на результаты. Значение при старте сохраняется в R8. Затем в конце сравнивается с минимальным значением, которое обновляется. Обратите внимание на инструкцию cmova — это стандартный способ избавиться от явного перехода if (value<min) min = value; а сравниваем мы беззнаковые числа.
    Ещё нам понадобится генератор случайных чисел, который заполнит массив Array размером %SIZE случайными байтами 0 или 1. Этот код, кстати, генерированный ИИ копилотом чуть более чем полностью и в общем живой и рабочий, при этом количество вызовов rdrand минимально — мы берём оттуда одиночные биты, так что один вызов поставляет нам 64 случайных числа, я оставлю оригинальные комментарии как есть:
    Код (ASM):
    1. random PROC
    2. mov rdi, Array
    3. mov rcx, %SIZE
    4. xor rdx, rdx
    5. .next_byte:
    6.     test    rcx, rcx
    7.     jz      .done             ; finished
    8.     ; if no bits left in rbx, get a new 64-bit random value
    9.     test    rdx, rdx
    10.     jnz     .have_bits
    11. .get_rdrand:
    12.     rdrand  rbx               ; random 64-bit value in rbx
    13.     jnc     .get_rdrand       ; retry if CF=0 (no random value)
    14.     mov     rdx, 64           ; 64 bits available
    15. .have_bits:
    16.     ; take lowest bit of rbx, store it as a byte 0 or 1
    17.     mov     al, bl            ; copy low byte
    18.     and     al, 1             ; keep only lowest bit (0 or 1)
    19.     mov     [rdi], al         ; store into buffer
    20.     shr     rbx, 1            ; drop used bit
    21.     dec     rdx               ; one less available bit
    22.     inc     rdi               ; advance buffer pointer
    23.     dec     rcx               ; one less byte to fill
    24.     jmp     .next_byte
    25. .done:
    26.     ret
    27. ENDPROC random
    Вот, почти всё готово, теперь мы напишем небольшой тест, который запустим десять тысяч раз, меняя содержимое массива на каждом проходе вызовом call random:
    Код (ASM):
    1. EUROASM CPU=X64, SIMD=AVX2, SPEC=Enabled
    2. %^SourceName PROGRAM Format=PE, Width=64, Model=Flat, Entry=main
    3. %SIZE %SET 4096
    4. %ITER %SET 10000
    5. INCLUDE winscon.htm, winabi.htm, cpuext64.htm
    6. INCLUDE Benchmark.inc, Random.inc
    7. Array:  DB %SIZE * BYTE ; 4 KiB Buffer
    8. RCycl DB Q -1
    9. NCycl DB Q -1
    10. main:nop
    11. StdOutput =B"Branch prediction benchmark", Eol=yes
    12. ;-----------------------------------------------------------------------------
    13. ; First Test - random array every time
    14. mov  r10, %ITER             ; number of timing iterations
    15. loop:
    16. call random
    17. StartBench
    18.     mov  r11, %SIZE             ; loop counter N
    19.     lea  r12, [Array]           ; r12 = address of byte array
    20.     xor  r13, r13               ; r13 = taken-branch counter (per timing run)
    21. loop_start:
    22.     mov  bl, [r12]              ; load current value (0 or 1)
    23.     test bl, bl
    24.     jz   branch ; branch-taken path
    25.     inc  r13                     ; do some harmless work
    26. branch: ; no work for 0, but still advance
    27.     inc  r12                     ; next byte
    28.     dec  r11
    29.     jne  loop_start
    30. EndBench RCycl
    31.     dec  r10
    32.     jnz  loop
    33. PrintBench "Rand Array: ", RCycl
    В регистре R13 у нас будет количество переходов, при случайном массиве размеров 4К должно быть что-то около двух тысяч, это просто для самоконтроля.
    Ну а второй тест мы будем запускать по одному и тому же массиву:
    Код (ASM):
    1. ; Second Test - same array every time
    2. mov  r10, %ITER
    3. loop2:
    4. StartBench
    5.     mov  r11, %SIZE
    6.     lea  r12, [Array]
    7.     xor  r13, r13
    8. loop_start2:
    9.     mov  bl, [r12]
    10.     test bl, bl
    11.     jz   branch2
    12.     inc  r13
    13. branch2:
    14.     inc  r12
    15.     dec  r11
    16.     jne  loop_start2
    17. EndBench NCycl
    18.     dec  r10
    19.     jnz  loop2
    20. PrintBench "Same Array: ", NCycl
    21. TerminateProgram
    22. ENDPROGRAM
    И вот результат на процессоре Хасвелл:
    Код (Text):
    1. Branch prediction benchmark
    2. Rand Array: 51960 Ticks
    3. Same Array: 36524 Ticks
    4.  
    Как видите, в первом случае процессору потребовалось в полтора раза больше времени, так работает постоянно ошибающийся предсказатель переходов. Он на самом деле многоуровневый и может "удержать" в памяти до 4К последних переходов. Я изначально полагал, что основное влияние оказывает кэш, но нет, это именно эффект предсказателя. Можно воспользоваться профилировщиком VTune либо Intel PCM и убедиться, что количество промахов предсказателя значительно выше в первом случае. Это на самом деле неплохой результат для цикла, в котором семь инструкций, отрабатывающего 4 тысячи итераций. В качестве самостоятельного упражнения попробуйте оставить массив, забитый нулями (да просто закомментируйте вызов call random добавив перед этой строкой ";") и вы увидите, как количество тиков упадёт где-то до восьми тысяч — это всего два такта на итерацию. Так работает конвейер вкупе с предсказателем, всегда верно угадывающим переход — ведь современный процессор может выполнять несколько инструкций, таких как комбинации dec/jne и dec/jnz при верно предсказанном переходе за один такт.

    Эксперимент — сравнение INC и ADD

    Ещё один эксперимент, основанный на статье Может ли устареть инкремент....
    Содержимое регистра можно увеличить на единицу двумя способами:
    1. inc rax
    2. add rax, 1
    Есть ли разница между этими командами? Небольшой эксперимент на ассемблере поможет ответить и на этот вопрос. Кроме того в рамках этого эксперимента мы может воочию увидеть латентность и пропускную способность инструкций. Замеры мы будем проводить двумя способами — в одном случае мы будем просить выполнить инкремент одного и того же регистра, это даст нам зависимость по данным, а во втором случае — независимых регистров, и в этом случае процессор может начать их параллельное выполнение соответственно пропускной способности. Значения латентности и пропускной способности можно проверить в таблицах uops.info.
    Чтобы выдать последовательность одних и тех же команд без утомительного копипастинга , мы воспользуемся макро языком ассемблера. Основная управляющая конструкция выглядит вот так:
    Код (ASM):
    1. i  %FOR  0..10000
    2. inc eax
    3. %ENDFOR i
    Здесь будет выдано десять тысяч последовательных команд inc eax, одна за одной. Если бы мы использовали нативный цикл ассемблера, то счётчик цикла "сбил" бы нам результат измерений, его пришлось бы учитывать, а в данном случае у нас именно непрерывная последовательность. Она не должна быть очень большой, желательно, чтобы мы полностью поместились в кэш инструкций, но и не маленькой, чтобы эффект был хорошо заметен.
    Теперь важно понять следующее. Когда мы выдаём команды inc eax, inc eax, ... одну за одной — они зависимы по данным, это значит, что процессор должен формально дождаться выполнения предыдущей инструкции, чтобы начать следующую (хотя и не всегда — это зависит от архитектуры). Количество тактов, затрачиваемое на одну такую инструкцию — это латентность. Однако если наши инструкции будут независимы, то процессор может начать выполнение следующей, не дожидаясь предыдущую, и количество таких инструкций, выполняемых за один такт — это пропускная способность. Последовательность из десяти тысяч независимых инструкций создаётся вот так:
    Код (ASM):
    1. i  %FOR  0..2500
    2. inc rax
    3. inc rbx
    4. inc rcx
    5. inc rdx
    6. %ENDFOR i
    Здесь у нас 2500 раз повторённая последовательность из четырёх команд, всё вместе — 10000.
    Кстати, в этом ассемблере вы можете комбинировать множественные инкременты, то есть код "inc rax, rbx, rcx, rdx" - это ровно тоже самое, что и четыре отдельных инкремента выше.
    Код (ASM):
    1. EUROASM AutoSegment=Yes, CPU=X64, SIMD=AVX2
    2. %^SourceName PROGRAM Format=PE, Width=64, Model=Flat, IconFile=, Entry=main:
    3. %ITER %SET 250_000
    4. INCLUDE winscon.htm, winabi.htm, cpuext64.htm
    5. INCLUDE benchmark.inc
    6. Cycles DB Q -1
    7. main: nop
    8.     mov r9, %ITER
    9. L1: ; --- Latency for ADD Instruction
    10.     StartBench
    11. i  %FOR  0..10000
    12.     add eax, 1
    13. %ENDFOR i
    14.     EndBench Cycles
    15.     dec r9
    16.     jnz L1
    17.     PrintBench "ADD cycles (Latency) = ", Cycles
    18.     mov r9, %ITER
    19. L2: ; --- Latency for INC Instruction
    20.     StartBench
    21. i  %FOR  0..10000
    22.     inc eax
    23. %ENDFOR
    24.     EndBench Cycles
    25.     dec r9
    26.     jnz L2
    27.     PrintBench "INC cycles (Latency) = ", Cycles
    28.     mov r9, %ITER
    29. L3: ; --- Throughput for ADD Instruction
    30.     StartBench
    31. i  %FOR  0..2500
    32.     add rax, 1
    33.     add rbx, 1
    34.     add rcx, 1
    35.     add rdx, 1
    36. %ENDFOR i
    37.     EndBench Cycles
    38.     dec r9
    39.     jnz L3
    40.     PrintBench "ADD cycles (Throughput) = ", Cycles
    41.     mov r9, %ITER
    42. L4: ; --- Throughput for INC Instruction
    43.     StartBench
    44. i  %FOR  0..2500
    45.     inc rax, rbx, rcx, rdx
    46. %ENDFOR i
    47.     EndBench Cycles
    48.     dec r9
    49.     jnz L4
    50.     PrintBench "INC cycles (Throughput) = ", Cycles
    51.     TerminateProgram
    52. ENDPROGRAM
    И вот результат для процессора Xeon E5-1620 v3 (Haswell):
    >AddInc.exe
    ADD cycles (Latency) = 10028 Ticks
    INC cycles (Latency) = 10028 Ticks
    ADD cycles (Throughput) = 3376 Ticks
    INC cycles (Throughput) = 3364 Ticks

    Всё красиво — для 10000 зависимых инструкций процессору надо примерно 10000 тиков, это ровно одна инструкция на такт, а вот для независимых инструкций — втрое меньше, потому что он начинает выполнять три инструкции за каждый такт. И нет, разницы между INC и ADD ровно никакой. Единственное отличие в длине машинного кода, ведь add eax, 1 это три байта 83C001, а вот inc eax — только два FFC0. Более компактный код занимает меньше места в кэше инструкций и в общем предпочтительнее.
    Ситуация, кстати, поменяется, если погонять этот код на P и E ядрах гибридного процессора, например на Core i7-13850HX, вот там ADD инструкция окажется предпочтительнее на Е ядрах, но это уже совсем другая история. RDTSC на самом деле показывает количество тиков на базовой частоте процессора, а он как правило работает на повышенной частоте и в реальности количество тактов на данном процессоре окажется заметно выше, кроме того придётся делать поправку на разную частоту ядер и лучше использовать RDPMC, но там есть свои тонкости, о которых написано в статье Достучаться до RDPMC, но вот про исключение, которое можно спровоцировать этой инструкцией, хотелось бы написать особо.
     
  3. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.249

    Эксперимент — обработка исключений

    При программировании на Ассемблере не бойтесь обрабатывать исключения. Вы с процессором "один на один" и можете легко повесить операционную систему. На выброшенные исключения натыкался каждый программист, и каждый, работающий с С++ в курсе про __try... __except, но не каждый программист точно знает, как именно исключение обрабатывается, и ассемблер может помочь разобраться в пречине возникновения исключения.
    Запись по нулевому указателю, равно как и деление на нуль — это слишком уж просто, там можно избежать исключения банальной проверкой. Возьмём пример посложнее, когда шансов нет — попросим процессор выполнить привилегированную инструкцию не имея на это соответствующего разрешения. Вызовем RDPMC, OUT или IN.
    Простейшая программа на ассемблере:
    Код (ASM):
    1. EUROASM CPU=x64
    2. %^SourceName PROGRAM Format=PE, Width=64, Entry=Start
    3. INCLUDE winabi.htm
    4. Start:     nop
    5.     StdOutput =B"Before RDPMC Call", Eol=yes
    6.     xor ecx, ecx
    7.     RDPMC ; Exception!
    8.     StdOutput =B"After RDPMC Call", Eol=yes
    9.     TerminateProgram
    10. ENDPROGRAM
    При запуске вы увидите первое сообщение, но не увидите второго, потому что в просмотрщике событий вы увидите ошибку с кодом 0хс0000096:
    rkf---n9z2tmrx1fm2dedm7g5ai.png
    Это документированная ошибка STATUS_PRIVILEGED_INSTRUCTION.
    Ровно того же эффекта вы добьётесь если попробуете на С++ __readpmc():
    Код (C++):
    1. #include <iostream>
    2. #include <windows.h>
    3. int main()
    4. {
    5.     // This will fault unless RDPMC is enabled for user mode
    6.     std::cout << "Before RDPMC call" << std::endl;
    7.     uint64_t value = __readpmc(0);
    8.     std::cout << "RDPMC value: " << value << std::endl;
    9.     return 0;
    10. }
    Однако не всё так плохо, ведь вы можете сделать вот так:
    Код (C++):
    1. #include <iostream>
    2. #include <windows.h>
    3. int main()
    4. {
    5.     std::cout << "Before RDPMC call" << std::endl;
    6.     __try {
    7.         // This will fault unless RDPMC is enabled for user mode
    8.         uint64_t value = __readpmc(0);
    9.         std::cout << "RDPMC value: " << value << std::endl;
    10.     }
    11.     __except (EXCEPTION_EXECUTE_HANDLER) {
    12.         std::cout << "SEH caught RDPMC exception!" << std::endl;
    13.     }
    14.     return 0;
    15. }
    И в этом случае программа не упадёт, она честно выдаст SEH caught RDPMC exception!
    И вот тут если вы попросите объяснить, как именно производится структурированная обработка исключений, то многие затруднятся ответить, а на самом деле там всё относительно несложно.
    Вот эквивалентный код на ассемблере, заодно и протестируем адекватность кнопки "объяснить код":
    Код (ASM):
    1. EUROASM CPU=X64, SIMD=AVX2
    2. %^SourceName PROGRAM Format=PE, Width=64, Model=Flat, IconFile=, Entry=Start
    3. INCLUDE winscon.htm, winabi.htm, cpuext64.htm
    4. [.text]
    5. Start: nop
    6.        StdOutput =B"Hello, SEH", Eol=yes
    7. try:   MOV ECX,0 ; Instructions Retired
    8.        RDPMC ; EXCEPTION_PRIV_INSTRUCTION (0xC0000096)
    9. safe_place:
    10.        StdOutput =B"Sucessfully finished", Eol=yes
    11.        TerminateProgram
    12. handler:
    13.        SUB RSP,8*(4+1) ; 0x0F8 is offset to CONTEXT64.Rip:
    14.        mov [R8+0x0F8], safe_place, DATA=Q
    15.        StdOutput =B"Instruction caused exception", Eol=yes
    16.        XOR EAX,EAX
    17.        ADD RSP,8*(4+1)
    18.        retn
    19. [.data]
    20.        align 4 ; alignment is required
    21. UNWIND DB 0x19,0,0,0 ; Hard coded for the moment
    22.        DD RVA# handler
    23.        DD 0
    24. [.pdata] SEGMENT PURPOSE=EXCEPTION
    25.        DD RVA# try
    26.        DD RVA# safe_place
    27.        DD RVA# UNWIND
    28. ENDPROGRAM
    Здесь есть три важных адреса: try — это то место, где может поплохеть, затем safe_place: — это там, где снова станет хорошо, и handler:, которое суть обработчик.
    Чтобы сообщить операционной системе о том, как мы собираемся обрабатывать ошибку, служит секция [.pdata], туда занесены три адреса (по сути это RUNTIME_FUNCTION структура) — собственно критическое место и безопасное продолжение, а также адрес UNWIND_INFO структуры. Магическое число 0х19 образуется из трёх битов, где один отвечает за версию, а другие два говорят о том, что у нас есть есть SEH‑обработчик UNW_FLAG_EHANDLER с пользовательским обработчиком UNW_FLAG_UHANDLER. Следом идёт относительный адрес обработчика (тут все адреса относительные, поэтому RVA, это как раз добавилось в свежей версии этого ассемблера). Теперь, когда мы налетаем на грабли инструкцией RDPMC, ядро операционной системы первым делом просматривает таблицу обработчиков, если её нет, то программа аварийно завершается, а вот если есть, управление передаётся нашему обработчику handler:. Но это не просто передача управления, по сути под капотом идёт вызов функции с четырьмя параметрами, которые передаются согласно соглашению о вызовах Win64 ABI. Вот почему нам первой же командой нужно выравнивание стека на 4 параметра плюс один — это адрес возврата (можно и SUB RSP, 48 сделать, хуже не будет). Четыре параметра, которые нам передаются, берутся из вот такого прототипа
    Код (C++):
    1. typedef EXCEPTION_DISPOSITION (*PEXCEPTION_ROUTINE) (
    2.     IN PEXCEPTION_RECORD ExceptionRecord,
    3.     IN ULONG64 EstablisherFrame,
    4.     IN OUT PCONTEXT ContextRecord,
    5.     IN OUT PDISPATCHER_CONTEXT DispatcherContext
    6. );
    Соответственно они передаются через регистры RCX, RDX, R8 и R9. Из всего этого нас интересует лишь структура PCONTEXT ContextRecord, адрес который лежит в R8, так как это третий параметр. Смещение 248 байт 0x0F8 — это поле RIP. А RIP это указатель адреса текущей инструкции. Именно сюда мы записываем адрес безопасного продолжения safe_place. Больше от нас ничего не требуется, мы выводим сообщение, что нас настигло исключение, сбрасываем код ошибки и восстанавливаем стек обратно. По выходу из процедуры обработчика исключения ядро выставит наш желаемый "безопасный" RIP, и мы выведем последнее сообщение. Вот и всё. На самом деле можно усложнить — например получить код исключения, и т.д.
    Это как раз тот пример, когда ассемблер помогает понять механизм работы.
    На этом можно было бы остановиться, но хотелось бы добавить, что код на ассемблере можно собрать и в DLL, которую вызвать из любого языка, который это допускает, начиная от Си и Питона и заканчивая Растом и LabVIEW, что открывает возможности для практического применения ассемблерного кода и интегрирования его в сторонние приложения.

    DLL на ассемблере

    Чтобы не усложнять, давайте просто сложим пару байтовых массивов, но используя SIMD, и вызовем полученную библиотеку, скажем из LabVIEW. Помимо очевидной замены РЕ на DLL нам потребуется занести нашу функцию в таблицу экспорта
    Код (ASM):
    1. EUROASM CPU=X64, SIMD=AVX2, AMD=ENABLED
    2. AsmDLL64 PROGRAM FORMAT=DLL, MODEL=FLAT, WIDTH=64
    3. EXPORT add_bytes_avx2
    4. ; void add_bytes_avx2(const uint8_t* a,
    5. ;                     const uint8_t* b,
    6. ;                     uint8_t* c,
    7. ;                     size_t n);
    8. add_bytes_avx2 PROC
    9.     test    r9, r9
    10.     jz      done
    11.     ; number of full 32-byte blocks
    12.     mov     r10, r9
    13.     shr     r10, 5            ; r10 = n / 32
    14.     jz      tail
    15. avx_loop:
    16.     vmovdqu ymm0, [rcx]
    17.     vmovdqu ymm1, [rdx]
    18.     vpaddb  ymm0, ymm0, ymm1
    19.     vmovdqu [r8], ymm0
    20.     add     rcx, 32
    21.     add     rdx, 32
    22.     add     r8,  32
    23.     dec     r10
    24.     jnz     avx_loop
    25. tail:
    26.     ; remaining bytes
    27.     and     r9, 31
    28.     jz      done
    29. tail_loop:
    30.     mov     al, [rcx]
    31.     add     al, [rdx]
    32.     mov     [r8], al
    33.     inc     rcx, rdx, r8
    34.     dec     r9
    35.     jnz     tail_loop
    36. done:
    37.     vzeroupper ; important for ABI
    38.     ret
    39. ENDP add_bytes_avx2
    40. ENDPROGRAM AsmDLL64
    И результат:
    qzeundz77vjfvl0umhnk9wjht0m.png
     
  4. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.249

    Вывод информации в консоль

    Код (ASM):
    1. EUROASM AutoSegment=Yes, CPU=X64, SIMD=AVX2
    2. cvidemo PROGRAM Format=PE, Width=64, Model=Flat, IconFile=, Entry=main:
    3. INCLUDE winscon.htm, winabi.htm, cpuext64.htm
    4. WelcomeMsg D "Hello, CVI and EuroAssembler!",0
    5. Buffer DB 80 * B
    6. AnswerMsg D "EuroAsm: The Answer is ",0
    7. main: nop
    8.     StdOutput WelcomeMsg, Eol=Yes, Console=Yes ; standard EuroAsm Macro
    9.     mov rax, 42
    10.     StoD Buffer ; https://euroassembler.eu/maclib/cpuext64.htm#StoD
    11.     StdOutput AnswerMsg, Buffer, Eol=Yes, Console=Yes
    12. ; ...
    используя Fmt и FmtOut функции из NI LabWindows / CVI. Все, что вам нужно, это создать ссылку против cvirt.lib, которая вызывает cvirte.dll во время выполнения:

    Код (ASM):
    1. EUROASM AutoSegment=Yes, CPU=X64, SIMD=AVX2
    2. cvidemo PROGRAM Format=PE, Width=64, Model=Flat, IconFile=, Entry=main:
    3. INCLUDE winscon.htm, winabi.htm, cpuext64.htm
    4. FmtBuf DB 80 * B
    5. FmtMsg D "CVI The Answer is %%d",13,10,0
    6. FortyTwo DB D 42
    7. LINK cvirt.lib ; For Fmt and FmtOut  
    8. main: nop
    9.     StdOutput WelcomeMsg, Eol=Yes, Console=Yes ; standard EuroAsm Macro  
    10.     mov rax, 42
    11.     WinABI FmtOut, FmtMsg, rax ; Direct Output to the Console
    12.      WinABI FmtOut, FmtMsg, 42  ; From Constant
    13.      WinABI FmtOut, FmtMsg, [FortyTwo] ; From Memory
    14. ;...
    это работает и с переменными с плавающей запятой:
    Код (ASM):
    1. FmtMsgF D "Float variable: %%f[p2] (Answer)",13,10,0
    2. Numerator DO Q 42.0
    3. Denominator DO Q 13.0
    4. ;...
    5.     mov rbx, 42
    6.     mov rdx, 13
    7.     movq xmm0, rbx ; movq xmm0, [numerator]
    8.     movq xmm1, rdx ; movq xmm1, [denominator]
    9.     divsd xmm0, xmm1
    10.     WinABI FmtOut, FmtMsgF, xmm0 ; Float Point
    11. ;...
    Полный пример кода:
    Код (ASM):
    1. ;/==============================================================================
    2. ;/
    3. ;/ Title:        CVI Demo for Fmt and FmtOut Formatting Functions
    4. ;/ Purpose:        How to call these from Assembly
    5. ;/
    6. ;/ Created on:    06.10.2025 at 11:32:43 by AD.
    7. ;/
    8. ;/==============================================================================
    9. EUROASM AutoSegment=Yes, CPU=X64, SIMD=AVX2
    10. cvidemo PROGRAM Format=PE, Width=64, Model=Flat, IconFile=, Entry=Start:
    11. INCLUDE winscon.htm, winabi.htm, cpuext64.htm
    12. WelcomeMsg D "Hello, CVI and EuroAssembler!",0
    13. Buffer DB 80 * B
    14. AnswerMsg D "EuroAsm: The Answer is ",0
    15. FmtBuf DB 80 * B
    16. FmtMsg D "CVI The Answer is %%d",13,10,0
    17. FortyTwo DB D 42
    18. FmtMsgF D "Float variable: %%f[p2] (Answer)",13,10,0
    19. FmtMsgFO D "%%s<%%f[p1] Divided by %%f[p1] is %%f[p2]",13,10,0
    20. Numerator DO Q 42.0
    21. Denominator DO Q 13.0
    22. InputNumerator D "Input Numerator (Float) :",0
    23. InputDenominator D "Input Denominator (Float) :",0
    24. BufferNumerator DB 80 * B
    25. BufferDenominator DB 80 * B
    26. ScanFmt D "%%s>%%f",0
    27. ScanInFmt D "%%l>%%f",0
    28. LINK cvirt.lib  
    29. Start: nop
    30. ;/==============================================================================
    31. ;/ Standard EuroAssembler
    32. ;/
    33.     StdOutput WelcomeMsg, Eol=Yes, Console=Yes ; standard EuroAsm Macro
    34.     mov rax, 42
    35.     StoD Buffer ; https://euroassembler.eu/maclib/cpuext64.htm#StoD
    36.     StdOutput AnswerMsg, Buffer, Eol=Yes, Console=Yes
    37. ;/==============================================================================
    38. ;/ CVI:
    39. ;/
    40.     WinABI Fmt, FmtBuf, FmtMsg, rax ; Fmt to Buffer
    41.     StdOutput FmtBuf,  Console=Yes  ; Buffer Output
    42.     mov rax, 42
    43.     WinABI FmtOut, FmtMsg, rax ; Direct Output to the Console
    44.      WinABI FmtOut, FmtMsg, 42  ; From Constant
    45.      WinABI FmtOut, FmtMsg, [FortyTwo] ; From Memory
    46. ;/==============================================================================
    47. ;/ Float Point:
    48. ;/
    49.     mov rbx, 42
    50.     mov rdx, 13
    51.     movq xmm0, rbx ; movq xmm0, [numerator]
    52.     movq xmm1, rdx ; movq xmm1, [denominator]
    53.     divsd xmm0, xmm1
    54.     WinABI FmtOut, FmtMsgF, xmm0 ; Float Point
    55.     movq xmm0, [Numerator]
    56.     movq xmm1, [Denominator]
    57.     divsd xmm0, xmm1
    58.     WinABI FmtOut, FmtMsgFO, [Numerator], [Denominator], xmm0
    59.  
    60. ;/==============================================================================
    61. ;/ With Input from EuroAssembler:
    62. ;/
    63.     StdOutput InputNumerator, Console=Yes ; standard EuroAsm Macro
    64.     StdInput BufferNumerator, Console=Yes
    65.     WinABI Scan, BufferNumerator, ScanFmt, Numerator
    66.     StdOutput InputDenominator, Console=Yes ; standard EuroAsm Macro
    67.     StdInput BufferDenominator, Console=Yes
    68.     WinABI Scan, BufferDenominator, ScanFmt, Denominator
    69.     movq xmm0, [Numerator]
    70.     movq xmm1, [Denominator]
    71.     divsd xmm0, xmm1
    72.     WinABI FmtOut, FmtMsgFO, [Numerator], [Denominator], xmm0
    73. ;/==============================================================================
    74. ;/ With Input from NI CVI:
    75. ;/
    76.     WinABI FmtOut, InputNumerator
    77.     WinABI ScanIn, ScanInFmt, Numerator
    78.     WinABI FmtOut, InputDenominator
    79.     WinABI ScanIn, ScanInFmt, Denominator
    80.     movq xmm0, [Numerator]
    81.     movq xmm1, [Denominator]
    82.     divsd xmm0, xmm1
    83.     WinABI FmtOut, FmtMsgFO, [Numerator], [Denominator], xmm0
    84.     TerminateProgram
    85. ENDPROGRAM
    Выход
    >cvidemo.exe
    Hello, CVI and EuroAssembler!
    EuroAsm: The Answer is 42
    CVI The Answer is 42
    CVI The Answer is 42
    CVI The Answer is 42
    CVI The Answer is 42
    Float variable: 3.23 (Answer)
    42.0 Divided by 13.0 is 3.23
    Input Numerator (Float) :1
    Input Denominator (Float) :2
    1.0 Divided by 2.0 is 0.50
    Input Numerator (Float) :3
    Input Denominator (Float) :4
    3.0 Divided by 4.0 is 0.75
     
    Последнее редактирование: 3 май 2026
  5. Ahimov

    Ahimov Active Member

    Публикаций:
    0
    Регистрация:
    14 окт 2024
    Сообщения:
    646
    Mikl___

    > Полный пример кода

    Это не пример кода, это скрипт. Со сборкой бинаря никак не связан:

    Код (Text):
    1. WelcomeMsg D "Hello, CVI and EuroAssembler!",0
    2. Buffer DB 80 * B
    3. AnswerMsg D "EuroAsm: The Answer is ",0
    4. main: nop
    В какой секции pe определены эти данные, где учет r/w/e ?
     
  6. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.249
    Ahimov,
    а я понятия не имею. Я только позавчера узнал об этом EuroAssembler'e. Создатель FASM ― поляк Томаш Грыштар (Tomasz Grysztar). Здесь чех ― Павел Шрубарш (Pavel Šrubař)
    И там, и там работа программистов одиночек. И FASM, и EuroAsm написаны на себе самих, компактные и портабельные. Поэтому создал раздел и притащил всё, что нашел. Если этот ассемблер заинтересует остальных ― я буду рад... И это не скрипт, это именно код с таким вот синтаксисом.
     
    Последнее редактирование: 7 май 2026 в 10:15
  7. Marylin

    Marylin Active Member

    Публикаций:
    0
    Регистрация:
    17 фев 2023
    Сообщения:
    328
    Здесь главное понять, что в нём есть такого, чего нет в остальных ассемблерах. То есть, что интересного он нам может предложить? А тупо перестраиваться на новый синтаксис идея так себе. Лично мне он чёто не нравится, хотя может нужно просто привыкнуть.
     
    Mikl___ и deepfacer нравится это.
  8. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.249
    Marylin,
    мне кажется, что это "игрушка" похожая на FASM, который сначала тоже всерьез не воспринимали. Пока буду искать примеры, и что-нибудь из MASM64 попробую переписать на EuroAsm, а там помотрим :)
     
    Marylin нравится это.
  9. Entropy

    Entropy Member

    Публикаций:
    0
    Регистрация:
    23 авг 2020
    Сообщения:
    269
    тут в коде, указание компилятору LINK,то есть получается он делает готовый exe-файл и линкует lib-файл
    Код (ASM):
    1.  
    2. EUROASM CPU=X64, SIMD=AVX2
    3. printf1 PROGRAM Format=PE, Width=64, Model=Flat, Entry=main:
    4. INCLUDE winabi.htm
    5. LINK msvcrt.lib ; ---< FASM Такого не умеет
    6. main: nop
    7. mov rax, 42
    8. WinABI printf, =B"printf: The Answer is %%d", rax
    9. ENDPROGRAM
    10.  
     
    Mikl___ нравится это.
  10. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.249
    Entropy, Marylin,
    я думаю, что это хорошо продуманный и хитросделанный продукт программистской мысли. Лето близко, постараюсь оторваться на €ASM ;)
     
  11. Marylin

    Marylin Active Member

    Публикаций:
    0
    Регистрация:
    17 фев 2023
    Сообщения:
    328
    Mikl___, скинь какой нибудь экзе сюда типа HelloWorld,
    чтобы посмотреть, что он из себя представляет.
    Как упоминул Ahimov, атрибуты есть там вообще?
     
    Mikl___ нравится это.
  12. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.249
    Набираю
    Код (ASM):
    1. HelloW64 PROGRAM Format=PE, Entry=Main:, Width=64 ; HelloW64.exe works in 64-bit Windows.
    2.          INCLUDE winabi.htm ; Define 64-bit macros
    3.    Main: WinABI MessageBox,0,="Hello, world of %^Width bits in Windows!",="Title",0, Lib=user32.dll
    4.          TerminateProgram Errorlevel=0
    5. ENDPROGRAM HelloW64
    сохраняю как HelloW64.asm
    для компиляции и линковки в командной строке набираю
    euroasm.exe HelloW64.asm

    появился HelloW64.exe
    06.png
     

    Вложения:

    • HelloW64.zip
      Размер файла:
      736 байт
      Просмотров:
      36
    Последнее редактирование: 6 май 2026
    Marylin нравится это.
  13. Marylin

    Marylin Active Member

    Публикаций:
    0
    Регистрация:
    17 фев 2023
    Сообщения:
    328
    У автора данного ассемблера много недоработок ещё, т.к. продукт видимо сырой.
    Из того-что сразу бросилось в глаза в "PE-Anatomist" и "CFF-Explorer", это флаг(Х) у секции импорта (зачем? чтоб авер проснулся), а так-же наличия секции релоков в EXE для поддержки ASLR, при том-что сам механизм ASLR отключён битом 0x40 и 0x20 в поле "DllCharacteristics" опц.заголовка. В общем компиль много самовольничает.
     
    Mikl___ нравится это.
  14. Research

    Research Active Member

    Публикаций:
    1
    Регистрация:
    6 янв 2024
    Сообщения:
    502
    А не проще масм до ума довести? Имхо там компиль не глупые люди писали, и есть смысл идти через тернии.
    Сделать/найти макросы. Переписать уроки Iczelion'a. Придумать новые примеры(строки, кодировки текста, ресурсы).
     
    Последнее редактирование: 6 май 2026
    GRAFik и Mikl___ нравится это.
  15. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.249

    Asm-файл

    Набираю
    Код (ASM):
    1. EUROASM UNICODE=Enabled     ; This option selects ANSI or WIDE version.
    2. EUROASM CPU=X64,SIMD=Yes,DumpWidth=32
    3.              INCLUDE "winsgui.htm"       ; This file defines the constant WM_APP.
    4. %IconResName %SET SkeletIcon
    5. %MenuResName %SET SkeletMenu
    6. %MenuIdExit  %SETA WM_APP + 1
    7. %MenuIdHelp  %SETA WM_APP + 2
    8. %MenuIdAbout %SETA WM_APP + 3
    9. %StatusBarId %SETA WM_APP + 9
    10. ;;
    11. skelet64     PROGRAM Format=PE, Subsystem=GUI, Width=64, Entry=WinMain
    12.                INCLUDE winabi.htm, wins.htm, winsgui.htm, fastcall.htm, \
    13.                        cpuext.htm, cpuext64.htm
    14.                LINK winapi.lib, skelet.res
    15. [.data]
    16. InfoText   D "Skeleton of Windows program written in EuroAssembler."
    17.            DU 0 ; This NUL character works both for ANSI and WIDE variants.
    18. HelpText   D "Press [F1] to show this help.",13,10, \
    19.              "Press [F2] to show information about this program.",13,10, \
    20.              "Press [Esc] to quit the program.",0
    21. %IF %^UNICODE
    22.   %Char %SET UNICODE
    23. %ELSE
    24.   %Char %SET ANSI
    25. %ENDIF
    26. AboutText  D "This %Char %^WIDTH[]bit program %^PROGRAM.exe was created",13,10, \
    27.              "by EuroAssembler ver.%^VERSION %^EUROASMOS",13,10, \
    28.              "on %^DATE[7..8].%^DATE[5..6].%^DATE[1..4] %^TIME[1..2]:%^TIME[3..4] UTC.",0
    29. StatusInfo D "Status bar displays information about menu items.",0
    30. WndClassName D "SKELET",0
    31. [.bss]
    32. WndClassEx   DS WNDCLASSEX ; Definition of the window class structure.
    33. hWindow      D QWORD       ; Handle of the window object.
    34. hStatusBar   D QWORD       ; Handle of the status bar.
    35. Msg          D MSG                  ; Window message.
    36. PaintStruct  DS PAINTSTRUCT ; Structured variable used in painting.
    37. hDC          D QWORD        ; Handle of device context used in painting.
    38. [.text]
    39. WinMain PROC                      ; Program entry point.
    40.      Clear SEGMENT#[.bss],Size=SIZE#[.bss] ; Make sure to start with zeroed memory of uninitialized reserved data.
    41.      CALL WndCreate               ; Initialize the program window.
    42. .MsgLoop:
    43.      WinABI GetMessage, Msg,0,0,0
    44.      TEST RAX
    45.      JZ .MsgQuit:                 ; ZF signalizes message WM_QUIT - request for program termination.
    46.      WinABI TranslateMessage, Msg ; Remap character keys from national keyboards.
    47.      WinABI DispatchMessage,  Msg ; Let Windows call our WndProc.
    48.      JMP .MsgLoop:                ; Wait for another message.
    49. .MsgQuit:
    50.      TerminateProgram Errorlevel=[Msg.wParam]
    51.   ENDPROC WinMain
    52. WndProc Procedure hWnd, uMsg, wParam, lParam ; These parameters are provided in RCX,RDX,R8,R9.
    53.     SaveToShadow
    54.     Uses RBX,RSI,RDI ; It's only necessary if some of callee-save registers was used in this fastcalled procedure.
    55.     ; Fork message uMsg=RDX to its handler using macro Dispatch:
    56.      Dispatch EDX, WM_CREATE, WM_DESTROY, WM_PAINT, WM_KEYDOWN, WM_COMMAND, WM_MENUSELECT
    57. .Def:WinABI DefWindowProc,[%hWnd],[%uMsg],[%wParam],[%lParam]  ; Pass ignored event to DefWindowProc with unchanged arguments.
    58.      JMP .Ret:  ; Go to EndProcedure with result value RAX as returned from DefWindowProc.
    59.      ; All message handlers terminate with a jump to label .Def: or .Ret0:.
    60. .WM_CREATE: ; The main window is being created.
    61.      WinABI SendMessage,[hStatusBar],SB_SIMPLE,1,0  ; Tell the status bar to be simple.
    62.      WinABI SendMessage,[hWindow],WM_MENUSELECT,0,0 ; Initialize status bar with StatusInfo.
    63.      JMP .Ret0:
    64. .WM_PAINT:  ; Window needs to repaint its contents.
    65.      WinABI BeginPaint,[hWindow],PaintStruct
    66.      MOV [hDC],RAX
    67.      WinABI TextOut,RAX,30,30,InfoText,SIZE# (InfoText) << %^UNICODE ; TextOutW expects text size in characters.>>
    68.                                     ; That's why the size in bytes is right-shifted by 1 when %^UNICODE is -1.
    69.      WinABI EndPaint,[hWindow],PaintStruct
    70.      JMP .Ret0:
    71. .WM_COMMAND:   ; User selected menu item identified by R8=wParam.
    72.      Dispatch R8, %MenuIdExit, %MenuIdHelp, %MenuIdAbout
    73.      JMP .Def: ; Pass unhandled items to WinAPI DefWindowProc.
    74. .WM_KEYDOWN:   ; Non-character hot key R8=wParam was pressed.
    75.      Dispatch R8, VK_ESCAPE, VK_F1, VK_F2
    76.      JMP .Def: ; Pass ignored keys to WinAPI DefWindowProc.
    77. .%MenuIdExit:  ; Menu Exit selected.
    78. .VK_ESCAPE:    ; Esc pressed.
    79.      WinABI SendMessage,RCX,WM_DESTROY,0,0
    80.      JMP .Ret0:
    81. .%MenuIdHelp:  ; Menu Help selected.
    82. .VK_F1:        ; F1 pressed.
    83.      WinABI MessageBox,RCX,HelpText,WndClassName,MB_ICONINFORMATION
    84.      JMP .Ret0:
    85. .%MenuIdAbout: ; Menu About selected.
    86. .VK_F2:        ; F2 pressed.
    87.      WinABI MessageBox,RCX,AboutText,WndClassName,MB_ICONINFORMATION
    88.      JMP .Ret0:
    89. .WM_MENUSELECT: ; User unrolled a menu item. Show online help in status bar.
    90. MenuStatus PROC ; Namespace MenuStatus is used to avoid collision in %MenuId* labels.
    91.      AND R8,0x0000_FFFF  ; Menu identifier is in the low word of wParam.
    92.      Dispatch R8,%MenuIdExit,%MenuIdHelp,%MenuIdAbout
    93.      MOV R10,StatusInfo ; Use neutral StatusInfo help text for undispatched menu items.
    94.  .ShowStatus:WinABI SendMessage,[hStatusBar],SB_SETTEXT,SB_SIMPLEID,R10
    95.      JMP WndProc.Ret0:
    96.  .%MenuIdExit: MOV R10,="Terminate program."
    97.                JMP .ShowStatus:
    98.  .%MenuIdHelp: MOV R10,="Show help information."
    99.                JMP .ShowStatus:
    100.  .%MenuIdAbout:MOV R10,="Show information about this program."
    101.                JMP .ShowStatus:
    102.  ENDP MenuStatus
    103. .WM_DESTROY: ; Program terminates.
    104.      WinABI PostQuitMessage,0     ; Tell Windows to quit this program with errorlevel 0.
    105.     ; JMP .Ret0:
    106. .Ret0:XOR EAX,EAX
    107. .Ret:EndProcedure WndProc
    108. WndCreate PROC
    109.     ; Register class SKELET for the main window.
    110.     MOV [WndClassEx.cbSize],SIZE# WNDCLASSEX
    111.     MOV [WndClassEx.lpszClassName],WndClassName
    112.     MOV [WndClassEx.style],CS_HREDRAW|CS_VREDRAW
    113.     MOV [WndClassEx.lpfnWndProc],WndProc
    114.     WinABI GetModuleHandle,0
    115.     MOV [WndClassEx.hInstance],RAX
    116.     MOV RDX,="%IconResName"           ; Icon name used in resources.
    117.     WinABI LoadIcon,RAX,RDX           ; Load icon handle from resources.
    118.     MOV [WndClassEx.hIcon],RAX
    119.     WinABI LoadCursor,0,IDC_HAND      ; Load cursor handle from stock.
    120.     MOV [WndClassEx.hCursor],RAX
    121.     WinABI GetStockObject,WHITE_BRUSH ; Default window background colour.
    122.     MOV [WndClassEx.hbrBackground],RAX
    123.     MOV [WndClassEx.lpszMenuName], ="%MenuResName" ; Menu name used in resources.
    124.     WinABI RegisterClassEx, WndClassEx
    125.     ; Define the main window.
    126.     WinABI CreateWindowEx, WS_EX_CLIENTEDGE,                        \
    127.            WndClassName, WndClassName, WS_OVERLAPPEDWINDOW,         \
    128.            CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT, \
    129.            0, 0, [WndClassEx.hInstance], 0
    130.     MOV [hWindow],RAX
    131.     ; Define status bar as the child of the main window.
    132.     WinABI CreateStatusWindow,WS_CHILD+WS_BORDER+WS_VISIBLE, \
    133.            StatusInfo, [hWindow], %StatusBarId
    134.     MOV [hStatusBar],RAX
    135.     WinABI ShowWindow, [hWindow], SW_SHOWNORMAL
    136.     WinABI UpdateWindow, [hWindow]
    137.     RET
    138.    ENDP WndCreate
    139.    ENDPROGRAM skelet64
    сохраняю как skelet64.asm

    Ресурсы

    Код (Text):
    1. skelet PROGRAM Format=BIN, OutFile="skelet.rc"
    2. %MenuIdExit  %SETA WM_APP + 1
    3. %MenuIdHelp  %SETA WM_APP + 2
    4. %MenuIdAbout %SETA WM_APP + 3
    5. D ' /* Resource definition for skeleton programs        ',13,10
    6. D '     which are shipped with EuroAssembler. */        ',13,10
    7. D '%IconResName ICON "skelet.ico"             ',13,10
    8. D '%MenuResName MENU                                    ',13,10
    9. D '              BEGIN                                  ',13,10
    10. D '                POPUP "&File"                        ',13,10
    11. D '                BEGIN                                ',13,10
    12. D '                  MENUITEM "E&xit  [Esc]",%MenuIdExit',13,10
    13. D '                END                                  ',13,10
    14. D '                POPUP "&Help"                        ',13,10
    15. D '                BEGIN                                ',13,10
    16. D '                  MENUITEM "&Help  [F1]",%MenuIdHelp ',13,10
    17. D '                  MENUITEM "&About [F2]",%MenuIdAbout',13,10
    18. D '                END                                  ',13,10
    19. D '               END                                   ',13,10
    20.        ENDPROGRAM skelet
    сохраняю как skelet.asm

    Компиляция и линковка

    В командной строке набираю
    euroasm.exe skelet.asm
    появился skelet.rc. С помощью команды
    rc.exe skelet.rc
    создаю skelet.res. Для компиляции и линковки в командной строке набираю
    euroasm.exe skelet64.asm
    появился skelet64.exe
    06.png
    В аттаче ico-/res-/rc-/asm-/exe-файлы
     

    Вложения:

    • skelet64.zip
      Размер файла:
      10,6 КБ
      Просмотров:
      34
    Последнее редактирование: 9 май 2026 в 03:02
  16. Research

    Research Active Member

    Публикаций:
    1
    Регистрация:
    6 янв 2024
    Сообщения:
    502
    --- --
    --- --
    --- --
    --- --

     
    Последнее редактирование модератором: 6 май 2026
  17. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.249
    Research,
     
    Research нравится это.
  18. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.249

    Выводы

    Непривычно, похоже на FASM. Компилятор, линкер и компилятор ресурсов в одном флаконе. Как компилировать/линковать указано в самом asm-файле, опять таки очень похоже на FASM. Еще бы и IDE написанную на EuroAssembler. Синтаксис интуитивно понятен и чувствуется влияние FASM (или мне так кажется? :scratch_one-s_head:). Плюс замечание от Marylin
    Так что решать Вам. Но всё же
    Вот тут я обеими руками за! :good2:
     
  19. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.249
    Характеристики EuroAsm
    • EuroAsm — макроассемблер с синтаксисом Intel для архитектуры IA-32 и x64 AMD и Intel.
    • Также может работать как компоновщик, линкер, конвертер объектов и менеджер make-файлов.
    • EUROASM.EXE — 32-битное консольное приложение для MS-Windows и Linux, которое считывает исходный текст, написанный на языке ассемблера, и создает скомпилированный объектный или исполняемый файл, а также листинг.
    • Программы, написанные на EuroAsm, могут работать в 16-\32-\64-битных операционных системах.
    • EuroAsm поставляется с прокомментированным исходным текстом, макробиблиотеками и образцами программ для быстрого старта.
    • EuroAsm доступен бесплатно
    Функции, которые встречаются в EuroAssembler
    • с помощью одного вызова Euroasm можно скомпилировать несколько исходных файлов. Для каждого исходного файла создается отдельный листинг и объектный файл.
      euroasm.exe source1.asm, source2.asm, more*.asm
    • Файл листинга, успешно сгенерированный в ходе предыдущего сеанса сборки, можно снова использовать в качестве исходного кода EuroAssembler. Формат листинга совместим с исходным кодом на ассемблере, поскольку шестнадцатеричный дамп сгенерированного кода игнорируется парсером EuroAssembler.
    • EuroAssembler — итеративный многопроходный макроассемблер с полной поддержкой прямых ссылок и частичной поддержкой типов данных.
    • На метки, символы EQUATED и структуры можно ссылаться (использовать) до того, как они будут определены (хотя это не рекомендуется).
      0000:; Referring structured memory variable Today which will be defined later.
      0000:C706[1000]E007MOV [Today.Year],2016 ; Put immediate value to WORD memory variable.
      0006:C606[1200]0CMOV [Today.Month],12 ; Put immediate value to BYTE memory variable.
      000B:C606[1300]1FMOV [Today.Day],31 ; Put immediate value to BYTE memory variable.
      0010:
      0010:00000000Today DS Datum ; Definition of a structured symbol whose structure will be declared later
      0014:
      [Datum]Datum STRUC ; Declaration of structure Datum.
      0000:.....Year DW WORD
      0002:...Month DB BYTE
      0003:...Day DB BYTE
      0004:ENDSTRUC Datum
    • Инструкции ассемблера можно комбинировать с HTML. Строки исходного кода, начинающиеся с <HTML tags>, считаются комментариями. Это позволяет сохранить связь между исходным кодом на ассемблере и его текстовой документацией.
    • С помощью директив INCLUDE можно импортировать либо весь исходный файл, либо только его части, которые можно указать как диапазон строк или как блок, ограниченный псевдоинструкциями HEAD и ENDHEAD. Подключаемое интерфейсное подразделение HEAD...ENDHEAD программного модуля не обязательно должно находиться в отдельном заголовочном файле (h-файлы на языке C или inc-файлы на языке ассемблер).
    • Ошибки и предупреждения выводятся в стандартный поток вывода и добавляются в листинг прямо под подозрительным оператором. Текст сообщения об ошибке соответствует сути проблемы.
    • EuroAsm исправляет ошибки в исходном тексте. Процесс сборки не останавливается при обнаружении первой ошибки (если только она не является критической).
    • По умолчанию генерируемый код всегда имеет кратчайшую форму, но программист может выбрать более длинный вариант, используя модификаторы инструкций
      00000000:41INC ECX
      00000001:41INC ECX,CODE=SHORT
      00000002:FFC1INC ECX,CODE=LONG
      00000004:
      00000004:83D801SBB EAX,1
      00000007:83D801SBB EAX,1,IMM=BYTE
      0000000A:81D801000000SBB EAX,1,IMM=DWORD
      00000010:
      00000010:E97D000000JMP $+0x82, DIST=SHORT
      ## W2401 Modifier "DIST=SHORT" could not be obeyed in this instruction.
      00000015:
    • Данные могут быть заданы либо явно (с помощью псевдоинструкций D, DB, DW и т. д.), либо с помощью литерального (специального) определения.
      Тип
      данных
      СокращениеРазмер в
      байтах|битах
      Выравнивание
      в байтах
      Typical
      storage
      Строка
      символов
      Целое
      число
      Вещественное
      число
      Packed
      vector
      BYTEB181R8ANSI8-bit
      UNICHARU2162R16WIDE
      WORDW2162R1616-bit
      DWORDD4324R32, ST32-bitSingle precision
      QWORDQ8648R64, ST64-bitDouble precision
      TBYTET10808STExtended precision
      OWORDO1612816XMM4×D2×Q
      YWORDY3225632YMM8×D4×Q
      ZWORDZ6451264ZMM16×D8×Q

      [DATA][DATA] ; Switch to data section.
      0000:4578706C~Explicit DB "Explicit text definition.$",0
      [CОDE][CОDE] ; Switch to code section.
      0020:BA[0000]MOV DX,Explicit
      0023:B409MOV AH,9 ; Write explicit string DS:DX to standard output.
      0025:CD21INT 21h ; Invoke DOS function.
      0027:BA[6400]MOV DX,=B"Implicit text definition (literal).$"
      002A:B409MOV AH,9 ; Write implicit string DS:DX to standard output.
      002C:CD21INT 21h ; Invoke DOS function.
      002E:
    • EuroAssembler поддерживает набор расширенных векторных расширений, включая инструкции Intel Xeon MVEX и EVEX-кодированные инструкции AVX-512.
    • Кроме обычных блоков подпрограмм PROC... ENDPROC EuroAssembler поддерживает полувстроенные процедуры PROC1... ENDPROC1, которые раскрываются из макроса только один раз, при первом вызове.
      EuroAssembler может связывать объектные модули (OMF, ELF, COFF) с исполняемыми форматами (COM, EXE, DLL, ELFX), а также с другими объектными модулями и библиотеками. См. таблицу поддерживаемых комбинаций.
    • При использовании динамически подключаемых функций можно указать их DLL при объявлении импорта, например IMPORT RegCloseKey, LIB="user32.dll". Компоновщик EuroAssembler не требует использования библиотек импорта (хотя они и поддерживаются).
    • Каждый исходный файл может содержать более одного модуля (программы), и каждый такой блок PROGRAM... ENDPROGRAM создает свой собственный объектный или исполняемый файл. Исходный код многомодульного проекта может храниться в одном большом файле, если автор так предпочитает.
    • Параметры командной строки, которые усложняют вызов многих других ассемблеров и компоновщиков, не нужны. Если вы распространяете исходный код своей программы, вам не нужно указывать, как она была создана. Исполняемые программы создаются с помощью простой и единой командной строки
      euroasm source.asm
    • EuroAssembler написан на языке EuroAssembler, его исходный код можно просмотреть онлайн.
    • В следующем примере создаются два варианта программы Hello, world!: HelloL32.x и HelloL64.x. Оба исполняемых файла будут созданы из исходного файла hello.asm с помощью одной команды
      euroasm hello.asm
      . Можно запустить их в Linux или в его эмуляторе для Windows — WSL:
      Код (ASM):
      1.  EUROASM CPU=x64
      2.  
      3. HelloL32 PROGRAM Format=ELFX, Entry=Main:, Width=32 ; HelloL32.x works in 32-bit Linux.
      4.  Main: MOV EAX,4 ; Kernel operation sys_write=4.
      5.  MOV EBX,1 ; File descriptor of the standard output (console).
      6.  MOV ECX,Message ; Address of the message.
      7.  MOV EDX,SIZE# Message ; Size of the message.
      8.  INT 0x80 ; Invoke the kernel.
      9.  MOV EAX,1 ; Kernel operation sys_exit=1.
      10.  XOR EBX,EBX ; Returned errorlevel=0.
      11.  INT 0x80 ; Invoke the kernel.
      12.  Message: DB "Hello, world of %^Width bits in Linux!",10
      13.  ENDPROGRAM HelloL32
      14.  
      15. HelloL64 PROGRAM Format=ELFX, Entry=Main:, Width=64 ; HelloL64.x works in 64-bit Linux.
      16.  Main: MOV RAX,1 ; Kernel operation sys_write=1.
      17.  MOV RDI,1 ; File descriptor of the standard output (console).
      18.  LEA RSI,[Message] ; Address of the message.
      19.  MOV RDX,SIZE# Message ; Size of the message.
      20.  SYSCALL ; Invoke the kernel.
      21.  MOV RAX,60 ; Kernel operation sys_exit=60.
      22.  XOR EDI,EDI ; Returned errorlevel=0.
      23.  SYSCALL ; Invoke the kernel.
      24.  Message: DB "Hello, world of %^Width bits in Linux!",10
      25.  ENDPROGRAM HelloL64
    Мы могли бы скрыть большую часть инструкций по сборке в макроинструкциях из библиотек linapi.htm (32-разрядная версия) и linabi.htm (64-разрядный) и использующий литералы (=B "Привет ...") для определения напечатанных строк:
    Код (Text):
    1. EUROASM CPU=x64
    2.  
    3. HelloL32 PROGRAM Format=ELFX, Entry=Main:, Width=32 ; HelloL32.x works in 32-bit Linux.
    4.  INCLUDE linapi.htm ; Define 32-bit macros StdOutput and TerminateProgram.
    5.  Main: StdOutput =B "Hello, world of %^Width bits in Linux!", Eol=Yes
    6.  TerminateProgram Errorlevel=0
    7.  ENDPROGRAM HelloL32
    8.  
    9.  %DROPMACRO * ; Forget macros defined in "linapi.htm".
    10.  
    11. HelloL64 PROGRAM Format=ELFX, Entry=Main:, Width=64 ; HelloL64.x works in 64-bit Linux.
    12.  INCLUDE linabi.htm ; Define 64-bit macros StdOutput and TerminateProgram.
    13.  Main: StdOutput =B "Hello, world of %^Width bits in Linux!", Eol=Yes
    14.  TerminateProgram Errorlevel=0
    15.  ENDPROGRAM HelloL64
    Аналогичный пример для MS-Windows:
    Код (Text):
    1. EUROASM CPU=x64, SIMD=Yes
    2.  
    3. HelloW32 PROGRAM Format=PE, Entry=Main:, Width=32 ; HelloW32.exe works in 32-bit and 64-bit Windows.
    4.  INCLUDE winapi.htm ; Define 32-bit macros WinAPI and TerminateProgram.
    5.  Main: WinAPI MessageBox,0,="Hello, world of %^Width bits in Windows!",="Title",0, Lib=user32.dll
    6.  TerminateProgram Errorlevel=0
    7.  ENDPROGRAM HelloW32
    8.  
    9.  %DROPMACRO * ; Forget macros defined in "winapi.htm".
    10.  
    11. HelloW64 PROGRAM Format=PE, Entry=Main:, Width=64 ; HelloW64.exe works in 64-bit Windows.
    12.  INCLUDE winabi.htm ; Define 64-bit macros WinABI and TerminateProgram.
    13.  Main: WinABI MessageBox,0,="Hello, world of %^Width bits in Windows!",="Title",0, Lib=user32.dll
    14.  TerminateProgram Errorlevel=0
    15.  ENDPROGRAM HelloW64
    PhaseUsed tool
    design-timeimagination
    write-timetext editor
    assembly-timeassembler
    combine-timelinker
    link-timelinker
    load-timeoperating system loader
    bind-timeoperating system loader
    run-timeprocessor
     
    Последнее редактирование: 9 май 2026 в 03:33
  20. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.249
    Продолжение следует...