утечка мозга КРИС КАСПЕРСКИ АКА МЫЩЪХ Спецвыпуск: Хакер, номер #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). В результате, утекать будет только адресное пространство, которое, между прочим, не бесконечно, и при интенсивной течи довольно быстро кончается. И что же тогда? Можно, конечно, просто завершить программу, но лучше рискнуть и попробовать освободить блоки, к которым дольше всего не было обращений. Разумеется, мы не можем гарантировать, что именно они ответственны за утечку памяти. Быть может, программа в самом начале выделила несколько блоков, планируя обратиться к ним при завершении процесса, но... риск благородное дело! |