Сохранёнки программиста
Open in Telegram
Заметки и ссылки на будущее, чтобы изучить когда будет время. Разместить рекламу: @tproger_sales_bot Правила общения: https://tprg.ru/rules Другие каналы: @tproger_channels Другие наши проекты: https://tprg.ru/med
Show more6 541
Subscribers
+324 hours
+37 days
-4530 days
Posts Archive
«x86 выполняет обращения к памяти строго по порядку, а ARM и RISC-V переставляют их как хотят, поэтому слабая модель памяти масштабируется лучше». Фабиан Гизен объясняет, почему эта формула ошибочна с обеих сторон.
За исключением совсем маленьких ядер и микроконтроллеров без кэша, процессоры вообще не соблюдают собственную модель памяти на каждом обращении. Они обещают вести себя так, как если бы соблюдали, и разница здесь принципиальная. Реализуют это оптимистично: считают, что данные никто параллельно не менял и что запись никем не оспаривается, выполняют обращения в удобном порядке, а рядом держат ровно столько метаданных, чтобы задним числом заметить нарушение порядка. Заметили — инструкция не фиксируется, состояние откатывается на момент до её выполнения, дальше повтор.
Так устроены все, независимо от модели памяти. Разница в другом: сколько метаданных нужно хранить для каждой обращающейся к памяти инструкции в полёте и какие порядки разрешено фиксировать при конфликте. Сильная модель чаще признаёт конфликт и уходит на повтор, слабая допускает больше законных порядков для расслабленных чтений и записей. При этом конфликт — медленный случай на любой архитектуре.
Часто цитируемые 7 процентов на аппаратный TSO у Apple Silicon Гизен отдельно снабжает оговоркой: это цена конкретной реализации, где TSO включается ради Rosetta и не является основным режимом ядра, спроектированного под AArch64.
Личное правило автора для многопоточного кода: конкурировать за общие данные реже, а не быстрее.
@prog_stuff
Венсан Берна собрал введение в spanning tree, где схемы не нарисованы, а работают. В браузере крутится настоящий демон MSTPD, скомпилированный в WebAssembly через emscripten: код, который обычно говорит с ядром Linux, заменён на C API — он заводит мосты и порты, отдаёт состояние в JSON и двигает время детерминированно.
Дальше можно ронять кабели и смотреть по шагам, вперёд и назад, как дерево пересобирается и какой порт уходит в блокировку. Чтобы понять, сошлась ли топология, автор после каждого шага сохраняет снимок памяти симуляции, проигрывает пятьдесят секунд модельного времени и откатывается к снимку.
Сорок юнит-тестов на эту обвязку проходят за 396 миллисекунд.
Чтения на тридцать шесть минут, и RSS-читалка не подойдёт: примеры живут только на странице.
@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
Тип
! в 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На скриншоте ролик, к которому 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
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, patchelf — UPDATE, а LD_PRELOAD — строка в таблице, которую можно включить и откатить транзакцией.
Цена: около 5 миллисекунд на старте и страницы, которые копируются из b-дерева вместо отображения в память.
@prog_stuffКак понять, что программисту пора в отпуск:
— на столе бардак;
— шорты не доставались с позапрошлого лета;
— чудится тифлинг;
— на вопрос «когда отдыхаешь?» отвечает «после релиза»;
— релиз был в феврале.
Сам он с места не сдвинется. Помогите Типичному Программисту собраться и улететь в отпуск в новой мини-игре!
Одна строка
#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В LLVM 23 время компиляции сократилось на 6,75 процента, а на sqlite3 — на 10,53. Никакого одного крупного изменения за этим нет: Александр Энгельке собрал по частям, откуда взялись эти проценты.
Хеш-таблицы перевели с квадратичного пробирования на линейное и избавились от ключей-надгробий:
DenseMap дал −1,27 процента, SmallPtrSet −0,24, StringMap −0,10. Хеш-функцию сменили с CityHash на xxh3. В SmallVector путь роста вынесли из тела push_back, чтобы разрешить хвостовой вызов, — ещё −0,50.
Отдельная линия — GlobalISel на AArch64 -O0: отставание от FastISel упало с 12,71 до 9,39 процента. Автор оговаривает, что любимую свою правку тут потом откатили.
@prog_stuffСамый быстрый известный алгоритм печати double безымянный: он живёт в файле
yy_double.c внутри JSON-библиотеки yyjson, написан её автором ibireme и почти нигде не описан. Виктор Зверович, автор {fmt}, разобрал, как он устроен и за счёт чего входит в число самых быстрых.
Классический Schubfach на каждое число делает два-три 192-битных умножения. Здесь ядро работает на целых фиксированной ширины и обходится одним умножением на заранее вычисленную степень десяти. Дальше рассматриваются четыре кандидата на округление и выбирается кратчайшее корректное представление с округлением к чётному. Отдельно разобран пограничный случай, где алгоритм выбирает между 2e2 и более длинным 19e1, и на первый взгляд это похоже на баг.
Автор использует тот же алгоритм в своей библиотеке Żmij. К тексту приложен интерактивный визуализатор на формате E4M3 из 256 кодировок, на котором видно, как работает каждый шаг.
@prog_stuffКак тестировать сервис, у которого 96 операций в API, больше 500 триллионов объектов и свыше 200 миллионов запросов в секунду? Инженеры Amazon описали свой подход на примере S3: рядом с настоящим сервисом живёт исполняемая эталонная модель, которая хранит состояние и сверяет с ним каждый ответ.
Сценарии к модели не пишутся руками и не берутся случайно. Поведение раскладывается на признаки: например, для чтения объекта учитываются 21 параметр запроса, 36 параметров ответа и само содержимое. В одном из экспериментов из 37 признаков получилось 135 категорий поведения и 1025 качественно различных сценариев, которые и генерируются целенаправленно.
Сравнение с обычным тестированием на свойствах показательно: там 28 457 запросов дали 9040 уникальных сценариев, остальные 19 417 оказались повторами. У направленной генерации трёхчасовая кампания выполняет около 432 000 запросов.
В трёх запусках в конвейере сборки модель поймала 171, 92 и 109 расхождений поведения — среди них десятки настоящих проблем, которые иначе уехали бы дальше.
@prog_stuff
Если хочется разобраться в криптографии руками, а не по формулам, есть Cryptopals — восемь наборов заданий, где вы последовательно ломаете реальные конструкции.
Устроено так: предварительных знаний криптографии не требуется, нужен только уверенный навык программирования, а язык любой. Каждое задание решается кодом, а не угадыванием.
Первый набор — разминка: hex, Base64, XOR одним байтом, XOR повторяющимся ключом, обнаружение режима ECB. Второй уже интереснее: дополнение по PKCS#7, режим CBC, оракул выбора режима, переворот битов в CBC. Дальше — потоковые шифры, генераторы случайных чисел и повторное использование одноразового значения. Затем атака посредника на обмен ключами Диффи-Хеллмана, подмена параметров группы. Ближе к концу — восстановление сообщений RSA, слабые одноразовые значения в подписях, оракулы дополнения.
Почему это не устарело за тринадцать лет: ECB, оракул дополнения, повторно использованный nonce и плохая случайность — это классы ошибок, а не конкретные библиотеки. Оговорка авторов тоже важна: набор учит ломать, но не является руководством по выбору криптографии для продакшена.
@prog_stuff
Обычная модель угроз для защиты от шифровальщиков предполагает, что операционная система на нашей стороне. Группа из Мичиганского университета взяла модель пожёстче: атакующий контролирует ядро, файловую систему, драйверы, гипервизор и даже привилегированного администратора.
Защита вынесена ниже всего этого — на уровень блочного устройства. Физический блок нельзя перезаписать до истечения заданного интервала, состояние блоков ведётся в журнале, который можно только дополнять, а каждая операция проверяется перед записью. То есть шифровальщик может записать свои данные, но не может стереть старые.
Прототип собран как блочный драйвер для ext4 на Raspberry Pi с обычными диском и твердотельным накопителем. Ядро проверяющей части занимает около 400 строк кода, а свойства «обход невозможен» и «восстановление корректно» доказаны формально в Dafny.
Проверили на 18 семействах программ-вымогателей: файловую систему удалось восстановить в каждом случае. Накладные расходы — 0,4 процента по времени и 0,5 процента по пропускной способности накопителя, счётчики занимают около 2 мегабайт на терабайт данных.
@prog_stuff
20 августа на crates.io вышла версия 0.3.10 крейта
arrayref. Исходники макросов в ней прежние, в манифесте одно изменение — добавлена зависимость proc-macro1 версии 1.0.107.
Настоящий крейт называется proc-macro2, а proc-macro1 — типосквот с подделанным полем authors под именем Дэвида Толная; его src/ копирует proc-macro2, поэтому сборка продолжала работать. Вредонос лежит в сборочном скрипте: адрес сервера собирается из base64-фрагментов, бинарник качается по TLS без проверки сертификата и запускается отдельно от сборки. Срабатывает во время компиляции — достаточно просто собрать проект.
Отдельный ход: с того же аккаунта отозвали версии с 0.3.5 по 0.3.9. Cargo на отозванную версию предлагает обновиться, и единственной неотозванной оставалась 0.3.10.
Rust Security Response Team сообщила, что 0.3.10 была доступна 86 минут: опубликована в 07:15 UTC, удалена в 08:41. Отозванные версии вернули, аккаунт заблокировали. Задеты ещё internment 0.8.7 и append-only-vec 0.1.9.
@prog_stuffПрофилировщик показывает, что горячая функция ждёт память. Дальше начинается гадание: какое именно поле какой структуры не влезает в кэш.
Группа из Университета штата Северная Каролина и Google сделала профилировщик, который отвечает на этот вопрос прямо: каждое обращение к памяти связывается с конкретным типом и полем внутри него. Работает поверх штатного
perf и отладочной информации, накладных расходов во время работы программы не добавляет, потому что разбор идёт офлайн.
Проверяли на ядре Linux 6.17, memcached, Redis, Git, FFmpeg и Binutils. Покрытие типов для обычной сборки Ubuntu — 92,7 процента, циклов ядра — больше 90. У FFmpeg покрытие циклов всего 40 процентов, потому что там много рукописного векторного кода без отладочной информации.
Пример находки: в нагрузке MySQL на 256 серверах структура cfs_rq из планировщика занимала 7,58 процента циклов ядра и давала 49,02 процента промахов последнего уровня кэша. Перестановка полей внутри структуры этот вклад заметно снизила.
@prog_stuffСписок, который стоит открывать каждый раз, когда садитесь писать что-то с датами: «Заблуждения программистов о времени» Ноа Сассмана. Тридцать четыре утверждения, каждое из которых кажется очевидно верным и каждое неверно. Во второй части их ещё семьдесят девять.
Выборочно: в сутках не всегда 24 часа. Часовой пояс машины не совпадает с поясом пользователя. Часовые пояса меняются политическим решением, а переходы на летнее время не постоянны. Часы клиента и сервера не совпадают. Минута на часах не всегда равна минуте реального времени. Временные метки не обязаны быть уникальными. Время события, время записи в журнал и время получения сообщения — три разных момента.
Любимый пример оттуда: виртуальная машина, приостановленная на два часа, после запуска продолжила считать, что всё ещё час дня.
Вторая часть добирается до високосных секунд, разницы между настенными и монотонными часами и до того, почему
sleep(1000) не значит «ровно секунда».
@prog_stuffСтатический анализатор выдал 147 643 предупреждения о работе с неинициализированной памятью в ядре Linux. Подтвердились и были исправлены 52.
Эта цифра — отправная точка работы группы из Калифорнийского университета в Риверсайде, представленной на OSDI в июле. Проблема известна всем, кто пробовал внедрить анализатор в большой проект: покрытие огромное, а доля настоящих находок такая, что список никто не разбирает.
Идея авторов: проверять каждое предупреждение отдельно, исполняя подозрительный участок кода по-настоящему. Для этого произвольный набор функций на C и C++ собирается в самостоятельный исполняемый файл без правки исходников, а дальше по нему идёт символьное исполнение. Запускать всё ядро или готовить окружение не нужно.
По цифрам: для Linux 6.16.0 удалось собрать 88,9 процента участков, для Android LTS 5.10.240 — 96,2 процента. Разбор одного предупреждения занимал в среднем 0,32 секунды против 5,14 у сравниваемого подхода, а до вердикта доводилось 95,46 процента случаев против 41,29.
@prog_stuff
Телефон переставал играть музыку в Bluetooth-наушниках, как только на компьютере открывалась вкладка AliExpress. Закрыть вкладку — звук возвращается, замьютить вкладку или всю систему — не помогает.
Автор блога laserphile обернул конструктор
AudioContext и нашёл два скрытых аудиоконтекста в состоянии running, подключённых к destination, — при том что ни <audio>, ни <video>, ни вызовов play() на странице нет. Создают их collina.js и fireyejs.js из каталога AWSC, антифрод-обвязки Alibaba.
Граф в обоих одинаковый: пилообразный осциллятор, AnalyserNode, ScriptProcessorNode, GainNode с нулевым усилением, выход в destination. Слышно ничего, но подключение к destination заставляет браузер обсчитывать граф по-настоящему, и аудиопуть остаётся занятым.
Звук здесь одна из мерок отпечатка. Рядом снимаются canvas и toDataURL(), данные WebGL, размеры экрана, hardwareConcurrency, поведение WebRTC, события мыши и скролла, показания акселерометра.
@prog_stuffИдея, которую в девяностых довели до рабочего состояния, а потом почти все забыли: файловая система, которая одновременно является базой данных.
Даниэль Козенца разбирает Be File System — ту, что досталась Haiku от BeOS. У файла там есть не только имя и содержимое, но и типизированные именованные атрибуты: у аудиофайла исполнитель и альбом, у письма отправитель и статус. По выбранным атрибутам строятся индексы, к тому можно писать предикаты, а живые запросы шлют приложению сообщение, когда подходящий файл появился, исчез или изменился. Почтовый клиент, который просто показывает результат запроса к файловой системе, — это оттуда.
Автор при этом честно очерчивает границы. Индекс принадлежит конкретному тому: наличие его на загрузочном диске ничего не говорит про соседний. Результат запроса не вечен — файл могут переименовать или удалить. Соединений таблиц, ссылочной целостности и транзакций над несколькими записями тут нет.
И главная ловушка: при переименовании атрибуты следуют за файлом, а вот при копировании наружу как повезёт — архиваторы и средства переноса ведут себя по-разному, и на файловой системе без поддержки атрибутов они просто теряются. Байты остались, смысл потерялся.
@prog_stuff
Час, который стоит потратить: доклад Рича Хики «Simple Made Easy» со Strange Loop 2011.
Весь доклад держится на разведении двух слов, которые в русском тоже слиплись. Простое — это то, что не переплетено с другими вещами, свойство самой конструкции. Лёгкое — это то, что близко и знакомо лично вам, свойство вашего опыта. Выбирая лёгкое, команда набирает сложность, которую потом невозможно распутать.
Хики вводит слово complecting — сплетать вместе то, что могло бы жить раздельно. Изменяемая переменная сплетает значение и время. Наследование сплетает тип и реализацию. Объект сплетает данные и поведение. Всё это удобно писать и тяжело менять через полгода.
Отдельный удар по привычным успокоительным: тесты, рефакторинг и система типов повышают безопасность, но не делают дизайн проще. Они ловят ошибки, а не распутывают связи.
Практическое, что можно унести на завтра: при проектировании развести вопросы «что», «кто», «как», «когда», «где» и «почему» и следить, чтобы в одном месте не отвечали сразу на несколько.
@prog_stuff
