Издательский дом ООО "Гейм Лэнд"СПЕЦВЫПУСК ЖУРНАЛА ХАКЕР #71, ОКТЯБРЬ 2006 г.

утечка мозга

КРИС КАСПЕРСКИ АКА МЫЩЪХ

Спецвыпуск: Хакер, номер #071, стр. 071-020-6


#define stack_alloc(p,n,total) { __asm{sub esp,n};\

__asm{mov dword ptr ds:[p],esp};\

total += n;}

#define stack_free(total) {__asm{add esp,total};}

А вот пример использования макросов stack_alloc и stack_free:

foo()

{

char* p; int n;

int total = 0;

n = 0x100;

stack_alloc(p, n, total);

strcpy(p,"hello, world!\n");printf(p);

stack_free(total);

}

Естественно, о вызове stack_free программист может забыть (и ведь наверняка забудет!), поэтому лучше выделять память так, чтобы при выходе из функции она освобождалась автоматически.

Ниже приведен исходный текст макроса auto_alloc, который именно так и работает:

#define auto_alloc(p,n) {__asm{add n,20h};\

__asm{mov eax,esp};\

__asm{sub esp,n};\

__asm{mov p,esp};\

__asm{push 20h};\

__asm{push eax};\

__asm{mov eax,p};\

__asm{push eax};\

__asm{call memcpy};\

__asm{add esp,0Ch};\

__asm{add p,20h};}

Как его можно использовать на практике? А хотя бы вот так:

foo()

{

char* p; int n; n = 0x100;

auto_alloc(p, n);

strcpy(p,"hello, world!\n");printf(p);

}

При работе со стековой памятью следует учитывать три обстоятельства. Во-первых, по умолчанию каждый поток получает всего лишь 1 Мбайт стековой памяти, что по современным понятиям очень мало. Стековую память первичного потока легко увеличить, передав линкеру ключ «/STACK:reserve[,commit]», где reserve – зарезервированная, а commit – выделенная память. Размер стековой памяти остальных потоков определяется значением аргумента dwStackSize функции CreateThread.

Во-вторых, при старте потока Windows выделяет ему минимум страниц стековой памяти, размещая за ними специальную «сторожевую» страницу (PAGE GUARD) при обращении к которой возбуждается исключение, отлавливаемое системой, которая выделяет потоку еще несколько страниц, перемещая PAGE GUARD наверх. Если же мы попытаемся обратиться к памяти, лежащей за PAGE GUARD — произойдет крах. Поэтому, при ручном выделении стековой памяти, необходимо последовательно обратиться хотя бы к одной ячейке каждой страницы: #define stack_out(p,n) for(a=0;a<n;a+=0x100)t=p[a];

В-третьих, размер выделяемых блоков памяти должен быть кратен четырем, иначе многие API и библиотечные функции откажут в работе.

Но что делать, если, несмотря на все усилия, память продолжает утекать? На этот случай у мыщъх'а припасено несколько грязных, но довольно эффективных трюков. Вот один из них: когда количество потребляемой приложением памяти достигает некоторой, заранее заданной отметки, мы «прогуливаемся по куче» API-функцией HeapWalk, сохраняя все выделенные страницы в специальный файл (устроенный по принципу файла подкачки) и возвращаем память системе, оставляя страницы зарезервированными и назначая им атрибут PAGE_NOACCESS. После чего нам остается только отлавливать исключения и подгружать содержимое реально используемых страниц, восстанавливая оригинальные атрибуты доступа (PAGE_READ или PARE_READWRITE). В результате, утекать будет только адресное пространство, которое, между прочим, не бесконечно, и при интенсивной течи довольно быстро кончается. И что же тогда? Можно, конечно, просто завершить программу, но лучше рискнуть и попробовать освободить блоки, к которым дольше всего не было обращений. Разумеется, мы не можем гарантировать, что именно они ответственны за утечку памяти. Быть может, программа в самом начале выделила несколько блоков, планируя обратиться к ним при завершении процесса, но... риск благородное дело!

Назад на стр. 071-020-5  Содержание  Вперед на стр. 071-020-7