ANYKS
EN
в разработкечасть AWH · 21 355 строк

awh::alloc

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

01 Зачем

Четыре нужды, которых системный распределитель не закрывает

Учёт расхода

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

Отдача памяти

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

Разбор сбоя

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

Защита тайн

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

Всё это — вызовами, а не переменными окружения: приложение задаёт режим само и меняет его в работе.

02 Захват

Три приёма захвата, по одному на семейство систем

ПриёмСистемыКак работает
Подмена именамиLinux, все BSD, Solaris, illumosнаш файл определяет malloc, и связыватель ставит его на место системного — захват состоялся до первой строки main
Имена и зонаmacOSподмена имён уводит к нам обращения своего образа, а зона malloc_zone_t ловит выданное внутри libsystem
Переписывание входаWindowsвход функций ucrtbase переписывается переходом на наш; выровненная выдача перехвачена отдельно

Доказано решающим наблюдением: память, выданную внутри стандартной библиотеки (например std::string), щуп находит в нашей области. Пул, подключённый к своему коду, такого не даёт.

03 Устройство

Пять слоёв и возможности сбоку

СлойЧто делает
Захватmalloc, free, calloc, realloc, выровненная выдача
Cacheкэш потока: свой у потока, замка не берёт вовсе
Centralцентральные списки, замок у разряда — обмен пачками
Pagesстраничная куча — обмен областями
Sourceисточник страниц у системы, подменяемый потребителем
  • 62 разряда от 16 байт до 32 768, потеря на округлении не более восьмой доли блока.
  • Страница кучи 8 КБ, кусок 4 МБ, выровненный по своему размеру: начало куска находится одной маской.
  • Связь свободных блоков — в самих блоках. Указатели перемешиваются зерном процесса: переписав свободный блок, злоумышленник не получит управляемого указателя.
  • Быстрый путь — десяток команд без единого замка: кэш потока, разряд таблицей без деления, блок с головы списка, учёт у потока.

Сбоку от лестницы: Huge — крупные выдачи, Guard — заслоны, Profile — учёт мест выдачи, Trace — разбор адреса сбоя.

04 Возможности

Чего у системного распределителя нет вовсе

  • Разбор адреса сбоя resolve() — нулевая страница, чужая память, живой блок, освобождённый, за концом блока, перед началом.
  • Заслоны guardRate — выборочные блоки за закрытыми страницами: выход за границу даёт сбой на месте. Своя ASan, работающая в рабочей сборке.
  • Ловля записи по освобождённому — карантин держит освобождённое засеянным; spoiled() называет число испорченных блоков, адрес и смещение.
  • Учёт мест выдачи profileRate со съёмом стека — поиск утечек в работающей программе.
  • Склад тайн secure() — память, укрытая от снимка, запертая от подкачки и затираемая при возврате. Что состоялось на деле, говорит shelter_t.
  • Порядок отдачи памяти — не отдавать, по просьбе, по порогу при освобождении, потоком обслуживания по отсрочке.
  • Опрос расхода — шесть величин: занято, пик, взято у системы, свободно в кэшах, в куче, отдано.
  • Потолок heapLimit сторожит всё взятое у системы, а не одну кучу.
alloc::options_t options;
options.purge     = alloc::purge_t::ONFREE;    // не отдавать · по просьбе · по порогу · потоком
options.heapLimit = 2ull * 1024 * 1024 * 1024; // потолок всего взятого у системы
options.hugeCache = 16 * 1024 * 1024;          // придержка крупных областей
options.guardRate = 1000;                      // один блок из тысячи — за закрытыми страницами
if(!alloc::Allocator::capture(options)) /* захват не состоялся */;
05 Замеры

Размеры разнородны — так работает настоящее приложение

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

СистемаAWHсистемныйjemalloctcmalloc
macOS ARM642,8111,2710,73—
Debian x86-6410,0422,2442,568,45
DragonFly BSD11,0926,90650,109,16
Alpine (musl)11,191 116,0750,988,61
NetBSD11,3134,9635,06—
FreeBSD11,8440,31(он же системный)9,02
OpenBSD21,922 563,45——

jemalloc обойдён везде

От 3,1 до 3,9 раза на смешанной нагрузке, на DragonFly — в 58 раз.

tcmalloc впереди на 7–15 %

Он кладёт разряд в заголовок блока; мы опознаём блок по адресу — это и даёт разбор адреса сбоя.

Ровность — 22 %

Разброс по тринадцати системам 10,1–12,4 нс, у соперников — на порядки.

06 Две правки

Рост на месте и придержка крупных областей

Рост перевыдачей на месте

Область растёт за счёт соседних свободных страниц, адрес не меняется. Цепочка удвоений 16 байт → 256 КБ: было 14 750 мкс, стало 3 259 при 2 634 у системного.

Придержка крупных

Свыше 4 МБ цена лежит в первом касании страниц. Освобождённая область придерживается под потолком hugeCache: «выдать и заполнить» 5 МБ — 15 761 → 108 799 МБ/с при 110 000 у системного.

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

07 Проверено

Восемь систем, ворошитель и санитайзеры

СистемаНаборTSan
macOS 2663/64чисто
Debian 12 · Alpine · FreeBSD 1549/49чисто
NetBSD 10.149/49—
OpenBSD 7.9 · Solaris 11.448/49—
Windows 11 ARM64прогнан—
Эльбрус-8С2 (lcc)перенос идёт—

Покрытие строк модуля — 79,65 %, и непокрытое разобрано по причинам: пути снятия распределителя внутри процесса непроверяемы, пути ветвления проверяются только на системах ELF, защитные ветви недостижимы по устройству.

Пропуски — записанные свойства систем: у OpenBSD нет syscall(2), у Sun прямое обращение к нему не поддерживается.

Под санитайзерами захват не состоится, и набор это утверждает: слои проверяются под ними напрямую.