- PVSM.RU - https://www.pvsm.ru -

(типичное утро понедельника)
Однажды, я решил сделать маленький костылик, чтобы не пичкать свой код free вызовами. Было решено написать аналог auto-release pool’a для чистого С (который может далее быть портирован куда угодно).
Главная идея – освободится от вызовах free в блоках и проверять свой код на недостаточные free. (как auto для heap участков)
Требования
По факту получается, что это что-то похожее на то, что используют ОС для работы с памятью [1],
но без сложных механизмов организации достаточной памяти. Такой себе маленький аналог.
По структуре схема будет сходная с вектором в С++, т.е. выделяем кусок памяти, а потом по требованию увеличиваем его в N-раз. Быстро по доступу, но не так быстро по добавлению новых элементов (хотя для разных N можно довольно гибко это регулировать)
В Си существует 4 функции для ручного управления памятью.
Их нужно либо перехватить (что будет работать для всего кода, и библиотечного тоже по идее), либо сделать так, чтобы вместо вызовов этих четырех функций в собственном коде неявно вызывалось что-то другое, но неявно.
Auto-release пул очень похож на песочницу, но дело в том, что обычно песочницы выделяют память очень большим куском в пару сотен мегабайт или пару гигабайт и, далее, форсирует все вызовы malloc/free в этом выделенном участке. И очищается этот кусок целиком, в конце рабочей программы, а не по вызову free. (вероятно, поэтому на mac os можно иногда перехватить данные после вызова free в с-коде)

Рис 1 Иерархические песочницы

Рис 2 Auto-release (ленивый free) pool
А auto-release pool по факту – это всего лишь динамический массив, который сохраняет указатели с хукованого или замененного malloc’a и удаляет их, по вызову free. Таким нехитрым способом можно как и защитится от утечек памяти путем тестов исполняемой программы, так и стать более ленивым программистом на С, просто не вызывая мн-ва free, а вызывая аналог [pool drain].
Во многих ОС реализована песочница, но будет интересная только одна. Это песочница из OpenBSD [2]. Ставка OpenBSD сделана во многом на безопасность, аудит кода, и всякие новые фичи. Вот по безопасности у них есть очень интересный но тривиальный механизм: выделяемая в куче память занимает относительно случайные места. Т.е. из выделенной песочницы в 65 МБ допустим мы выделяем строки по 80 байт и они выделяются не с начала песочницы, а в совершенно разных местах.

Рис 3 Rand-песочница
Таким образом, т.к. кусок памяти цельный, мы не может перераспределить память, в случае недостатка, т.к. нативный realloc не гарантирует того, что данные не переедут в другое место. Отсюда следует, что таким образом система просто похерит все рабочие указатели программиста и ПО перестанет работать. Выход – только сделать еще одну песочницу. Но это уже совсем другая история.
Auto-release пул проще, потому что он хранит только указатели, которые возвращались нативным malloc-ом (да, да, молоком). И таким образом этот динамический список или массив можно переносить и перераспределять сколько угодно раз, т.к. он не затрагивает реальную память.
Выше были упомянуты два способа взаимодействия с malloc-free функциям, а именно методы их перехватов и далее будет рассмотрен второй метод. Это текстовая подстановка.
Строка #define malloc <что-то там> сделает все вызовы malloc»а чем то. Например не константным указателем на функцию той же сигнатуры.
void* (*RMallocPtr) (size_t size);
void* (*RCallocPtr) (size_t size, size_t blockSize);
void* (*RReallocPtr)(void* ptr, size_t size);
void (*RFreePtr) (void* ptr);
#define malloc RMallocPtr
#define realloc RReallocPtr
#define calloc RCallocPtr
#define free RFreePtr
Перед этим необходимо сохранить указатели на библиотечные функции, т.к. malloc уже не будет доступен. Т.е.
// константные указатели на stdlib (OS) функции
static void* (*const RTrueMalloc) (size_t size) = malloc;
static void* (*const RTrueRealloc)(void* ptr, size_t size) = realloc;
static void* (*const RTrueCalloc) (size_t size, size_t blockSize) = calloc;
static void (*const RTrueFree) (void* ptr) = free;
Теперь malloc-free функции можно заменять на свои и тестировать свой код сколько угодно. На такой же основе можно построить реализацию внутренних песочниц или auto-release пулов.
Для С-кода реализована песочница трех типов standart, rand и delegated. Т.е. обычного типа (память за памятью), случайного типа рассмотренного выше и делегированного, чтобы можно было сделать абсолютно свою реализацию. Все исходники открыты по ссылке [3].
А для пользователя это выглядит очень просто:
createSandBoxSingleton(someSandBox, 65535) // имя и размер песочницы в байтах
int main(int argc, const char *argv[]) {
size_t iterator;
initPointers();
switchToSandBox(someSandBox()); // сдесь начинается песочница
forAll(iterator, 20) {
RCString *temp = randomRCString(); // много раз динамически выделяем память
}
// нет вызовов free
$(someSandBox(), p(RSandBox)));
deleter(someSandBox(), RSandBox); // автоматически отключается и очищает все
return 0;
}
Иерархические песочницы также возможны.
Спасибо за внимание. Приятного утра понедельника.
Автор: StrangerInRed
Источник [4]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/bezopasnost/75927
Ссылки в тексте:
[1] ОС для работы с памятью: https://ru.wikipedia.org/wiki/%D0%9C%D0%B5%D0%BD%D0%B5%D0%B4%D0%B6%D0%B5%D1%80_%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8
[2] OpenBSD: https://ru.wikipedia.org/wiki/OpenBSD
[3] ссылке: https://github.com/kojiba/RayLanguage/tree/master/Classes/RayFoundation/RMemoryOperations
[4] Источник: http://habrahabr.ru/post/244617/
Нажмите здесь для печати.