Избранное

Позднее Ctrl + ↑

Ассемблер под Линукс

Заметка про полноту по Тьюрингу команды присваивания в ассемблере натолкнула меня на мысль, что я как-то не удосужился попрограммировать на ассемблере под Линукс — эту ОС я начал осваивать примерно в то же время, когда начал быстро терять интерес к ассемблеру.

Тогда не довелось, решил вчера попробовать. Теорию в очень общих чертах я знал — есть системные вызовы (сисколы), которые можно дёргать какой-то командой, имена сисколов мне знакомы через Си. Попробовал написать программу, которая выводит на вход то, что ей дают на вход. Быстро разобрался, что регистры сейчас имеют впереди букву «эр», а сисколы делаются прерыванием №128. Удобный способ, параметры идут в логичном виде — последовательно через регистры в почти алфавитном порядке (ближе к концу последовательность нарушается):

MOV RAX, 3 ; sys_read
MOV RBX, 0 ; stdin
MOV RCX, string ; адрес строки, которую выведем на экран
MOV RDX, len ; длина строки
INT 0x80

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

MOV RAX, 0 ; sys_read
MOV RDI, 0 ; stdin
MOV RSI, string ; адрес строки, которую выведем на экран
MOV RDX, len ; длина строки
SYSCALL

К тому времени меня посетила мысль сделать простенький аналог линуксовой утилитки tee, но для этого нужно было как-то прочитать параметры командной строки. Гугление показало, что после запуска программы на стеке лежат количество параметров и указатели на каждый из них, причём каждый параметр кончается нулём — обычная сишная строка.

Так как системный вызов для открытия файла (sys_open) как раз требует на вход сишную строку, я передал указатель, который взял со стека, в сискол, ожидая, что всё заработает. Это было моей ошибкой. Я потратил не меньше часа, разбираясь, что я сделал не так, перебирал способы передачи параметров, ползал по стеку при помощи отладчика gdb, ничего не помогало, пока не забрезжила догадка, что сами параметры командной строки тоже лежат на стеке и системному вызову это может не нравится.

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

Пока получилась очень простая утилитка, но хочу её дописать как выпадет свободный вечер — сделать поддержку указания нескольких файлов и флага для режима присоединения. Размер радует, кстати, — всего 752 байта.

Дизассемблируй это

Дум (71.16КиБ)
«ДУМ», скомпилированный с использованием одних только команд MOV

В моей жизни последовательно сменяли друг друга три ассемблера. Первый, для компьютера «Радио-86РК», я выучил по комментариям к ассемблерному коду в журнале «Радио» — другой литературы не было, а до моего знакомства с интернетом оставалось несколько лет. Потом были последовательно ассемблеры для «Спектрума» и интеловских процессоров.

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

В ассемблере есть такая команда — MOV (в некоторых ассемблерах — LD), записывает содержимое одного аргумента в другой. Сейчас набор комманд разросся, аргументом может быть почти что угодно — регистр, ячейка в памяти, сумма некоторого числа, одного регистра и другого, умноженного на число, но по сути это всегда присваивание.

И вот оказалось, что эта команда — полная по Тьюрингу. Звучит невероятно, но это так. Некие ребята заморочились и сделали компилятор, который компилирует любую программу на Си в последовательность команд MOV. Причём им даже ДУМ удалось скомпилировать, правда один кадр рисуется семь часов. Кстати, такая программа неуязвима для горюшка века — Мелтдауна и Спектра.

Есть небольшая (на 156 страниц и 90% воды) презентация, достаточно популярно объясняющая как этого удалось достичь, но для её чтения надо знать ассемблер, поэтому я позволю себе раскрыть детали трансляции двух инструкций, чтобы пояснить принцип для тех, кто ассемблера не знает или ленится причитать.

Например, сравнение двух чисел делается при помощи следующего псеводокода:

mov [X], 0
mov [Y], 1
mov R, [X]

У нас есть два числа в аргументах «X» и «Y», результат сравнения которых попадает в «R» — там будет ноль, если числа не равны и единица в противном случае. Как же это работает?

Первой командой ноль записывается в ячейку по адресу «X». Это ассемблер, у нас тут всё — число, остальное — человеческие интерператации, поэтому записанное в «X» мы используем как адрес. Второй командой единица записывается в ячейку по адресу «Y». Третьей командой мы читаем значение по адресу «X» и если значения в «X» и «Y» совпадают, то ноль перетрётся единицей (и она попадёт в R), если нет, то в ячейке по адресу «X» ноль останется (который попадёт в R).

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

Возьмём, например, логическое «ИЛИ» («OR»), тут чуточку сложнее:

OR_ADDRS: dd OR_0, OR_1
OR_0: dd 0, 1
OR_1: dd 1, 1
; …
mov eax, X
mov edx, [OR_ADDRS + eax]
mov eax, Y
mov eax, [eax + edx]
mov R, eax

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

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

Далее в регистр «edx» записывается число из адреса, который является суммой адреса массива OR_ADDRS и содержимого регистра eax. Таким образом в eax попадёт OR_0 или OR_1, в зависимости от того былы записаны в eax ноль или единица. Эти значения — тоже числа и являются адресами двух других массивов из двух элементов.

Далее в eax мы записываем аргумент Y, его значение складывается с адресом полученным на предыдущем шаге и из получившегося адреса мы читаем записанное там значение. В переводе на ПХП получается следующее:

function mov_or(int $X, int $Y): int
{
    define('OR_0', [0, 1]);
    define('OR_1', [1, 1]);

    define('OR_ADDRS', [OR_0, OR_1]);

    $R = OR_ADDRS[$X][$Y];

    return $R;
}

Кстати, интересно, что у знаменитого дисассемблера «ИДА» от полученной таким образом программы крепко уносит крышу — при попытке отладки диссасемблер не видит никаких ветвлений и падает на анализе кода. Получился бы неплохой метод защиты от анализа, если бы не производительность.

Интернет-археология: браузер ViolaWWW

Неудачная попытка запуска (12.62КиБ)
Моя попытка запустить браузер под Линукс пока завершилась провалом

Пока читал про «Си-минус-минус», наткнулся на браузер ViolaWWW. Браузер разрабатывался с 1991 года в университете Беркли одним-единственным человеком — Вэй Пей-Юанем.

Эта вещь достойна внимания из-за примечательного факта — там реализованы таблицы стилей, за несколько лет до появления CSS, а так же первый скриптовый язык — задолго до «ДжаваСкрипта» и «Си-минус-минуса»! Причём с событиями и подобием аякса!

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

\class {field}
\name {wave}
\parent {}
\children {wave.sb}
\script {
    switch (arg[0]) {
    case "graph":
        f = float(arg[1]);
        xx = width();
        r  = height() / 2.0;
        theta = 0;
        for (x = 20; x < xx; x += 2) {
            theta = theta + 0.1;
            y = sin(theta * f) * r + r;
            drawLine(x, y, x, y + 1);
        }
        return;
    break;
    }
    usual();
}
\width {300}
\height {200}
\
\class {slider}
\name {wave.sb}
\parent {wave}
\script {
    switch (arg[0]) {
    case "_shownPositionV":
        usual();
        send(parent(), "graph", arg[1]);
        return;
    break;
    }
    usual();
}
\x {2}
\y {2}
\width {15}
\height {200}
\shownSizeV {10}

К сожалению, позапускать программы пока нечем. Исходный код я нашёл, но под Линуксом он компилироваться не захотел, собственно, судя по коду, он никогда под ним и не собирался. Я сделал минимально необходимые правки, код собрался, но попытка запуска завершилась крахом. Подумаю стоит ли тратить силы дальше.

Даже интересно какой бы веб мы увидели сейчас, если бы этот браузер тогда получил дальнейшее распространение! Получается в 90-х он умел больше, чем некоторые браузеры в двухтысячных. Например в скриптовом языке я заметил селекторы nthChild и даже nthWord!

Насколько я могу видеть, поддерживалось более десятка форматов графики, в том числе всем знакомый GIF, правда без анимации и в формате 1987 года, а так же сейчас уже экзотический XBM (до относительно недавнего времени поддерживался «Эксплорером», «Сафари» и «Оперой»).

Военный поиск

Палаточный лагерь (268.97КиБ)
Палаточный лагерь поискового отряда, крайняя левая палатка — моя

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

Обычно они занимаются Великой Отечественной, а тут под закрытие сезона выехали на захоронение предположительно времён Гражданской.

Захоронение (290.88КиБ)
Один из откопанных скелетов, в могилах никаких личных вещей не найдено

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

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

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

2017  

Билл «Кон» Гейтс

Устройство CON (184.46КиБ)
Устройство «CON» в 86-DOS версии 1.00

Какой херни только по радио не услышишь! Ехал вчера в такси, а по радио рассказывают про файл с именем CON, который нельзя создать в Винде, якобы потому что это было прозвище Билла Гейтса в детстве и он поклялся, что в его системе такой файл нельзя будет создать!

Ну что за бред?

Во-первых, это специальное имя файла, всё, что в него записывается выводится на CONsole — консоль. Таких имён множество, в основном трёх- и четырёхсимвольных. Например, есть PRN — принтер, COM1 — первый ком-порт и другие. Они достались Винде ещё от ДОСа.

Во-вторых, первые версии системы, которую сейчас называют просто «ДОС» были написаны в «Сиэтл Компьютер Продактсе» Тимом Патерсоном и лишь версия 1.10 была приобретена во владение Микрософтом и переименована из 86-DOS в MS-DOS. Специальные имена файлов были добавлены в версии 0.74, если запустить версию 1.00, легко убедиться, что CON там имеет тот же специальный смысл, причём ещё в те времена, когда система не принадлежала Биллу Гейтсу.

2017  

AppleScript и Тетрис

Тетрис (20.11КиБ)
Непритязательный внешний вид получившегося Тетриса

Я тут книгу читаю про хакеров (в первоначальном смысле слова) — «Хакеры. Герои компьютерной революции». Книга довольно занудная, но тот дух, который она пытается передать, я ещё очень хорошо помню. Ассемблер, такты, хаки, придуманные трудности — всё это было как специи в программировании. Сейчас как-то чем дальше, тем преснее.

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

Трудности, которые мне тут успешно самому себе удалось придумать, заключаются в том, что ЭплСкрипт в этой плоскости очень бедный язык. Если не сказать нищий. У него нет встроенных средств рисования чего-либо на экране или опроса клавиатуры, поэтому пришлось выкручиваться (те самые специи).

Для тех, кто не знает, надо наверное пояснить. ЭплСкрипт — это язык автоматизации пользовательских сценариев работы, он умеет управлять приложениями (с разной степенью успеха) и кое-какими вещами в «МакОСи».

Так как возможности что-то рисовать в нём нет, то стакан и фигуры сделаны из уменьшенных окошек приложения «TextEdit» — я их создаю по потребности и масштабирую к малому размеру. Получаются такие белые шарики, из которых всё и состоит.

Дольше всего я ломал голову над управлением, пока не придумал следующее — ЭплСкрипт умеет опрашивать значение громкости, так что клавиши уменьшения и увеличения я приспособил под сдвиг фигуры, а кнопку включения/выключения звука — под поворот.

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

Кажется это первая и единственная игра в реальном времени на ЭплСкрипте.

Крещение дочки

Крещение дочки (220.68КиБ)
Групповая фотография с крещения дочки, в центре — виновница торжества

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

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

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

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

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

2017   дочка

«Жизнь» Конвея на «R»

«Жизнь» (18.72КиБ)
Одно поколение в игре «Жизнь», запущенной в терминале

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

Не помню, чтобы я её когда-то либо программировал в чистом виде, но совершенно точно должен был ею вдохновляться, когда в детстве, ещё на Турбо Паскале реализовывал какую-то собственную её версию с генетическим обменом между соседями.

Сегодня ночью постарался написать классическую версию игры «Жизнь» на языке «Эр». Мне показалось это интересной задачей, так как «Эр» очень удобным образом позволяет работать со множествами так, как будто это скалярные значения.

Это можно увидеть почти сразу, в строке, где создаётся матрица случайных значений:

life <<- matrix(as.integer(runif(rows * cols) < ratio), rows, cols)

Сравнение с переменной ratio и вызов as.integer тут применяется к каждому значению из множества, генерируемого функцией runif (Random Uniform Distribution, генерирует случайные значения с нормальным распределением). Так матрица игры заполняется случайными нулями и единицами.

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

Суть такова: матрица двигается во все восемь направлений (с шагом 45°), все получившиеся матрицы складываются, это позволяет оценить количество соседей любой клетки в каждом из направлений. Далее в две строки выполняются основные условия:

# Any dead cell with exactly three live neighbours becomes a live cell
life.new[life == 0 & life.neighbors == 3] <- 1
# Any live cell with fewer than two or more than three live neighbours dies
life.new[life == 1 & (life.neighbors < 2 | life.neighbors > 3)] <- 0

Тут участвуют три матрицы: life — поле игры на предыдущем шаге, life.new — поле игры на текущем шаге и life.neighbors — матрица соседей (точка в «Эре» не имеет специального значения). Как видите матрица сравнивается с числом, как будто она скаляр, результат этой операции тоже матрица, но из булевых значений, в каждой клетке — результат операции. Такую матрицу можно использовать как индекс, в результате чего вернутся только те ячейки, по адресам которых в булевой матрице было TRUE.

Дальше просто, поскольку ячейки возвращаются по ссылке, им скопом присваивается требуемое значение.

Погодный плагин для «Sublime Text»

Окно редактора (86.25КиБ)
Окно редактора «Саблайм Текст» с погодой и пробками в строке состояния

Написал свой первый плагин для «Саблайма» — для отображения в строке состояния погоды и пробок «Яндекса». Заодно немного повспоминал «Пайтон», давно на нём ничего не писал.

Удивительно, но факт — другого работоспособного плагина на эту тему не обнаружилось. Единственный конкурент использует старое АПИ «Яху», которое уже не работает, потому не работает и плагин.

Отдельное спасибо «Яндексу» за то, что их АПИ умеет определять текущее местоположение — ничего задавать в конфиге не надо, удобно.

Поскольку в строке состояния можно выводить только буквы, воспользовался Юникодом для вывода погодных значков, для уровня пробок не нашлось ничего лучше фруктов, так что индикатором загруженности дорог у меня служат зелёное яблоко, жёлтый лимон и красный помидор. Не знаю будут ли видны эти символы пользователям Линукса или Виндоуза, мне негде посмотреть.

Пришлось повозиться с АПИ самого «Саблайма». Во-первых, отдельного события на старт редактора нет, пришлось активировать плагин на получение фокуса окном. Во-вторых, некоторые авторы плохо читают документацию — у многих их плагины полностью переинициализируются на событие, это ни к чему, я, например, дёргаю «Яндекс» как можно реже, по таймеру в отдельном потоке, а в строке показываю кешированное.

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

Подал пул реквест на включение в саблаймовский пакетный менеджер, но особо упорствовать не буду, если откажут.

🎱 Сложности при написании «99 бутылок» на «Электронике МК-61»

Электроника МК-61 (127.50КиБ)
Тот самый калькулятор «Электроника МК-61», который попал мне в руки

Как и обещал в прошлый раз, хочу рассказать с какими сложностями мне пришлось столкнуться при написании «Песни о пиве» на программируемый калькулятор «Электроника МК-61».

Калькулятор попал ко мне для передачи в компьютерный музей, который готовится к открытию в высшей школе «ИТИС» и у меня и мыслей-то не было что-то под него писать — хотелось лишь посмотреть что из себя представляет написание программ под этот гаджет, с которыми я много сталкивался в детстве через журнал «Наука и жизнь».

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

Шестнадцатеричная система калькулятора (38.55КиБ)
По этой таблице калькулятор отображает шестнадцатеричные числа

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

Тем более мне очень повезло в том, что индикацией этого режима является именно восьмёрка, стоящая спереди — так похожая на первую букву в нужном мне слове. Как вы теперь понимаете, на экране в моей программе написано на «BEEr», с точки зрения калькулятора там одни числа и индикатор режима — «8.EEГ» или «EED» в привычной записи (десятичное «3821»).

Так же я воспользовался тем, что «F» в этой странной нотации — пробел, что позволило мне отделить число от «надписи». Дальнейшее — дело техники.

Листинг из предыдущего поста начинается с подготовки данных в регистрах — нескольких масок на которые битовыми операциями в дальнейшем наложатся числа так, чтобы получилась нужная мне «строка». Настоящая программа начинается с нажатий на «В/О» и «ПРГ», и активно пользуется подготовленными данными.

Маски и оставшиеся числа побитно накладываются двумя подпрограммами — по отдельности на диапазоны 0…5 и 6…9 — способы их получения разнятся. Я широко использую операцию «ИНВ» — она инвертирует биты числа, по логике инвертирования цифры 6…9 превращаются в на свои позициях 9…6, и тут никаких сложностей, а знаки в диапазоне 0…5 получаются сложнее — там накладываются сразу две специальные маски и счётчик.

Для примера кусочек подпрограммы, номер слева соответствует номеру инструкции при вводе в калькулятор и каждую строку я прокомментирую, но следует понимать, что в реальности эти комментарии частью программируемы не являются:

// когда что-то попадает в «X» предыдущее значение смещается в «Y» (там стек из 4 позиций)
45 1 // «1» кладётся в регистр «X», входной параметр сместился в «Y»
46 + // Y + 1, передаётся в «X», это коррекция данных после операций, которые шли выше
47 К П→Х 7 // в «X» помещается число из регистра (маска), указанного в регистре №7
48 + // Y+X → X
49 ИНВ // инвертирование битов в «X», сейчас на экране написано «8.EEГ» плюс некий пока мусорный символ
50 К П→Х 8 // в «X» помещается число из регистра, указанного в регистре №8
51 ^ // операция «И», на экране — «8.EEГ», из некого символа получилось требуемое число
52 В/О // возврат из подпрограммы

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

Подпрограмма вызывается два раза с разными параметрами — для десятков и единиц, получаются две маски, которые позднее накладываются друг на друга.

Сильно расстраивает отсутствие памяти для долговременного хранения, в устройстве хоть и есть батарейный отсек, но при любом сбое питания (батарейки сели, а блок питания не подключен), память калькулятор сбрасывается и программу придётся набрать заново. То же ожидает и в случае, если одну программу нужно будет заменить другой — хотя программы можно запускать с произвольного адреса, много их в память не уместится — её ёмкость всего 105 инструкций.

Ранее Ctrl + ↓