Недавно, было дело, сидел и ждал результатов какой-то конференции на одном из предприятий. Сидеть было скучно, и я вытащил мобильник, чтобы погрузиться в мир убивания времени. Но, к моему огорчению, мы были в месте с нереально слабым сигналом, и я понял, что нахожусь в том странном и непонятном мире, когда интернета нету. Ничего путного на мобиле у меня установлено не было, посему я переключил своё внимание на гостевой лаптоп. Внутрикорпоративный прокси спрашивал логин и пароль для интернета, коих у меня не имелось. Ступор. Я вспомнил 1990-е, когда интернет был только по модему и добывать его надо было через поход на почту или в «Интернет-кафе». Странное чувство.
К счастью, на вышеозначенном компьютере была обнаружена игрушка под названием 2048. Замечательно, подумал я, и погрузился в складывание кубиков на целых 30 минут. Время было убито чётко и резко. Когда пришла пора уходить, я попытался закрыть игрушку, и увидел, что она подвисла. Я по привычке запустил менеджер задач и хотел уже было убить несчастную, когда вдруг мои глаза увидели потребление 250-ти мегабайт оперативной памяти. Волосы встали дыбом под мышками, пока я пристреливал кобылку. Страшные 250 мегабайт оперативки не хотели вылезать из моей головы.
Я сел в машину и поехал домой. Во время поездки я только и думал о том, как можно было так раскормить 2048 до состояния, когда она будет пожирать 250 мегабайт оперативки. Ответ был достаточно прост. Зоркий глаз системщика увидел электрон, который запускал нагружённый яваскриптовый движок, который рендерил на экране 16 16-ти битовых чисел.
И я подумал, а почему-бы не сделать всё намного более компактно? Сколько битов тебе на самом деле надо, для того, чтобы хранить цифровое поле 2048?
Для начала обратимся к интернетам. Учитывая, что мы играем абсолютно правильную игру и все ставки на нас, то при самом хорошем расходе, мы не сможем набрать больше 65536. Ну, или если всё будет в нашу пользу, и мы будем получать блоки с четвёрками в 100 процентах случаев, то мы можем закончить с тайлом в 131072. Но это на грани фантастики.
Итак, у нас есть поле из 16-ти тайлов, размером до 131072, который умещается в Int. В зависимости от битности системы, int может быть 4 или 8 байт. То есть, 16*4 = 64 байта, хватило бы для хранения всего игрового поля.
Хотя, на самом деле, это тоже жутко много. Мы ведь можем хранить степени двойки, так ведь?
;00 = nothing
;01 = 2
;02 = 4
;03 = 8
;04 = 16
;05 = 32
;06 = 64
;07 = 128
;08 = 256
;09 = 512
;0a = 1024
;0b = 2048
;0c = 4096
;0d = 8192
;0e = 16384
;0f = 32768
;10 = 65536 - maximum with the highest number is 2
;11 = 131072 - maximum with the highest number 4
;12 = 262144 - impossible
Ага, мы можем запихнуть каждую клетку поля в один байт. На самом деле, нам нужно всего лишь 16 байт, на то, чтобы хранить всё игровое поле. Можно пойти немного дальше и сказать, что случай, когда кто-то соберёт что-то больше 32768 — это граничный случай, и такого быть не может. Посему можно было бы запихнуть всё поле в полубайты, и сократить размер всего поля до восьми байт. Но это не очень удобно. (Если вы реально забыли бинарное и шестнадцатеричное счисление, то тут нужно просто сесть, погуглить и вспомнить его)
Итак, подумал я, если всё это можно запихнуть в 16 байт, то чего бы этим не заняться. И как же можно отказаться от возможности вспомнить мой первый язык программирования — Ассемблер.
[flashback mode on]
Картинки детства. Выпуск №45
Именно в этой статье я вычитал про разные компиляторы, нашёл мануалы и попробовал писать. Писалось плохо, потому что я понимал, что мне не хватает понимания основ, и нужен был какой-то фундамент, который позволил бы работать более стабильно.
Ужасы детства. Ссылка на издание
Из всех сайтов, приведённых в примерах журнала Хакер, в живых не осталось ни одного. Но, не бойтесь, дело живо и инструкции публикуются. Вот здесь, например, есть одно из самых подробных описаний работы с Ассемблером.
[flashback mode off]
Когда я добрался домой и сел за свой компьютер, я понял, пошёл вспоминать молодость. Как скомпилировать ассемблер? В своё время, когда мы всему этому учились, у нас был TASM, MASM и MASM32. Я лично пользовался последними двумя. В каждом ассемблере был линкер и сам компилятор. Из этих трёх проектов в живых остался только оригинальный MASM.
Для того чтобы его установить в 2021 году, надо сливать Visual Studio и устанавливать кучу оснасток, включая линкер. А для этого надо качать полтора гигабайта оснасток. И хотя я, конечно, нашёл статьи о том, как использовать llvm-link вместо link при работе с Ассемблером, там нужно то ещё скрещивание ужей с ежами и альбатросами. Такими непотребностями мы заниматься не будем.
Хорошо, в таком случае, что? С удивлением обнаружил, что большое количество курсов по Ассемблеру х64 написано для линукса. YASM и NASM там правят бал и работают просто прекрасно. Что хорошо для нас, NASM отлично запускается и работает на Windows. Типа того.
Запускается-то он, запускается, но линкера у него в комплекте нету. (По-русски этот линкер должен называться компоновщиком, но мне это непривычно и звать я его буду линкером или линковщиком). Придётся использовать Майкрософтский линковщик, а как мы знаем, для его использования нам нужно качать гигабайты MSVS2021. Есть ещё FASM, но он какой-то непривычный, а в NASM бонусом идёт отличная система макросов.
Опять же, дружить всё это с llvm-link мне было очень занудно, потому что ни одна из инструкций не описывала того, как эту сакральную магию правильно применять.
Весь интернет пестрит рассказами про то, как прекрасен MinGW. Я же, будучи ленивым, пошёл по упрощённому пути и слил систему разработки CodeBlocks. Это IDE со всякими свистопипелками и, самое главное, наличием установленного MinGW.
Отлично, устанавливаем всё, добавляем в PATH и теперь мы можем компилировать, запуская:
nasm -f win64 -gcv8 -l test.lst test.asm
gcc test.obj -o test.exe -ggdb
Отлично! Давайте теперь сохраним данные в памяти:
stor db 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00
fmt db "%c %c %c %c", 0xd, 0xa,"%c %c %c %c", 0xd, 0xa,"%c %c %c %c", 0xd, 0xa,"%c %c %c %c", 0xd, 0xa, "-------",0xd, 0xa, 0
Вот наше игровое поле
stor
, а вот — беспощадное разбазаривание оперативной памяти — строка форматирования fmt
, которая будет выводить это игровое поле на экран.Соответственно, для того, чтобы обратиться к какой-либо клетке поля, мы можем считать байты следующим образом:
; byte addressing
; 00 00 00 00 [stor] [stor+1] [stor+2] [stor+3]
; 00 01 00 00 [stor+4] [stor+5] [stor+6] [stor+7]
; 00 01 00 00 [stor+8] [stor+9] [stor+a] [stor+b]
; 00 00 00 00 [stor+c] [stor+d] [stor+e] [stor+f]
Тут начинаем втягиваться в разницу того самого 16-ти битного ассемблера под ДОСом из страшного Хакера 2002 года и нашего 64х битного ассемблера прямиком из 2021.
У нас были регистры ax, bx и так далее, помните? Все они делились на две части: _l _h, типа al, ah для записи байта в верхнюю часть ax или в нижнюю его часть. Соответственно, al был восьми битовым, ax был 16-ти битовым, а если вы были счастливым обладателем нормального процессора, то вам был доступен eax для целых 32х бит. Хаха! Добро пожаловать в новые процессоры. У нас теперь есть rax для записи 64х бит.
Что, страшно читать про регистры? Теряетесь и вообще не понимаете о чём идёт речь? Обратитесь к ответу frosty7777777 по адресу qna.habr.com/q/197637. Он приводит список книг по Ассемблеру на русском языке.
Более того, в мире 64х битных процессоров у нас в распоряжении есть не только EAX, EBX, EDX и ECX (не будем забывать про EDI, EBP, ESP и ESI, но и играться с ними тоже не будем). Нам даны R8 – R15 – это замечательные 64х битные регистры. Зубодробилка начинается, если вы хотите считывать данные из этих регистров. Байты можно считать обращаясь к r10b, слова находятся по адресу r10w, двойные слова можно найти по r10d, а ко всем 64ти четырём битам можно обратиться через к10. Почему всё это не назвать так же, как и предыдущие регистры — чёрт его знает. Но ничего, привыкнем.
Более того, благодаря SSE, SSSE и AVX у нас на руках ещё есть 15 регистров по 128 или 256 бит. Они названы XMM0-XMM15 для 128 бит и YMM0-YMM15 для 256 бит. С ними можно вытворять интересные вещи. Но статья не об этом.
Идём дальше. Как выводить данные на экран. Помните ДОС и те замечательные времена, когда мы делали:
mov dx, msg ; the address of or message in dx
mov ah, 9 ; ah=9 - "print string" sub-function
int 0x21 ; call dos services
Теперь забудьте. Прямой вызов прерываний нынче не в моде, и делать этого мы больше не сможем. Если вы ассемблируете под линуксом, вы сможете дёргать системные вызовы, или пользоваться прерыванием 80, которое, отвечает за выплёвывание данных на экран. А вот под Windows у вас нет иных вариантов, кроме как воспользоваться printf. (Нет, конечно, можно было бы получить дескриптор консоли и писать напрямую, но тут уже совсем было бы неприлично). В принципе, это не так-то плохо. Printf это часть стандартной библиотеки Си, и вызывать его можно на чём угодно.
Посему программу мы начнём с пары объявлений для компилятора и линкера:
bits 64
default rel
global main
extern printf
extern getch
extern ExitProcess
Первая строка указывает, что мы работаем на настоящем, ламповом 64х битном процессоре. Последние 3 строки говорят, что нам нужно будет импортировать 3 внешних функции. Две printf и getch для печатания и читания данных и ExitProcess из стандартной библиотеки Windows для завершения приложения.
Соответственно, для того чтоб нам воспользоваться какой-либо из вышеперечисленных функций, нам нужно сделать следующее:
push rbp
mov rbp, rsp
sub rsp, 32
lea rcx, [lost] ;Load the format string into memory
call printf
Сохраняем текущую позицию стека, выравниваем стек и даём ему дополнительные 32 байта. Про магию выравнивания стека можно читать вот здесь. (Статья на английском, как и многие из рекомендованных мною материалов. Комментируйте, если есть на русском, мы добавим.) Загружаем в регистр CX адрес строки под названием
lost
, которая определена как lost db "You are done!",0xd, 0xa, 0
и вызываем printf
, которая эту строку и выведет на экран.Два основных момента, о которых надо знать — это как выравнивать стек, и как передавать параметры в функции. В примере чуть выше, мы передаём только один параметр. А вот для показа значения всех 16 полей в командной строке мы должны передать 16 параметров, для этого нам нужно будет грузить часть их них в регистры, а часть записывать в стек. Вот — очень запутанный пример того, как программа вызывает printf с 16-ю параметрами для того, чтобы отобразить игровое поле на экране.
Хорошо, что мы уже умеем? Можем грузить данные в память и из памяти, перекладывать в многочисленные регистры и запускать функции из стандартной библиотеки.
Будем использовать getch для того, чтобы считать ввод с клавиатуры. Управление будет вимовским, то есть, hjkl для того, чтобы двигать тайлы. Просто пока не будем мучиться со стрелочками.
Что осталось сделать? Написать саму логику программы.
И тут вот в чём прикол. Можно было бы делать математику и прибавлять значения и всё такое, но это всё очень уж сложно. Давайте посмотрим, на наше игровое поле, и на то, что с ним происходит каждый раз, когда пользователь нажимает на кнопку в любом направлении.
Во первых, направление неважно. Что бы пользователь не нажимал на клавиатуре, мы всегда можем это развернуть и сказать что это просто сжимание 16ти байт слева направо. Но так как ряды у нас не пересекаются, то мы можем сказать, что вся логика-это сжимание четырёх байт слева направо, повторённое четыре раза.
А так как у нас всего лишь четыре байта, то мы можем просто написать логику на граничных кейсах. Какая разница?
Посему считываем направление, проходимся по всем значениям в одной строке и загружаем их в регистры r10 – r14. С этими регистрами и будем работать.
Чтобы облегчить нам жизнь, мы воспользуемся макросами NASM. Пишем два макроса, один для считывания памяти в регистры, другой для переписывания регистров в память. В данном объявлении макроса мы говорим, что у нас будут 4 параметра — 4 адреса в памяти. Их то мы и двигаем в регистры или из регистров. (Все параметры позиционные, % обращается к конкретной позиции)
%macro memtoreg 4
xor r10, r10
mov r10b, byte [stor + %4]
xor r11, r11
mov r11b, byte [stor + %3]
xor r12, r12
mov r12b, byte [stor + %2]
xor r13, r13
mov r13b, byte [stor + %1]
%endmacro
%macro regtomem 4
mov [stor + %4], r10b
mov [stor + %3], r11b
mov [stor + %2], r12b
mov [stor + %1], r13b
%endmacro
Тут всё просто.
После этого, передвижение всего поля в любом направлении будет простой задачей. Вот пример направления down. Мы просто выгружаем байты из памяти в регистры, вызываем процедуру, которая обсчитывает сдвиг и двигаем байты обратно в память.
down:
push rbp
mov rbp, rsp
sub rsp, 32
memtoreg 0x0, 0x4, 0x8, 0xc
call shift
regtomem 0x0, 0x4, 0x8, 0xc
memtoreg 0x1, 0x5, 0x9, 0xd
call shift
regtomem 0x1, 0x5, 0x9, 0xd
memtoreg 0x2, 0x6, 0xa, 0xe
call shift
regtomem 0x2, 0x6, 0xa, 0xe
memtoreg 0x3, 0x7, 0xb, 0xf
call shift
regtomem 0x3, 0x7, 0xb, 0xf
leave
ret
Если посмотреть на другие направления — происходит всё, то же самое, только мы берём байты в другой последовательности, чтобы симулировать «движение» влево, вправо, вниз и вверх.
Процедура самого сдвига находится в этом файле и является самой запутанной процедурой. Более того, точно вам могу сказать, в определённых кейсах она не работает. Надо искать и дебажить. Но, если вы посмотрите на сам код этой процедуры, она просто сравнивает кучу значений и делает кучу переходов. Математики в этой процедуре нет вообще.
inc r11
— это единственная математика, которую вы увидите. Собственно говоря, единственное, что происходит в игре с математической точки зрения, это просто прибавление единицы к текущему значению клетки. Так что нам незачем грузить процессор чем-либо ещё.Запускаем, пробуем — всё хорошо. Цифры прыгают по экрану, прибавляются друг к другу. Нужно дописать небольшой спаунер, который будет забрасывать новые значения на поле. Желания писать собственный рандомизатор прямо сию секунду у меня не было, так что будем просто запихивать значение в первую пустую клетку. А если оной не найдём, то скажем, что игра проиграна.
Складываем всё воедино, собираем, пробуем.
Красота исполнения -5 из десяти возможных. Мы, заразы такие, даже не потрудились конвертировать степени двойки обратно в числа. А могли бы. Если добавить табуляций в вывод, то всё может выглядеть даже поприличнее.
Смотрим в потребление оперативной памяти:
Итого — 2.5 мегабайта. Из них 1900 килобайт это общие ресурсы операционной системы. Почему так жирно? Потому что наш printf и ExitProcess используют очень много других системных вызовов. Если распотрошить программу с помощью x64dbg (кстати, замечательный бесплатный дебаггер, не IDA, но с задачей справляется), то можно увидеть, какие символы импортируются и потребляются.
Сама же программа использует 300 килобайт памяти на всё про всё. Это можно было бы ужать, но статья не об этом.
▍ Итак, что же мы теперь знаем про Ассемблер в 2021 году
- Он всё ещё живой и люди им пользуются. Существует масса инструментов разработки для всех ОС. Вот, например, ассемблер для новых маковских чипов М1. А здесь можно слить более 5000 страниц документации по процессорам Intel. Ну а если у вас завалялась где-то Raspberry Pi (а у кого она не завалялась?), то вам сюда.
- Не всё так просто, как это было в наши стародавние времена, где надо было заучивать таблицу прерываний наизусть. Сегодня мануалов больше и они тяжеловеснее.
- Но и не всё так сложно. Опять же, сегодня мануалы найти проще, да и StackOverflow имеет достаточно данных про ассемблер. Да и на Хабре есть большое количество тёплых ламповых статей про Ассемблер.
- Скрещивать ассемблер и другие языки программирования не так-то сложно. Мы с вами в этом примере импортировали функции, а можем их экспортировать. Достаточно знать правила работы со стеком, чтобы передавать данные туда и обратно.
- Серьёзные системщики, которые могут раздебажить BSOD на лету и распотрошить любую программу с целью её пропатчить, могут читать подобный код без каких-либо проблем. Так что, если вам нужно серьёзно заняться системным программированием, то без ASM вы далеко не двинетесь. (Пусть даже вы не будете писать на асьме напрямую, а будете использовать C-ASM или читать листинги программ)
▍ Для чего вам это надо?
Для того чтобы вы понимали, как работают процессоры. В те старые, тёплые, ламповые времена, когда мне приходилось писать на ASM, я глубоко усвоил основополагающие данные о работе компьютера. После того как вы понимаете, как работать с памятью, что происходит в программе и, как и куда передаются ваши данные, у вас не будет проблем учить любые другие языки программирования. Система управления памяти в С и С++ покажется вам более удобной и приятной, а освоение Rust не займёт много времени.
В этой статье я привёл большое количество ссылок на материалы. Ещё раз обращу ваше внимание на вот эту страницу. Здесь автор собрал в одном файле замечательное руководство по Ассемблеру в Windows.
А вот здесь огромная документация для YASM на русском.
Я бы рекомендовал всем тем, кто только начинает писать программы на языках высокого уровня, взять небольшой пет-проект и написать его на Ассемблере. Так, чисто для себя, чтобы разобраться, на чём вы работаете.
▍ Тёплый, ламповый конкурс на пиво
В дополнение ко всему, вот вам конкурс на пиво. Весь код «работающего» приложения 2048 находится по адресу: github.com/nurked/2048-asm
Вот как выглядит игра на данный момент:
Играем чистыми степенями двойки
Слить скомпилированный бинарник можно по адресу . Играем нажатиями hjkl, выходим по нажатию s.
Всем тем кто принимал участие — спасибо! Особенно tyomitch, который перепиарил весь проект. И Healer за работу с кнопками.
Переписан спаунер, и он на самом деле рандомно выбирает клетку на экране, в которой появляется новая фишка на поле.Переписан отображатель, и он выводит в консоль числа, а не степени двоек, возможно даже с подобием сетки.Добавлены цвета.Найдена и исправлена ошибка, когда мы сжимаем следующую строку: 7 6 6 1, она сожмётся до 8 1 0 0 за один раз, вместо 7 7 1 0, 8 1 0 0По нажатию на s игра должна закрываться, но сейчас она тихо падает, потому что стек обработан неправильно. Это нужно починить.Управление всё-таки нужно сделать стрелочками.
За первый работающий PR по каждому из этих пунктов я лично отправляю создателю денег на пиво пейпалом. Пишите в личку.
Всем успешного учения ассемблера!
Комментарии (100)
klimin007
23.09.2021 17:18+11Лично я сразу вспоминаю ассемблер Z80... Некоторые машинные коды даже сейчас помню... )
Nurked Автор
23.09.2021 20:20С сожалением скажу, что до спектрума я так и не добрался. 8-(
klimin007
24.09.2021 08:54+1Нет, на Спектруме только играли, а на ассемблере писал на КУВТ Yamaha MSX2.
Z80A
24.09.2021 17:14+4На Спектруме демосцена так писала, как КУВТу и не снилось.
Защита с переписыванием LDIR инструкций во время исполнения, xor с регистром R, раскатывание циклов, недокументированные инструкции процессора, синхронизация с развёрткой для мультиколора, жонглирование теневым ПЗУ для прямого доступа к контроллеру FDD, семплирование через музсопроцессор в режиме COVOXa, божественные MONS, GENS и последователи их TASM и STS…
SVNKz
30.09.2021 09:59+2Ассемблер мини компьютера Д3-28 считаю был лучшим в своём классе по удобству и лёгкости освоения - всё множество команд было расписано в таблице на формате А3 и никаких дополнительных документов для работы не требовалось.
Однажды была сделана попытка скомпоновать команды ассемблера из софта AVR Studio аналогичным образом - этот вариант компоновки команд и сегодня был бы востребован...
Причём тип используемого языка не имеет значения - главное это "согласование" особенности зрительного восприятия образов со структурой команд, которые мозг, мой во всяком случае, тоже запоминает в виде набора ассоциативно связанных между собой картинок.
klimin007
30.09.2021 12:10Я только конец эры ДЗ-28 застал… Ни разу не пользовался… Не знал, что там тоже ассемблер был… Почитал документацию, прикольно!
beerware
23.09.2021 18:51+3Вот это ассемблер:
MOV R0,R1
010001 в восьмеричном. По памяти пишу )
Nurked Автор
23.09.2021 19:03А это что за ассемблер?
pae174
23.09.2021 20:59+5PDP-11, ДВК-2, Электроника-60, СМ-4, Электроника БК-0010, и некоторые другие. Это очень популярная тема и удобная система команд.
Nurked Автор
23.09.2021 21:15Ого! Да, я всё-таки пропустил это. Начинал писать в 1997 году, но даже тогда у меня на руках был неимоверно понтовый компьютер. Букашек видел только в школе, но и там нам не давали ничего, кроме черепашки.
Ivanii
24.09.2021 09:03+1В юности писал и редактировал простые програмки для БК прямо в машинных кодах, например записывал сигнал с ИК пульта ДУ телевизора, статическая оперативка работала быстрее и тайминги предсказуемы в отличии от динамической.
Система команд https://ru.wikipedia.org/wiki/PDP-11#Особенности_PDP-11
Sergey_Cheban
26.09.2021 00:15+1О да.
MOV (PC)+, R1
#12345SVNKz
30.09.2021 12:20+1На формате листа А3 было размещено краткое описание 256 команд ассемблера - 16 строк по 16 квадратных табличек с описанием команды. Найти любую команду можно было за кратчайшее время...
piton_nsk
23.09.2021 19:12+2Прикольная штука. Я ассемблером баловался как бы уже не лет десять назад в последний раз и наверное чего забыл. Почему printf, а не через вин апи?
Nurked Автор
23.09.2021 19:29Хотелось консольного, лампового. Хотя WinAPI втянуть так же просто, но хотелось сделать что-то с очень низким потреблением памяти. А даже прогрузка самых базовых библиотек занимает мегабайты, как вы можете видеть.
piton_nsk
23.09.2021 19:45+1Прямо сейчас, конечно, точно утверждать не могу, но ЕМНИП для консоли нужно user32 и/или kernel32. В общем надо пробовать.
Nurked Автор
23.09.2021 19:47Так, я это буду считать подначкой! :-) могу даже попробовать спортировать на фасм.
tyomitch
23.09.2021 23:07+1Вывод в консоль — это WriteFile из kernel32
user32 нужен только для GUImistergrim
24.09.2021 11:32+1Ну вообще говоря docs.microsoft.com/en-us/windows/console/console-functions
Z80A
24.09.2021 17:29Почему бы не сразу рвануть в бутлоадер?
tyomitch
24.09.2021 17:39+2Версия, более актуальная для 21 века: habr.com/ru/company/ruvds/blog/571624
Переписать её на ассемблере несложно, но делать этого я, конечно, не буду.
pvvv
24.09.2021 21:13+2.global main .data hello: .string "Hello world\0" .text main: push $0 push $0 lea hello, %eax push %eax push $0 call MessageBoxA ret
собирается tcc -m32 test.s -luser32 в 1.5кБ exe и хочет 1МБ памяти.
trak
23.09.2021 19:47+5Запахло перфокартами и перфолентами. Всплакнул.
Nurked Автор
23.09.2021 20:17+2Ах, к сожалению, перфокарты мне приходилось использовать как закладки и как бумагу для заметок. В живую я их не делал.
Но если вам так уж всплакнулось, то рекомендую послушать альбом замечательного композитора Йохана Йохансонна (который писал музыку к Прибытию). Альбом называется "IBM 1401 инструкция пользователя"
perfect_genius
23.09.2021 19:58+1линкер должен называться компоновщиком, но мне это непривычно
Также вам непривычно "скачать", да? "Слить" — сейчас это про утечки.
Nurked Автор
23.09.2021 20:19Если честно, то глагол который я использую чаще всего в этом случае это "get". Как "получить" я его перевести не смогу, это будет звучать наравне с "потрачено". Приходится придумывать, и не всегда мои придумывания приводят к правильным ответам.
raamid
23.09.2021 20:24+1Он всё ещё живой и люди им пользуются. Существует масса иснтрументов разработки для всех ОС. Вот, например, ассемблер для новых маковских чипов М1. А здесь можно слить более 5000 страниц документации по процессорам Intel. Ну а если у вас завалялась где-то Raspberry Pi (а у кого она не завалялась?), то вам сюда.
Похоже ссылки не работают.
voidptr0
23.09.2021 21:26+2Когда моя мини-программа которая много читала из файлов на диске, что-то с ними делала и опять записывала на диск (ну вот такая "сложная" программа для нескольких файлов в 10к) заработала, я был готов к ее оптимизации. Но время исполнения в 14 мс на 5600X убило идею в зародыше. Конечно я понимаю, что криворуких кодеров, которые пишут страшный код для чтения JSON в 10 Мб хватает, но для современных процессоров x86... Мне страшно это говорить в слух, но в большинстве случаев оптимизация не нужна, тем более на ассемблере.
А так, да - весело, проностальгировал, вспомнил программирование TSR-программ под DOS.
Nurked Автор
23.09.2021 21:46Согласен. Но я бы вот как сформулировал мыслю:
Современное ПО сильно требует оптимизации. Но не всё, что высокоуровнево - это ад на земле. На самом деле в мире есть приличные высокоуровневые программы. Но, в то же время, существует масса когда, который написан криворуко до ужаса.
Понимание ассемблера дало бы народу понимание того, как не писать сильно неоптимизированный код
titbit
23.09.2021 22:12+1Понимание ассемблера дало бы народу понимание того, как не писать сильно неоптимизированный код
Хочу уточнить, что скорее понимание как и главное почему так устроена архитектура используемой системы дала бы понимание. А ассемблер — это просто инструмент, которым иногда можно и нужно пользоваться.
mSnus
23.09.2021 21:36+2Читал с ностальгией, но с надеждой никогда к ассемблеру не возвращаться. Хотя, может, в современных IDE все проще и надежнее… нет, никогда, никогда больше! хватит того, что в голове зачем-то хранится всякий jmp FFFF:0000 и int 19
vamal
23.09.2021 21:39+6300к для такой игры — это овер-овер-овердофига.
А регистры нынче большие, туда всё поле влезет. Можно вообще без памяти обойтись. :-)Nurked Автор
23.09.2021 21:40Согласен, хотелось бы меньше. Но, к сожалению, тут надо будет выяснять что да как делать. Весь этот оверхед это просто использование стандартной библиотеки винды. Если посмотреть на символы в деббагере, то всё то, что идёт комплектом к ExitProcess - это приличное количество функций.
vamal
23.09.2021 21:46+2Полагаю, для достижения нормального для такой программы размера, следует всё таки разбраться с линкером и дёргать winapi вместо подключения стандартной библиотеки.
Кстати, FASM классный!
CodeRush
23.09.2021 21:43+6%macro memtoreg 4
xor r10, r10
mov r10b, byte [stor + %4]
xor r11, r11
mov r11b, byte [stor + %3]
xor r12, r12
mov r12b, byte [stor + %2]
xor r13, r13
mov r13b, byte [stor + %1]
%endmacro
байтоебилиэкономили память, изучали платформу и регистры, радовались, а потом пошли копировать в регистры и обратно по одному байту, потому что на этой платформе за неправильное выравнивание bus error не прилетит.
Не надо так, если у вас все игровое поле влезает в пару широких машинных регистров — читайте\пишите его туда\оттуда целиком за один выравненный доступ в память, а потом разберите побайтно сдвигами, если вам прямо так неудобно с ними оперировать.
Понятно, что все это буйство оптимизации разобьется все равно об последующее копирование 16 параметров в стек для printf, но без него статья получается странная, потому что непонятно, зачем оно все, если на С можно написать ровно то же самое, только проще в разы.
Надо будет написать статью про современное применение ассемблера там, где он реально нужен — в прошивке до инициализации оперативной памяти или cache-as-ram, в ядре ОС для перехода во всякие хитрые режимы исполнения и выхода из них, и т.п.Nurked Автор
23.09.2021 21:55+1Байто**ля... Писец. New achievement unlocked. Согласен. Лучше слова и не придумаешь. Но тут я уже смотрел с точки зрения оптимизации, сколько это будет стоить в циклах. Я так предположил что память-то закешируется и тянуть её из L1 в регистры будет несложно, в таком случае двигание регистров будет дополнительной нагрузкой, которая себя просто не окупит.
Но, с другой стороны, вы правы. Если мы говорим об уменьшении размера оперативки, то это было бы да - просто считать трёхбитовыми значениями степень, в которую надо возводить. В таком случае всё можно запихнуть в один 64х битный регистр. Но опять-же, у нас на данный момёнт жёсткие потери из-за подгрузки библиотек.
А по поводу статьи - это вы правы. Посмотрим 8-)
titbit
23.09.2021 22:01+3Но тут я уже смотрел с точки зрения оптимизации, сколько это будет стоить в циклах.
Наверное, если сильно заморочиться можно на одних регистрах написать. Вон, сортировку внутри широченного xmm/ymm делают же, чем тут хуже? :)Nurked Автор
23.09.2021 22:172048, v2 извращённая. Убираем поддержку стандартной библиотеки винды, выводим поле опкодами BSOD или как-нить так. Пилим всё аккуратно и сохраняем всё поле в одном регистре. Радуемся футпринту в 2кб.
Кстати... Я так подумал, а ведь если я запущю всё это на FreeDOS у меня будет возможность добираться до современных регистров и, в то же время, использовать int 21h, что жутко сэкономит память.
titbit
23.09.2021 22:43Я так подумал, а ведь если я запущю всё это на FreeDOS у меня будет возможность добираться до современных регистров и, в то же время, использовать int 21h, что жутко сэкономит память.
Формально вам понадобится какой-нибудь 64-битный dos-extender, если не хотите сами писать кучу обвязки для перехода в 64-битный режим и вызова оттуда какого-нибудь int 21h. Можно обойтись и 32-битным расширителем, выбор коих намного больше. Но зачем, если сэкономить память можно и в современных windows/linux.2048, v2 извращённая.
Уж если речь зашла про dos, то можно на голом mmu написать, он на x86 вполне тьюринг-полный. Такие извращения только под dos и можно пускать, ну или прямо из первичного загрузчика.CrashLogger
24.09.2021 11:07+1В DOS можно совершенно спокойно использовать 32-х битные регистры без всяких расширителей. Расширитель нужен для адресации памяти за пределами 1Мб.
CodeRush
23.09.2021 22:39+8Мерять надо, понятно, но мой собственный палец (это которым «в небо») говорит, что арифметические операции, выполняемые целиком на ALU, выполняются нынче за один такт (на современных х86 после Skylake это так даже для 256- и 512-битовых регистрах), а половина примерно мувов из регистра в регистр — за ноль (потому что там внизу регистровый файл, и переименование фактически бесплатное). L1D на этих же процессорах умеет при идеальных условиях читать две линии по 512 бит за такт, или писать одну, но там если самому с выравниванием не заморочиться хоть немного, можно попасть на границу кэш-линии (и просесть вдвое на ровном месте), или физической страницы (и просесть в ~200 раз), ну и плюс мы тут всяко не одни исполняемся, и из кэша наше игровое поле может вымыть в любой момент…
Короче, мерять надо все равно, и написать кучу разных вариантов, но обычно те, кто это все умеет и практикует — они уже на работе сыты этим всем по горло. Студентам зато можно дать в качестве задачи со звездочкой.Nurked Автор
23.09.2021 22:44Снимаю шляпу!
Переписывать это можно бесконечно. Если посмотрите на исходники - всё это писалось быстро и без уж очень большой оптимизации.
titbit
23.09.2021 22:50+1они уже на работе сыты этим всем по горло. Студентам зато можно дать в качестве задачи со звездочкой.
Насчет сыты по горло — это правда. Это же объясняет, почему так мало статей, а тем более не в корпоративных блогах.
А насчет задач со зведочкой, я даю такую: сколько доступов в память в худшем случае может быть сделано при выполнении команды nop, включая ее выборку. И сколько (хотя бы примерно) их вообще может быть максимально для одной команды x86.CodeRush
24.09.2021 01:33+3Хорошая задача, но нуждается в дополнительных уточнениях условий, потому что реально худший случай может быть CXL-устройством, подключенным в адресное пространство процессора, которое при попытке декодировать с него nop пойдет этот самый доступ в память осуществлять, а процессору скажет подождать немного, пока оно не закончит, и ждать он там может до ишачьей пасхи, или пока вочдог всех не прибьет.
Если подразумевать, что ничего такого у нас нет, и задача без особого подвоха, то получится примерно «пошли декодировать, нарвались на начало длинного нопа с префиксами, который попал посередине на границу физических страниц, при этом в TLB ничего нет нужного, и потому надо идти в память декодировать, и еще оказалось, эта вторая страница не исполняемая, ошибка декодирования, кровь, кишки, распидорасило, охулиард записей в память на логи и крэш-дамп».titbit
24.09.2021 10:07+1Уточню: команда лежит в обычной кэшируемой оперативной памяти, это 1-байтовый nop, который ни при предвыборке, ни при исполнении не вызвал никаких исключений. Рассматриваем только результат одной команды, никаких крешей, прерываний, dma/rdma/smm и т.п. нет. Ядро при простоты тоже одно.
Сразу скажу, что число чтений памяти больше одного, число записей (!) тоже больше одного. А есть еще не nop, а более хитрые команды, там все еще хитрее.CodeRush
24.09.2021 14:04+2То, что чтений может быть больше одного — это нетрудно сделать, TLB-промаха достаточно для того, чтобы процессор вынужден был выполнить трансляцию адресов, для которой понадобится одно или несколько обращений к таблице трансляции (в зависимости от того, как именно настроен paging).
С несколькими записями тоже проблем нет — кладем инструкцию последним байтом страницы, заполняем кэши так, чтобы ничего ни с этой, ни со следующей страницы в них не было, выполняем на эту нашу инструкцию nop неожиданный дальний переход — pipeline flush, cache flush, протокол когерентности заставил сделать write-back как минимум двух вымытых из кэшей линий (которые теперь заменили одна с конца этой страницы, ее запросил декодер, а другая — с начала следующей, эту запросил префетчер).titbit
24.09.2021 14:33+2Я не сомневался, что вы ответите на этот несложный вопрос :)
Просто хотел подчеркнуть, что иногда последствия самых вроде безобидных команд могут быть не сразу очевидными. Обычные программисты с такими фокусами вряд ли столкнутся, разве что при очень тщательной оптимизации, а вот системщики — более вероятно. Кроме того, это просто полезно знать, чтобы понимать как устроена не самая простая архитектура x86.
p.s. позволю себе еще немного коварства: а если кэша нет (отключен и по коду и по данным, память не кешируемая и т.д.) можно ли получить несколько записей в память? а на каком-нибудь i386sx, где все еще проще можно?CodeRush
24.09.2021 19:14+2иногда последствия самых вроде безобидных команд могут быть не сразу очевидными
Зачастую, эти последствия бывают неочевидными даже для самих разработчиков процессора, целая плеяда микроархитектурных атак вроде Spectre, Meltdown, Foreshadow, RIDL, Fallout и остальных — отличное тому подтверждение. Знать и понимать — надо, конечно, но надо тоже осознавать пределы своего понимания, а то ведь я до сих пор вижу в индустрии людей, которые в своем ассемблерном коде старательно избегают pipeline hazard'ы, которых их нынешний процессор давно уже не боится, а на Pentium Pro их код запускаться точно уже не будет…
Про ситуацию с «кэша нет» думать уже не хочу, прошу пардону, потому что и лень, и «так верстают только мудаки (тм)». Можно пойти почитать мануал на тему «кто там в память сбрасывает свой architectural state, и при каких условиях», вспомнить про сегментную адресацию и thread local storage, и прочие разные штуки, но я уже в пижаме и мне уже слишком влом.
rogoz
25.09.2021 16:25+2p.s. позволю себе еще немного коварства: а если кэша нет (отключен и по коду и по данным, память не кешируемая и т.д.) можно ли получить несколько записей в память? а на каком-нибудь i386sx, где все еще проще можно?
В таблицах для страничной адресации есть флаг «accessed», на 386 при любой трансляции линейного адреса просматриваются 2 таблицы, в каждой нужно бит установить.
pae174
23.09.2021 23:04+1из кэша наше игровое поле может вымыть в любой момент
На некоторых архитектурах есть cache locking и предзагрузка данных/кода в кэш.
CodeRush
23.09.2021 23:09+2На х86 тоже можно добиться некоторыми трюками с CAT: events19.linuxfoundation.org/wp-content/uploads/2017/11/Introducing-Cache-Pseudo-Locking-to-Reduce-Memory-Access-Latency-Reinette-Chatre-Intel.pdf
titbit
23.09.2021 21:46+2Более того, благодаря SSE, SSSE и AVX у нас на руках ещё есть 15 регистров по 128 или 256 бит. Они названы XMM0-XMM15 для 128 бит и YMM0-YMM15 для 256 бит.
16 же, от 0 до 15. В 32-битном режиме только 8. А еще 8 FPU/MMX тоже есть и в них тоже можно хранить что угодно при желании. Ну а уж если AVX512 вспомнить, то там аж 32 ZMM и еще маски…
p.s. младшие части XMM/YMM/ZMM — это одни и те же регистры, т.е. биты 0-127 совпадают в xmm0 и в ymm0 и в zmm0.
K_Chicago
24.09.2021 09:42-5Давно хотел сказать, ненавижу это вот "погнали" или "поехали".
Здесь запрещен падонковский язык, почему же допускается это гопническое панибратство.
И вот это омерзительное "доставайте пивко"! Почему бы не семки и "есть чо"?
Это может быть попытка автора прикинуться "своим чётким пацаном", но бога ради, здесь ведь иногда читают и культурные и высокообразованые люди, я даже не побоюсь этого слова, люди интеллигентные.
По моему опыту, любое обучение, освоение технического материала с алкогольной интоксикацией несовместимо чуть более чем абсолютно.
Пожалуйста, не надо превращать технический блог в фидопойку.
tyomitch
24.09.2021 12:18+3Пожалуйста, не надо превращать технический блог в фидопойку.
Это вы ещё не заметили, что в коде вместо.bss
автор опечатался и поставил.bbs
:D
dlinyj
24.09.2021 14:02+6«Я понял, в чём ваша беда. Вы слишком серьёзны. Умное лицо — ещё не признак ума, господа. Все глупости на Земле делаются именно с этим выражением. Улыбайтесь, господа, улыбайтесь!»
На самом деле стиль автора — это отсылка ко времени «Хакера», тем кто его не читал, не понять такой формат общения, хотя именно на нём выросли современные специалисты IT в СНГ. И, да, я согласен на счёт алкоголя, но данный стиль — это просто добрые воспоминания.K_Chicago
25.09.2021 05:17-2Приведённая цитата приличествует барону. Кухаркиным детям она не приличествует.
В этом проблема. Все считают себя баронами.
dlinyj
25.09.2021 13:02+1Интересно, к кому причисляете себя вы, с таким высокомерием и пафосом, а главное серьезным лицом? Можете не отвечать, и так всё ясно.
K_Chicago
27.09.2021 00:46-4К потомкам дворян я себя не причисляю.
Но и к хамам, переходящим на личности когда больше нечего сказать, я также себя не причисляю.
А вы?
kuza2000
27.09.2021 11:09+2Ох, и не знал, что я подонок и разговариваю на сленге) И "погнали", и "поехали" я использую в своем разговоре...
Nurked Автор
27.09.2021 18:35+2Ай-яй-яй! Как же это вы, сударь, не используете речевые обороты, приличествующие достопочтенному бомонду. Нельзя же так-с. В мире ваших вычеслительных машин нет приличной ианеры самовыражения.
Мне это всё напоминает:
Щенок! Щенок... не нужен? Дёшево отдам. (Погуглите если не знаете)
DmitrySpb79
24.09.2021 10:31+3Хе-хе, начали с подсчета полубайт, а в итоге все равно получилось 2.5Мб :) Если бы написать на чистом Си, думаю было бы не сильно больше, но зато 100% переносимо, хоть на CP/M запускай. Автору респект, но нет, обратно на ассемблер уже не тянет :)
tyomitch
24.09.2021 12:25+1Это потому что автор линкуется с сишной стандартной библиотекой.
Если переписать на WinAPI, WS сразу снижается до 1.5 МБ
CrashLogger
24.09.2021 11:14+3ИМХО не самое полезное применение ассемблера. Вот если эту штуку написать для Arduino или любого другого микроконтроллера с четырьмя кнопками и экраном - будет нагляднее. Под Windows все равно все упирается в системные вызовы, которые нам не подвластны, а на голом железе все в наших руках.
DX168B
24.09.2021 11:23+2Ассемблер не пугает. Главное, чтобы мануалы по инструкциям были. В свое время я учил ассемблер для AVR микроконтроллеров. Потом перешёл на Си и С++. И вот однажды мне пришлось на STM32F407 организовать цепочку цифровых обработок одного сигнала и выжать максимум производительности с ядра ARM Cortex-M4. А тут только ассемблер и нужен. Мануал по ассемблеру вышеупомянутого ядра оказался довольно увлекательным. Там очень много интересных инструкций на все случаи жизни. Ну и задачу я свою решил. Не стал писать вставки и ограничился вызовами intrinsic функций библиотеки CMSIS.
Calc
24.09.2021 14:13+2cygwin еще не советовали для ленивых?
Там есть всё, что требуется для таких развлечений. И yasm и yasm и MinGW и линкеры
Mike-M
24.09.2021 22:42+2Шекспира лучше читать в подлиннике.
Так и с процессором лучше разговаривать на его родном языке — языке Ассемблера, без дополнительного «переводчика» в виде Си компилятора.
Был и остаюсь фанатом FASM: х86, AVR, ARM.K_Chicago
27.09.2021 01:07-1Скажите, а вы пробовали читать Шекспира в подлиннике?
Например, сонет 116:
Let me not to the marriage of true mindes
Admit impediments,loue is not loue
Which alters when it alteration findes,
Or bends with the remouer to remoue.
O no,it is an euer fixed marke
That lookes on tempeſts and is neuer ſhaken;
It is the ſtar to euery wandring barke,
Whoſe worths vnknowne,although his higth be taken.
Lou's not Times foole,though roſie lips and cheeks
Within his bending ſickles compaſſe come,
Loue alters not with his breefe houres and weekes,
But beares it out euen to the edge of doome:
If this be error and vpon me proued,
I neuer writ,nor no man euer loued.
Mike-M
27.09.2021 12:47Сонеты Шекспира меня не заинтересовали. А вот детективы Сидни Шелдона — да, прочел штук пять в подлиннике.
Минус Вашему комментарию я не ставил. Но тоже считаю его неуместным, т.к. в той крылатой фразе заложен смысл не про Шекспира, а про native language.K_Chicago
28.09.2021 08:20-2Вы изволили с апломбом заявить "Шекспира лучше читать в подлиннике", я привел пример совершенно нечитаемого подлинника Шекспира, был заминусован ненавистниками...видимо, Шекспира в подлиннике? Или теми, кому "умники" не нравятся, т.е. обычному б....у. Минусующие в этом слове себя узнают.
Может быть вам следует следить за своими изъяснениями и отвечать за то что вы изволили нам всем подарить публично? Я понимаю, фраза "отвечать за базар" в рунете крайне непопулярна, конечно. В этом и прелесть рунета.
Я примерно понимаю, что вы хотели сказать "Шекспира лучше читать в подлиннике", это просто желание подтвердить "умным" высказыванием ваше крайне сомнительное утверждение "с процессором лучше разговаривать на его родном языке". Не буду даже впадать в обсуждение этой, IMHO, бредовой фразы, вы безусловно имеете на нее право.
Просто не привлекайте для обоснования своей личной позиции бессмысленные шексиро-related высказывания, вы тем самым не только демонстрируете свое незнание "Шекспира в подлиннике", но и компрометируете свои высказывания, которые вы пытались приукрасить этим простихоспади Шекспиром.
morgot
28.09.2021 01:56+2Есть masm64 от того же hutch (автор замечательного пакета masm32). Да,ехе нужно брать со студии,но тут есть макросы,инклуды , примеры. Также советую посмотреть uasm - masm синтаксис но гораздо больше возможностей ( создание файлов пол разные ос,даже подобие ооп)
dlinyj
Да, ассемблер, сколько в этом звуке, для сердца нашего слилось. х86 асм я как-то миновал (ну то есть там пописывал, но серьёзно к этому относиться нельзя). Но вот основательно пописал для с51 и AVR и чутка для arm. Даже сегодня, когда расчехляешь arduino, в голове считаешь такты.
Прекрасный язык, с одним чудовищным недостатком: непереносим.
Nurked Автор
Ну конечно. Хотя, не знаю что сказать, но недостатком ассемблера я это назвать не могу. Это как говорить о недостатках кентавров — нет рогов, и летать не умеет. Как бы и правда, но всё же не того типа лошадь. 8-)
С другой стороны — намного проще работа с памятью. Вот нигде, где была работа с памятью, не было проще, чем в асьме. Возможно потому, что за всем надо было следить вручную, и у тебя даже нет идеи переменных. В итоге - у тебя есть что надо, и память не течёт направо и налево.
dlinyj
Ассемблер безупречный язык, при том что на первый взгял он кажется сложным, но на деле проще всех других языков, потому что ты мыслишь уже на уровне «железа». В общем, громадное вам спасибо.
На счёт недостатка, не хотел яду добавить, я сам люблю ассемблер, но отошёл от него, потому что программы которые я писал на нём, можно использовать только на целевой архитектуре, а сишный код я часто переносил туда-сюда, и это решающий фактор.
zuek
Это Вы напрямую с DOS'овскими MCB не пытались работать, а мои поделки (светодиодами в LPT-порту помигать, попищать писи-скрипером по таймеру, загрузить в матричный принтер русские шрифты, если на LPT-порту статусы похожи на переинициализацию принтера) были более изобретательными при работе с оперативной памятью и файловыми буферами, и чтобы у TSR-сторожа, следящего за принтером, не "текла память" (я тогда даже термина такого не знал, но с явлением столкнулся), пришлось довольно много отладочной информации собирать.
Но да, структура кода была более понятная (минимум абстракций), и отлавливать свои ошибки было сильно проще, чем ловить неизвестные ошибки в чужих библиотеках.
KvanTTT
Если вам нужен переносимый ассемблер, возьмите MSIL или JVM байткод.
CrashLogger
Только смысла в этом нет. На ассемблере пишут ради скорости и компактности, а когда у тебя запускается виртуальная машина на сотни мегабайт чтобы исполнить твое маленькое приложение - это сводит все старания на нет.
PsyHaSTe
Есть ещё LLVM который никуда не запускается. То что многие существующие байткоды принято житовать не означает, что это единственный возможный вариант.
dlinyj
Да мне си пока хватает
alliumnsk
На ассемблере по сравнению с Си некоторые вещи можно сделать радикально по-другому, и иногда в разы лучше. Предоставляет ли какую-нибудь потенциальную выгоду использование JVM вместо Java? Кроме более громоздкого синтаксиса.
tyomitch
В прошлом году здесь постили хороший пример: компилятор Java позволяет использовать до 2746 значений в enum, тогда как при создании .class-файла вручную этот предел расширяется в 24 раза.
alliumnsk
Спасибо, не знал. Но полезность этого примера довольно ограничена, если все примеры которые удастся найти, такого рода -- это подтверждает мой тезис
tyomitch
Я думаю, что и все примеры на Си такого рода: всё более полезное добавляют в язык как минимум интринсиками.
alliumnsk
На ассемблере можно сделать процедуру с несколькими входами, нетривиальные операции со стеком, прыжки вместо setjmp/longjump.
Как на Си с помощью инстринсиков сделать продедуру, которая сигнализирует об ошибке в флаге переноса? Инстринсики для add with carry есть но они покрывают только ограниченные случаи использования.
PsyHaSTe
Один из вариантов MSIL — возможность написать функцию по ансейф трансмутации из одного типа в другой. Что-то типа
reinterpret cast
— сама функция же просто делаетret
собственного единственного аргумента. Бывает полезно при обработке низкоуровневых данных, когда нужно представить данные в немного другом виде (например преобразованиеbyte[]
->long[]
. В последних версиях C# оно появилось в стд в видеUnsafe.As<T>
но во-первых раньше его не было, во-вторых если таргетится не последний неткор то этой функции в стандартной поставке нет и нужно продолжать использовать свою.SVNKz
В MathCad 14, например, встроен прекрасный ассемблер, на котором удобно отлаживать программные вложенные циклы. Если битовые значения брать из массива BMP - картинки, то результат работы высвечивается подробно и красиво.
Nurked Автор
Ну, это всё же, другой ассемблер. Он хоть и ассемблер, но для виртуального процессора.
Это как тот пацан, который написал эмулятор процессора на экселе. Процессор Шрёдингера. Он одновременно и низкоуровневый и нет.
SVNKz
Это самый настоящий ассемблер для отладки программ фильтрации или обработки видеокадра, представленного в виде BMP - файла, - ничего другого для себя пока представить не могу.