Сохранёнки программиста
6.55K subscribers
1.17K photos
59 videos
10 files
1.86K links
Заметки и ссылки на будущее, чтобы изучить когда будет время.

Разместить рекламу: @tproger_sales_bot

Правила общения: https://tprg.ru/rules

Другие каналы: @tproger_channels

Другие наши проекты: https://tprg.ru/med
Download Telegram
Одна строка #define MICROPY_HW_ENABLE_RNG (0) отключила аппаратный генератор случайных чисел на STM32, и прошивка аппаратного кошелька молча перешла на слабый программный Yasmarang. Гостевой разбор на btc++ по мотивам истории с Coldcard показывает, как это осталось незамеченным.

В коде видна попытка переопределить pyb_rng_get под собственный источник энтропии, рядом комментарий «у нас своя версия этого кода». Но make_new_wallet() вызывал random.bytes(32), а тот шёл другим путём, через rng_get(), и в итоге брал числа из генератора, который годится для игр, а не для ключей. Код исполнялся без ошибок и честно возвращал 32 байта.

Отдельная часть текста про ревью: автор сравнивает обычный коммит на 15 строк и 235 символов описания с подозрительным изменением на 1534 строки и 5 символов в сообщении. Это независимый разбор, а не официальный отчёт Coldcard, о чём в тексте сказано прямо.

@prog_stuff
Как понять, что программисту пора в отпуск:
— на столе бардак;
— шорты не доставались с позапрошлого лета;
— чудится тифлинг;
— на вопрос «когда отдыхаешь?» отвечает «после релиза»;
— релиз был в феврале.

Сам он с места не сдвинется. Помогите Типичному Программисту собраться и улететь в отпуск в новой мини-игре!
😁5
ELF — это база данных, которая отказывается в этом признаться: .strtab делает интернирование строк, .gnu.hash работает индексом, таблица заголовков секций — это таблица таблиц, а st_name — внешний ключ, разложенный руками.

Фарид Закария довёл мысль до конца. Его прототип SELF — исполняемый файл, который целиком является базой SQLite: file hello показывает «SQLite 3.x database», ./hello печатает Hello, world!, а sqlite3 hello 'SELECT soname FROM ldd' отвечает libc.so.6. Запуск идёт через binfmt_misc и отдельный интерпретатор.

Отсюда strip — это DELETE и VACUUM, patchelfUPDATE, а LD_PRELOAD — строка в таблице, которую можно включить и откатить транзакцией.

Цена: около 5 миллисекунд на старте и страницы, которые копируются из b-дерева вместо отображения в память.

@prog_stuff
На скриншоте ролик, к которому YouTube показывает пометку «снято камерой». Внутри — рендер из Big Buck Bunny.

Дэвид Бьюкенен разобрал, почему C2PA на Android так подделывается. Приложения-камеры опираются на Key Attestation и Play Integrity, а рут, полученный через эксплойт, обе проверки не тревожит: загрузчик остаётся заблокированным, ключи AVB — вендорскими, и серверы Google спокойно выдают устройству ключи подписи. Сам ключ из StrongBox не вытащить, но попросить Titan M2 подписать произвольные данные рут может.

Проверял автор на Pixel 8a и 9a, рут брал одноклик-эксплойтом CVE-2026-43499, который на полностью обновлённых Pixel всё ещё работает. Приложение Pixel Camera при этом имеет Assurance Level 2 — высший из определённых сейчас уровней программы соответствия C2PA.

Google закрыла отчёт как «Won't fix (infeasible)», выплатила 7500 долларов, а плашка у ролика после публикации исчезла — автор считает, что её сняли руками.

@prog_stuff
Тип ! в Rust обозначает значение, которого не бывает: его «возвращает» функция, которая не возвращается никогда. Нестабильным он пробыл десять лет.

24 августа PR из восемнадцати коммитов влили в rust-lang/rust:main. Автор под ником WaffleLapkin пишет, что занимался стабилизацией больше двух лет, а до него было пять неудавшихся попыток.

Помимо самой стабилизации в патче ещё три вещи. Fallback никогда-типа теперь ! во всех редакциях, и это ломающее изменение — под него гоняли Crater по экосистеме. std․convert․Infallible стал псевдонимом !, то есть из pub enum превратился в pub type. Линт dependency_on_unit_never_type_fallback удалён: подсказывать больше не о чем.

Речь про nightly; в стабильную ветку изменение приедет обычным циклом релизов.

Сам автор описал ощущение от финала так: «Чувствую себя… пустым. Прямо как never type».

@prog_stuff
Отладочная сборка Firefox линкуется у lld за 4,44 секунды, у mold — за 0,89. Разница видна на графике: mold почти всю линковку держит занятыми 32 ядра, а lld преимущественно работает на одном ядре, изредка выходя в многоядерные всплески.

Руи Уэяма, автор mold, разобрал устройство своего линкера в статье от 24 августа. Ускоряли не отдельные проходы, а весь конвейер: разбор всех входных файлов, включая каждый элемент архивов, разрешение символов отдельным проходом через атомарный compare-and-swap, сборка мусора по секциям, слияние строк и запись результата. На девяти больших проектах mold быстрее lld в 2,4–16,1 раза и до 112 раз — GNU ld.

Одним проходом ускорение не объясняется: оставить последовательными копирование вывода и применение релокаций — и время линковки растёт на 542 процента.

Цена — эвристика: порядок разрешения конфликтов символов между общими библиотеками стандарт не описывает, mold выбирает его сам. Проверяли сборкой всего Gentoo: из 19 000 с лишним пакетов не собрались два.

@prog_stuff
Венсан Берна собрал введение в spanning tree, где схемы не нарисованы, а работают. В браузере крутится настоящий демон MSTPD, скомпилированный в WebAssembly через emscripten: код, который обычно говорит с ядром Linux, заменён на C API — он заводит мосты и порты, отдаёт состояние в JSON и двигает время детерминированно.

Дальше можно ронять кабели и смотреть по шагам, вперёд и назад, как дерево пересобирается и какой порт уходит в блокировку. Чтобы понять, сошлась ли топология, автор после каждого шага сохраняет снимок памяти симуляции, проигрывает пятьдесят секунд модельного времени и откатывается к снимку.

Сорок юнит-тестов на эту обвязку проходят за 396 миллисекунд.

Чтения на тридцать шесть минут, и RSS-читалка не подойдёт: примеры живут только на странице.

@prog_stuff
1👍1
«x86 выполняет обращения к памяти строго по порядку, а ARM и RISC-V переставляют их как хотят, поэтому слабая модель памяти масштабируется лучше». Фабиан Гизен объясняет, почему эта формула ошибочна с обеих сторон.

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

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

Часто цитируемые 7 процентов на аппаратный TSO у Apple Silicon Гизен отдельно снабжает оговоркой: это цена конкретной реализации, где TSO включается ради Rosetta и не является основным режимом ядра, спроектированного под AArch64.

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

@prog_stuff
Big Pineapple — платформа, на которой у Cloudflare работают 1.1.1.1, Gateway DNS, DNS Firewall и AS112, — держит в памяти больше 250 миллиардов записей кэша. Пять правок в том, как одна запись там лежит, вернули компании около 100 терабайт: столько оперативной памяти стоит примерно в 130 серверах их тринадцатого поколения.

Компания разобрала все пять по порядку. Vec и String заменили на Box<[T]> и Box<str>: поле capacity бесполезно там, где запись после создания уже не меняется, а это 8 байт на поле и 64 байта на запись. Три списка DNS-секций свернули в один список с двумя смещениями по u16 — минус ещё 28 байт. Крупные варианты enum убрали в кучу, чтобы размер записи не диктовал самый жирный из них.

@prog_stuff
Крейт из одной строчки показал, что Rustdoc делает лишнюю работу на каждой сборке

Перед каждым стабильным релизом Rust команда прогоняет Crater — инструмент, который собирает новой версией компилятора всю публичную экосистему. В прошлом месяце Crater нашёл, что бета-Rustdoc падает на крейте indented-blocks. Весь крейт — одна строчка #![recursion_limit = "8"], ничего больше.

Rustc такой код компилирует спокойно. Rustdoc падает с «reached the configured maximum number of stack frames», разбирая внутренний трейт NumBufferTrait из core::fmt.

Формально это не регресс: число кадров стека не входит в гарантии стабильности. Но Ноа Лев из команды Rustdoc зацепился за другое — с какой стати почти пустому крейту вообще инлайнить документацию трейта из core::fmt, который никто не переэкспортирует. Логи подтвердили, что Rustdoc действительно лезет за ним.

За неделю разбора и серии PR среднее время работы упало на 25%, то есть Rustdoc стал быстрее на 33%. На реальных крейтах вроде hyper и bitmaps выигрыш доходит до 40%, на микробенчмарках вроде helloworld — до 60%.

Автор прошёл весь путь по шагам: от странного сообщения на Zulip до профилирования и правок, попутно объясняя, как Rustdoc устроен изнутри и почему он так тесно завязан на внутренние API компилятора.

@prog_stuff
❤‍🔥2
«TUI лучше, потому что с клавиатуры» — аргумент, который держится на плохих GUI

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

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

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

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

@prog_stuff
Переписать с нуля — один из семи вариантов, и обычно худший

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

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

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

В нашем канале Tproger разобрали подробнее.

@prog_stuff
Samsung встроила вычислительные блоки прямо в LPDDR5X

Samsung продолжила линию Processing-in-Memory: в LPDDR5X-9600 у каждого из 16 банков появился PIM-блок с MAC-деревом, регистрами инструкций и векторов активаций. Память выполняет часть вычислений там, где лежат данные, и работает через стандартный протокол LPDDR5X. Особые адреса строк включают режимы по принципу MMIO-регистров.

Устройство напоминает жёстко ограниченный SIMD: одна операция и один операнд распространяются на все 16 банков. Их суммарная внутренняя пропускная способность достигает 614 ГБ/с, тогда как обычное обращение к памяти через внешнюю шину упирается в 76,8 ГБ/с.

Каждый PIM-блок держит четыре операции MAC в INT8 или FP8 за такт данных, то есть восемь за такт без учёта удвоенной передачи. На четырёхбитных весах пропускная способность удваивается, и пакет выдаёт 2,4 TOPS. Восемь чипов дают 9,6 INT8 TOPS, как NPU Intel Meteor Lake, и требуют 128 ГБ системной памяти. Архитектуру разобрали по материалам Hot Chips 2026.

@prog_stuff
🤯1
OpenOffice не печатал по вторникам, и виноват был Erlang

В багтрекере Ubuntu годами висела жалоба: печать из OpenOffice иногда не работает. Пользователи спорили, чей это баг, потому что из других программ печаталось нормально. Один снёс и переставил пакет, отчитался в четверг, что помогло — и через две недели, во вторник, написал, что не помогло.

Через четыре месяца жена одного из разработчиков Ubuntu сформулировала точно: OpenOffice не печатает по вторникам. Показать мужу она этого не могла, потому что была среда.

Разгадка оказалась в утилите file, которая определяет тип файла по сигнатурам в начале. OpenOffice записывает в PostScript дату создания, и по вторникам она принимает вид %%CreationDate: (Tue MMM D hh:mm:...).

В базе сигнатур лежало правило для Erlang JAM-файла:

4 string Tue Jan 22 14:32:44 MET 1991 Erlang JAM file - version 4.2

Пробелы в образце не экранированы, поэтому сравнивались только первые три символа. Правильная запись — 4 string Tue\ Jan\ 22\ 14:32:44\ MET\ 1991. По вторникам PostScript опознавался как Erlang JAM и до принтера не доезжал.

Правил в file больше 1600, и порядок проверки решает: ложное совпадение с Erlang случалось раньше, чем проверка на PostScript. История целиком — в собрании курьёзных багов.

@prog_stuff
😁1
Одна JSON-колонка в SQLite вместо спроектированной заранее схемы

David Leadbeater показал приём, который сохраняет смысл спустя годы: документ целиком лежит в одной колонке SQLite, а востребованные поля постепенно становятся частью схемы. Заранее перечислять всю структуру JSON не требуется.

Поле извлекается через json_extract внутри generated column. Виртуальную колонку можно индексировать обычным CREATE INDEX, после чего запрос использует индекс как для привычного столбца. Когда появляется новый способ поиска, схема расширяется через ALTER TABLE; в статье план запроса проверяется командой EXPLAIN QUERY PLAN.

Generated columns есть в SQLite начиная с версии 3.31. Leadbeater разбирает варианты VIRTUAL и STORED, ограничение NOT NULL и индексы.

Границы у приёма явные. Колонку STORED таким способом через ALTER TABLE добавить нельзя, а некорректный JSON ломает вставку. Получается постепенная схема: исходный документ остаётся целым, а наружу выходят только поля, которым действительно понадобились ограничения или быстрый поиск.

@prog_stuff
Писать код и строить систему — разные работы

Эссе, которое разбирают на Hacker News, начинается с наблюдения: перевести идею в инструкции для машины и решить, какие инструкции вообще должны существовать, — два разных занятия. Пример автора: «обрабатывать входящие события и обновлять данные». Синхронно или через очередь? Ровно один раз или хотя бы один? Что делать, если потребитель лежал три часа? Ни один из вопросов не про синтаксис.

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

В нашем канале «Tproger» разобрали подробнее.

@prog_stuff
Почтовый CSV Японии рвёт строки посередине слова — и парсить его приходится всей стране

Пол О'Лири МакКанн, автор питоновской библиотеки posuto, разобрал ken_all.csv — файл, в котором Почта Японии раздаёт базу всех почтовых индексов. Формат легендарный: о нём регулярно ругаются в соцсетях, а один твит описывает грешников, которых в аду заставляют парсить этот файл вечно.

Внутри: если название района длиннее 38 символов, строка разрезается на несколько — в случайном месте, не по границе слова, — а остальные поля дублируются в каждой. В поле названия встречаются скобочные примечания «для читателя», ссылающиеся на порядок строк, — в формате, где строки должны быть самодостаточны. Поля не квотируются. На один индекс может приходиться до 66 строк.

Причину разрезания никто не объяснил; автор предполагает буфер фиксированной длины в системе тридцатилетней давности. Урок переживёт сам файл: данные, которые готовили для человеческого глаза, мстят машинному парсеру.

Статья — Parsing the Infamous Japanese Postal CSV

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

Вам достался файл без спецификации: двоичный файл настроек прибора, сохранение игры, кеш программы. Автор ImHex — бесплатного открытого hex-редактора — годами отвечал на этот вопрос одной фразой «посмотри декомпилированный код того, кто его пишет», и наконец разобрал процесс целиком на файле сохранения игры FEZ.

Первый взгляд в hex-редактор уже даёт три вывода: в файле видны читаемые строки вроде DOT_LOCKED_DOOR_A, значит он не сжат и не зашифрован; в начале нет сигнатуры, значит по ней формат не опознать. Дальше автор идёт к тому, кто файл пишет. FEZ написана на C#, её сборки декомпилируются почти в исходник; в EasyStorage находится класс PCSaveDevice, а в нём строка "SaveSlot" + index — то самое имя файла. Рядом метод Save, который заводит буфер на 40 960 байт и заполняет его через BinaryWriter. Порядок вызовов записи и есть структура файла.

Остаток разбора — перенос этого порядка в Pattern Language, встроенный язык описания форматов ImHex: struct с полями, размещение по адресу через оператор @, отдельные типы под строки, списки и перечисления. Схема одновременно и парсит файл, и документирует находки, и проверяет их: ошиблись в поле — данные перестанут сходиться.

Общий рецепт автор в конце сводит к четырём шагам: проверить, не известен ли формат (magic-детект ImHex, binwalk); найти код, который файл читает или пишет; опознать в нём привычные кирпичи — числа, строки, булевы значения, структуры; записать всё это схемой. Для .NET декомпилятор — Rider, для нативного кода — Ghidra, IDA или Binary Ninja, для JVM — Recaf.

@prog_stuff
👍2
Ваш бинарник в Alpine может быть на 26% медленнее из-за libc, а не из-за кода

musl — лёгкая libc, на которой собраны образы Alpine и самодостаточные статические бинарники «один файл — и работает». Джонатан Эллис, сооснователь DataStax и многолетний глава проекта Apache Cassandra, замерил цену этого удобства на своей рабочей нагрузке.

Со штатным аллокатором musl проигрывает разгромно, причём на обычных четырёхъядерных машинах, а не только под высокой конкурентностью. Замена на mimalloc убрала большую часть разрыва, но 26% остались: профилирование указало на базовые процедуры работы с памятью внутри самой libc.

В нашем канале «Tproger» разобрали подробнее, включая то, что автор оставил для musl.
METR разобрала, как у неё украли API-ключ: приложение, написанное с ИИ, при сбое проверки прав пускало всех

Лаборатория, которая тестирует модели для крупных разработчиков, опубликовала отчёт о двух инцидентах. В марте исследователь поднял на личном EC2-инстансе маленькое веб-приложение за входом через Google. В коде была ошибка fail-open: если проверка авторизации не срабатывала, приложение считало пользователя допущенным. Инстанс несколько дней смотрел в интернет, атакующий вытащил ключ к моделям, добавил свой SSH-ключ и три недели гонял чужие запросы.

Заметить было трудно: за кредиты METR не платила деньгами, так что счёт не рос, а внутренняя панель METR не показывала отклонённые по лимитам запросы. В мае пришла вторая волна: сканирование, перебор паролей, попытки через OAuth-токены и ошибка в публичном просмотрщике транскриптов с read-only SQL.

Выводы для любой команды: ключи только в секрет-хранилище, приложения с доступом к ключам делать fail-closed, на каждый ключ лимит и алерт по числу запросов, а не только по деньгам. Полный разбор цепочки на сайте.

@prog_stuff
Один параметр readahead сократил обращения к диску в 12 раз, а io_uring тут ни при чём

Фернандо Симойнс разбирал, почему его встраиваемая СУБД на io_uring не выигрывала у обычного чтения на сканах. Тест: запрос Q6 из TPC-H по таблице в 1,2 GiB, файл открыт с O_DIRECT, запросы на чтение уходят в очередь io_uring.

Без упреждающего чтения база отправила 195 тысяч запросов по 4 KiB, и почти столько же дошло до устройства: ядру нечего было склеивать. С окном readahead в 32 страницы запросов в очереди стало даже больше, 218 тысяч, но до диска добралось около 16 тысяч. Ядро объединило 93% соседних чтений, средний запрос вырос с 4,37 до 56,53 KiB.

Цена тоже есть: поток sqpoll, который опрашивает очередь вместо системных вызовов, съел 65% циклов процессора. Вывод автора в том, что io_uring не ускоряет сам по себе, а только даёт очередь, и без прогрева этой очереди последовательными чтениями выигрыша нет.

@prog_stuff