Что можно написать на ассемблере

Почему Ассемблер — это круто, но сложно

Потому что это круто. Но сложно.

Есть высокоуровневые языки — это те, где вы говорите if — else, print, echo, function и так далее. «Высокий уровень» означает, что вы говорите с компьютером более-менее человеческим языком. Другой человек может не понять, что именно у вас написано в коде, но он хотя бы сможет прочитать слова.

Но сам компьютер не понимает человеческий язык. Компьютер — это регистры памяти, простые логические операции, единицы и нули. Поэтому прежде чем ваша программа будет исполнена процессором, ей нужен переводчик — программа, которая превратит высокоуровневый язык программирования в низкоуровневый машинный код.

Ассемблер — это собирательное название языков низкого уровня: код всё ещё пишет человек, но он уже гораздо ближе к принципам работы компьютера, чем к принципам мышления человека.

Вариантов Ассемблера довольно много. Но так как все они работают по одинаковому принципу и используют (в основном) одинаковый синтаксис, мы будем все подобные языки называть общим словом «Ассемблер».

Как мыслит процессор

Чтобы понять, как работает Ассемблер и почему он работает именно так, нам нужно немного разобраться с внутренним устройством процессора.

Кроме того, что процессор умеет выполнять математические операции, ему нужно где-то хранить промежуточные данные и служебную информацию. Для этого в самом процессоре есть специальные ячейки памяти — их называют регистрами.

Регистры бывают разного вида и назначения: одни служат, чтобы хранить информацию; другие сообщают о состоянии процессора; третьи используются как навигаторы, чтобы процессор знал, куда идти дальше, и так далее. Подробнее — в расхлопе ↓

Общего назначения. Это 8 регистров, каждый из которых может хранить всего 4 байта информации. Такой регистр можно разделить на 2 или 4 части и работать с ними как с отдельными ячейками.

Указатель команд. В этом регистре хранится только адрес следующей команды, которую должен выполнить процессор. Вручную его изменить нельзя, но можно на него повлиять различными командами переходов и процедур.

Регистр флагов. Флаг — какое-то свойство процессора. Например, если установлен флаг переполнения, значит процессор получил в итоге такое число, которое не помещается в нужную ячейку памяти. Он туда кладёт то, что помещается, и ставит в этот флаг цифру 1. Она — сигнал программисту, что что-то пошло не так.

Флагов в процессоре много, какие-то можно менять вручную, и они будут влиять на вычисления, а какие-то можно просто смотреть и делать выводы. Флаги — как сигнальные лампы на панели приборов в самолёте. Они что-то означают, но только самолёт и пилот знают, что именно.

Сегментные регистры. Нужны были для того, чтобы работать с оперативной памятью и получать доступ к любой ячейке. Сейчас такие регистры имеют по 32 бита, и этого достаточно, чтобы получить 4 гигабайта оперативки. Для программы на Ассемблере этого обычно хватает.

Так вот: всё, с чем работает Ассемблер, — это команды процессора, переменные и регистры.

Здесь нет привычных типов данных — у нас есть только байты памяти, в которых можно хранить что угодно. Даже если вы поместите в ячейку какой-то символ, а потом захотите работать с ним как с числом — у вас получится. А вместо привычных циклов можно просто прыгнуть в нужное место кода.

Команды Ассемблера

Каждая команда Ассемблера — это команда для процессора. Не операционной системе, не файловой системе, а именно процессору — то есть в самый низкий уровень, до которого может дотянуться программист.

Любая команда на этом языке выглядит так:

Метка — это имя для фрагмента кода. Например, вы хотите отдельно пометить место, где начинается работа с жёстким диском, чтобы было легче читать код. Ещё метка нужна, чтобы в другом участке программы можно было написать её имя и сразу перепрыгнуть к нужному куску кода.

Команда — служебное слово для процессора, которое он должен выполнить. Специальные компиляторы переводят такие команды в машинный код. Это сделано для того, чтобы не запоминать сами машинные команды, а использовать вместо них какие-то буквенные обозначения, которые проще запомнить. В этом, собственно, и выражается человечность Ассемблера: команды в нём хотя бы отдалённо напоминают человеческие слова.

Операнды отвечают за то, что именно будут делать команды: какие ячейки брать для вычислений, куда помещать результат и что сделать с ним дополнительно. Операндом могут быть названия регистров, ячейки памяти или служебные части команд.

Комментарий — это просто пояснение к коду. Его можно писать на любом языке, и на выполнение программы он не влияет. Примеры команд:

mov eax, ebx ; Пересылаем значение регистра EBX в регистр EAX mov x, 0 ; Записываем в переменную x значение 0 add eax, х ; Складываем значение регистра ЕАХ и переменной х, результат отправится в регистр ЕАХ

Здесь нет меток, первыми идут команды (mov или add), а за ними — операнды и комментарии.

Пример: возвести число в куб

Если нам понадобится вычислить х³, где х занимает ровно один байт, то на Ассемблере это будет выглядеть так.

Первый вариант

mov al, x ; Пересылаем x в регистр AL imul al ; Умножаем регистр AL на себя, AX = x * x movsx bx, x ; Пересылаем x в регистр BX со знаковым расширением imul bx ; Умножаем AX на BX. Результат разместится в DX:AX

Второй вариант

mov al, x ; Пересылаем x в регистр AL imul al ; Умножаем регистр AL на себя, AX = x * x cwde ; Расширяем AX до EAX movsx ebx, x ; Пересылаем x в регистр EBX со знаковым расширением imul ebx ; Умножаем EAX на EBX. Поскольку x – 1-байтовая переменная, результат благополучно помещается в EAX

На любом высокоуровневом языке возвести число в куб можно одной строкой. Например:

на худой конец x = x*x*x.

Хитрость в том, что когда каждая из этих строк будет сведена к машинному коду, этого кода может быть и 5 команд, и 10, и 50, и даже 100. Чего стоит вызов объекта Math и его метода pow: только на эту служебную операцию (ещё до самого возведения в куб) может уйти несколько сотен и даже тысяч машинных команд.

А на Ассемблере это гарантированно пять команд. Ну, или как реализуете.

Почему это круто

Ассемблер позволяет работать с процессором и памятью напрямую — и делать это очень быстро. Дело в том, что в Ассемблере почти не тратится зря процессорное время. Если процессор работает на частоте 3 гигагерца — а это примерно 3 миллиарда процессорных команд в секунду, — то очень хороший код на Ассемблере будет выполнять примерно 2,5 миллиарда команд в секунду. Для сравнения, JavaScript или Python выполнят в тысячу раз меньше команд за то же время.

Ещё программы на Ассемблере занимают очень мало места в памяти. Именно поэтому на этом языке пишут драйверы, которые встраивают прямо в устройства, или управляющие программы, которые занимают несколько килобайт. Например, программа, которая находится в брелоке сигнализации и управляет безопасностью всей машины, занимает всего пару десятков килобайт. А всё потому, что она написана для конкретного процессора и использует его возможности на сто процентов.

Справедливости ради отметим, что современные компиляторы С++ дают машинный код, близкий по быстродействию к Ассемблеру, но всё равно немного уступают ему.

Почему это сложно

Для того, чтобы писать программы на Ассемблере, нужно очень любить кремний:

Теперь добавьте к этому отсутствие большинства привычных библиотек для работы с чем угодно, сложность чтения текста программы, медленную скорость разработки — и вы получите полное представление о программировании на Ассемблере.

Для чего всё это

Ассемблер незаменим в таких вещах:

На самом деле на Ассемблере можно даже запилить свой сайт с форумом, если у программиста хватает квалификации. Но чаще всего Ассемблер используют там, где даже скорости и возможностей C++ недостаточно.

Источник

За что я люблю ассемблер?

Этой статье уже почти 3 года. Однако сегодня я решил подредактировать её, дополнить и выложить, наконец, на Хабр.

Что можно написать на ассемблере. Смотреть фото Что можно написать на ассемблере. Смотреть картинку Что можно написать на ассемблере. Картинка про Что можно написать на ассемблере. Фото Что можно написать на ассемблере

Оговорочки

Хочу сразу оговориться, что правильно говорить не «ассемблер» (assembler), а «язык ассемблера» (assembly language), потому как ассемблер – это транслятор кода на языке ассемблера (т.е. по сути, программа MASM, TASM, fasm, NASM, UASM, GAS и пр., которая компилирует исходный текст на языке ассемблера в объектный или исполняемый файл). Тем не менее, из соображения краткости многие, говоря «ассемблер» (асм, asm), подразумевают именно «язык ассемблера».

Синтаксис директив, стандартных макросов и пр. структурных элементов различных диалектов (к примеру, MASM, fasm, NASM, GAS), могут отличаться довольно существенно. Мнемоники (имена) инструкций (команд) и регистров, а также синтаксис их написания для одного и того же процессора примерно одинаковы почти во всех диалектах (заметным исключением среди популярных ассемблеров является разве что GAS (GNU Assembler) в режиме синтаксиса AT&T для x86, где к именам инструкций могут добавляться суффиксы, обозначающие размер обрабатываемых ими данных, что бывает довольно удобно, но там есть и другие нюансы, сбивающие с толку программиста, привыкшего к классическому ассемблеру, к примеру, иной порядок указания операндов, хотя всё это лечится специальной директивой переключения в режим классического синтаксиса Intel).

Поскольку ассемблер – самый низкоуровневый язык программирования, довольно проблематично написать код, который корректно компилировался бы для разных архитектур процессоров (например, x86 и ARM), для разных режимов одного и того же процессора (16-битный реальный режим, 32-битный защищённый режим, 64-битный long mode; а ещё код может быть написан как с использованием различных технологий вроде SSE, AVX, FMA, BMI и AES-NI, так и без них) и для разных операционных систем (Windows, Linux, MS-DOS). Хоть иногда и можно встретить «универсальный» код (например, отдельные библиотеки), скажем, для 32- и 64-битного кода ОС Windows (или даже для Windows и Linux), но это бывает нечасто. Ведь каждая строка кода на ассемблере (не считая управляющих директив, макросов и тому подобного) – это отдельная инструкция, которая пишется для конкретного процессора и ОС, и сделать кроссплатформенный вариант можно только с помощью макросов и условных директив препроцессора, получая в итоге порой весьма нетривиальные конструкции, сложные для понимания.

Откуда растут ноги?

Ассемблером я увлёкся лет в 12–13, и он меня изрядно «затянул». Почему?

Во-первых, экономия памяти (дисковой и оперативной) и погоня за скоростью в те DOS-овские времена далёких 90-х годов были куда более актуальными темами, чем сейчас.

Во-вторых (и это более существенно), на ассемблере можно было делать много того, что сделать на языках высокого уровня (ЯВУ, не путайте с Java) нельзя, затруднительно или не так эффективно. К примеру, мне очень нравилось писать резидентные программы.

Но с тех пор прошло уже более 2-х десятков лет, и сейчас экономия памяти (особенно дисковой) в подавляющем большинстве случаев уже не так актуальна, да и скорости современных процессоров для выполнения повседневных задач вполне хватает (популярность языков сверхвысокого уровня подтверждает это, хотя закон Вирта никто не отменял). А современные компиляторы зачастую могут оптимизировать код по скорости даже лучше человека. Что же может привлекать программиста в ассемблере, ведь исходники на нём гораздо более объёмные и сложные, а на разработку требуется больше времени и внимания (в т.ч. на отладку)?

Вон оно что!

Приведу свои доводы относительно того, чем так хорош ассемблер.

Ассемблер даёт полный контроль над кодом и обладает большей гибкостью, чем любой другой язык программирования (даже C/C++). На асме мы можем конструировать нашу программу, размещая блоки кода и данных как нам вздумается. Каждый генерируемый байт будет таким, каким мы хотим его видеть. Без лишнего runtime-кода стандартных библиотек. Правда, справедливости ради отмечу, что необходимость в этом может возникнуть лишь в весьма специфических случаях. Однако существуют аппаратные платформы с ограниченными ресурсами, где оптимизация кода важна и актуальна и в наши дни.

На ассемблере можно написать ВСЁ, он всемогущ! Вряд ли у вас получится создать MBR-загрузчик полностью на C или на чём-то ещё. Для работы с железом на низком уровне, программирования чипсетов зачастую может потребоваться ассемблер. Для внедрения кода в другие процессы (injection, не только с вредоносными целями), создания различных антиотладочных приёмов тоже необходим ассемблер. Или, скажем, для проделывания чего-то вроде этого. Для C/C++ имеются интринсики – функции для генерации отдельных инструкций процессора (есть ли что-то подобное для других языков программирования – не знаю, не встречал). Но их частое использование загромождает код (не проще ли тогда писать на чистом ассемблере?) А их отсутствие не позволяет нам контролировать генерируемый компилятором код (при этом, к слову говоря, Visual C/C++, GNU C/C++ и Clang будут генерировать разный код; и даже один и тот же компилятор с разными настройками выдаст различный результат).

Обычно одна строка кода на ЯВУ разворачивается в несколько (или даже десяток) инструкций процессора. А знаете ли вы о том, что некоторые инструкции процессора Intel требуют несколько строк для реализации на ЯВУ (на том же C/C++, если не использовать интринсики)? Если не знаете, просто поверьте на слово, а я, возможно, напишу об этом в одной из следующих статей. Приведу лишь один простой пример: аналоги инструкций rol, ror (существующих ещё в самых ранних процессорах i8086 с конца 70-х годов) появились только в стандарте C++20 в библиотеке bit (как функции std::rotl, std::rotr), а в большинстве других языков они вообще отсутствуют.

Есть такое направление компьютерного искусства: демосцена. Написать intro, уместив исполняемый файл в 256 байт [1, 2, 3, 4] (а то и 128, 64, 32 или даже ещё меньше) на чём-то отличном от ассемблера (ну или по крайней мере, без использования ассемблера для финальной корректировки кода) вы вряд ли сможете.

Ещё одна интересная область применения ассемблера – создание файлов данных с помощью макросов и директив генерации данных. К примеру, fasm позволяет создавать виртуальные данные и генерировать отдельные файлы (директива virtual), а также читать и изменять ранее сгенерированный код (директивы load, store). Есть даже примеры AES-шифрования файлов.

Без ассемблера не обойтись при исследовании (reverse engineering), а зачастую и при отладке программ.

В ассемблере есть особая магия и притягательность! Но справедливости ради скажу, что писать всегда на ассемблере – занятие не очень разумное с точки зрения времени, усилий, вероятности допустить ошибку и кроссплатформенности (я сам реже пишу на ассемблере, нежели на других языках). Не так часто нам требуется полный контроль над кодом и столь уж жёсткая оптимизация, когда экономия пары тактов процессора имеет критически решающее значение.

На том же C/C++ можно написать практически всё, что можно написать и на ассемблере, причём сразу под десяток платформ и ОС, включая и выключая отдельными опциями компилятора использование различных наборов инструкций, векторизацию, оптимизацию и пр.

Но иногда использование ассемблера действительно оправдано (пример). Часто ассемблер хорошо использовать в виде вставок в код на ЯВУ (посмотрите RTL-модули Delphi, там этого добра в изобилии). Да и использование интринсиков, как правило, не имеет смысла (или даже опасно) без знания ассемблера.

Подытожим…

Итак, приведу неполный перечень того, в каких случаях используется ассемблер.

Создание загрузчиков, прошивок устройств (комплектующих ПК, встраиваемых систем), элементов ядра ОС.

Низкоуровневая работа с железом, в т.ч. с процессором, памятью.

Внедрение кода в процессы (injection), как с вредоносной целью, так и с целью защиты или добавления функционала. Системный софт.

Блоки распаковки, защиты кода и прочего функционала (с целью изменения поведения программы, добавления новых функций, взлома лицензий), встраиваемые в исполняемые файлы (см. UPX, ASProtect и пр).

Оптимизация кода по скорости, в т.ч. векторизация (SSE, AVX, FMA), математические вычисления, обработка мультимедиа, копирование памяти.

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

Вставки в языки высокого уровня, которые не позволяют выполнять необходимую задачу, либо позволяют делать это неоптимальным образом.

При создании компиляторов и трансляторов исходного кода с какого-либо языка на язык ассемблера (например, многие компиляторы C/C++ позволяют выполнять такую трансляцию). При создании отладчиков, дизассемблеров.

Собственно, отладка, дизассемблирование, исследование программ (reverse engineering).

Создание файлов данных с помощью макросов и директив генерации данных.

Вы не поверите, но ассемблер можно использовать и для написания обычного прикладного ПО (консольного или с графическим интерфейсом – GUI), игр, драйверов и библиотек 🙂

Быть или не быть?

Так, нужно ли изучать ассемблер современному программисту? Если вы уже не новичок в программировании, и у вас серьёзные амбиции, то изучение ассемблера, внутреннего устройства операционных систем и функционирования железа (особенно процессоров, памяти), а также использование различных инструментов для дизассемблирования, отладки и анализа кода полезно тем, кто хочет писать действительно эффективные программы. Иначе будет сложно в полной мере понять, что происходит «под капотом» любимого компилятора (хотя бы в общих чертах), как оптимизировать программы на любом языке программирования и какой приём стоит предпочесть. Необязательно погружаться слишком глубоко в эту тему, если вы пишете на Python или JavaScript. А вот если ваш язык – C или C++, хорошенько изучить ассемблер будет полезно.

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

Это будет полезно

Что можно написать на ассемблере. Смотреть фото Что можно написать на ассемблере. Смотреть картинку Что можно написать на ассемблере. Картинка про Что можно написать на ассемблере. Фото Что можно написать на ассемблере

Если вы решили изучить ассемблер и окунуться в низкоуровневое программирование, вам будет полезна следующая литература:

Зубков С.В. Assembler для DOS, Windows и Unix. – ДМК Пресс, 2017. – 638 c., ISBN 978–5–97060–535–6.

Руслан Аблязов. Программирование на ассемблере на платформе x86–64. – ДМК Пресс, 2016. – 302 с., ISBN 978–5–97060–364–2.

Статьи старого WASM’а – кладезь обучающего материала на самые разные низкоуровневые темы (крайне рекомендую!)
Новый WASM (форум по низкоуровневому программированию и сборник статей).

Книги и статьи Криса Касперски (много).

Официальная документация AMD (множество документов) [всё на английском, PDF].

Архитектура и система команд микропроцессоров x86 (староватая документация на русском языке; из описания расширений есть только x87, MMX, 3DNow! и SSE(1)).

Марк Руссинович, Дэвид Соломон, Алекс Ионеску. Внутреннее устройство Microsoft Windows. – 6-е изд., часть 1. – Питер, 2013. – 800 с., ISBN 978–5–496–00434–3, 978–5–459–01730–4 (англ.: 978–0735648739).
Вышло 7-е издание этой части с Павлом Йосифовичем в качестве ещё одного соавтора – Питер, 2018 – 944 с., ISBN 978–5–4461–0663–9 (англ.: 978–3864905384).

Марк Руссинович, Дэвид Соломон, Алекс Ионеску. Внутреннее устройство Microsoft Windows. Основные подсистемы ОС. – 6-е изд., часть 2. – Питер, 2014. – 672 с., ISBN 978–5–496–00791–7 (англ.: 978–0735665873).
7-е издание этой части есть пока только на английском языке (ISBN 978–0135462409).

Джеффри Рихтер. Windows для профессионалов. Создание эффективных Win32-приложений с учётом специфики 64-разрядной версии Windows. – 4-е изд. – Питер, Русская редакция, 2001. – 752 с. (есть вариант книги 2008 г. на 720 с., но она тоже 4-го издания, с переводом 2000 года… в чём разница?), ISBN 5–272–00384–5, 978–5–7502–0360–4 (англ.: 1–57231–996–8).

Джеффри Рихтер, Кристоф Назар. Windows via C&C++. Программирование на языке Visual C++ – 5-е изд. – Питер, 2009 – 896 с., ISBN 978–5–388–00205–1, 978–5–7502–0367–3, 978–0–7356–2424–5 (англ.: 978–0735624245).

Павел Йосифович. Работа с ядром Windows. – Питер, 2021 – 400 c., ISBN 978–5–4461–1680–5 (англ.: 978-1977593375).

Pavel Yosifovich. Windows 10 System Programming, Part 1 – 2020, ISBN 979-8634170381 [англ].

Михаил Гук. Аппаратные средства IBM PC. Энциклопедия. – 3-е изд. – Питер, 2008. – 1072 с., ISBN 978–5–46901–182–8 (2001 г. – 816 с., ISBN 5–88782–290–2).

Владимир Кулаков. Программирование на аппаратном уровне. Специальный справочник (+ дискета). – 2-е изд. – Питер, 2003. – 848 с., ISBN 5–94723–487–4.

Всеволод Несвижский. Программирование аппаратных средств в Windows (+ CD-ROM). – 2-е изд. – БХВ-Петербург, 2008. – 528 с., ISBN 978–5–9775–0263–4.

Компиляторы и инструменты:

MASM32 (Macro Assembler) – наверное, самый популярный пакет самого популярного ассемблера.
MASM64 includes and libs – заголовки и библиотеки для 64-битной версии MASM (информация); файлы ml64.exe, link.exe и прочие потроха можно взять из Visual Studio (путь к папке с нужными файлами примерно такой: C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\VC\Tools\MSVC\14.12.25827\bin\Hostx64\x64\).

fasm (flat assembler) – современный и удобный компилятор под DOS, Wndows, Linux с очень развитой системой макросов и полным набором инструкций Intel/AMD. Рекомендую в качестве основного!
Там же можно скачать и fasmg (flat assembler g) – универсальный ассемблер под любую платформу (имеются include-модули для создания кода под AVR, i8051, x86/x64, генерации байт-кода JVM, аналогично можно создать свои модули).

NASM (Netwide Assembler) – ещё один современный кроссплатформенный компилятор с хорошей макросистемой и полным набором инструкций Intel/AMD, популярен в зарубежных проектах и при программировании под Linux/BSD.
NASMX – пакет макросов, include’ов, примеров и утилит для NASM под Windows, Linux, BSD, Xbox; включает макрос invoke, символы для работы с OpenGL и пр.

UASM (он же HJWasm) – современный MASM-совместимый мультиплатформенный ассемблер с полным набором инструкций Intel/AMD.

TASM 5.x (Turbo Assembler) – старый, но всё ещё популярный ассемблер, в основном используется для создания программ под DOS.

ALINK, GoLink – компоновщики для программ под DOS и Windows.

objconv – преобразователь форматов объектных файлов (COFF/OMF/ELF/Mach-O).

ResEd – бесплатный редактор ресурсов.

GoRC – компилятор ресурсов (rc → res) [в вышеупомянутом NASMX есть и GoLink, и objconv, и GoRC].

Windows 10 Software Development Kit (SDK) – заголовочные файлы, библиотеки, инструменты (в том числе отладчик WinDbg) для разработчиков Windows.

Fresh IDE – визуальная среда разработки для fasm.

SASM – простая кроссплатформенная среда разработки для NASM, MASM, GAS, fasm с подсветкой синтаксиса и отладчиком (для NASM имеется набор макросов для упрощения работы с консолью).

OllyDbg – популярный 32-битный отладчик (готовится 64-битная версия, но пока ещё не вышла).

x64dbg – хороший отладчик для 32- и 64-битного кода.

IDA Pro – мощный интерактивный дизассемблер (shareware).

VMware Workstation Player – мощный виртуализатор, позволяющий создавать и запускать виртуальные машины (бесплатный для персонального использования).

Oracle VirtualBox – альтернативный бесплатный виртуализатор.

Bochs – эмулятор компьютера IBM PC.

QEMU – эмулятор аппаратного обеспечения различных платформ (QEMU Manager).

Intel Software Development Emulator (SDE) – эмулятор расширений (инструкций) процессоров Intel.

DOSBox – очень популярный эмулятор компьютера для запуска программ под DOS (имеет встроенный замедлитель скорости).

Hiew – редактор двоичных файлов со встроенным дизассемблером, просмотром и редактированием заголовков исполняемых файлов (shareware).

PE Explorer – редактор секций, ресурсов PE, дизассемблер (shareware).

Windows Sysinternals – набор системных утилит для Windows (работа с процессами, мониторы и прочее).

ReactOS – бесплатная Windows-совместимая операционная система с открытым исходным кодом.

KolibriOS – миниатюрная ОС, умещающаяся на дискету 1.44 Mb, с исходниками на fasm.

Все эти ссылки (а также множество других, которые не вошли в эту статью) вы можете найти, кликнув сюда.

Также хочу пригласить вас в наш уютный «ламповый» раздел Assembler Форума на Исходниках.Ру 😉

Кто интересуется демосценой и сайзкодингом, welcome here.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *