Авторские статьи Выявление ошибок памяти

Discussion in 'Статьи' started by _Great_, 7 Apr 2007.

  1. _Great_

    _Great_ Elder - Старейшина

    Joined:
    27 Dec 2005
    Messages:
    2,032
    Likes Received:
    1,119
    Reputations:
    1,139
    Article: Выявление ошибок памяти
    Author: Great
    Date: 06.04.2007
    Lang: C/C++ user mode
    Note: В статье описаны действенные методы для выявления таких ошибок в программах, как переполнение буфера или утечек памяти.

    Переполнение буфера - очень частая ошибка программистов, особенно начинающих - попытка записи в буфер за его пределами. Конечно, мы не сможем контролировать переполнение локального буфера, но мы сможем контролировать все операции с динамическими буферами. Ошибки такого типа трудноуловимы, потому что результат ошибки проявляется не немедленно, а перед ним может пройти значительное время. Например, программа выделяет два буфера - строку и управляющую структуру. Потом строка случайно перезаписыватся так, что происходит ее переполнение и затирается часть идущей за ней структуры, т.к. буфера выделяются в куче непосредственно друг за другом. Но эта ошибка проявится только при следующем использовании структуры. Таким образом, было бы неплохо отловить то место, когда происходит выход за границы буфера. Кстати, возможна и обратная ситуация: в результате неправильного вычисления индекса массива происходит обращение к элементу, находящемуся до начала массива. Такие ошибки менее часто встречаются, но их так же трудно отловить.
    Существует один простой метод для "отлова" ошибок такого рода:
    а) для обнаружения переполнений все буфера в программе выделяются особым образом: резервируется идущих подряд страницы виртуальной памяти, дальше первая из них передается (commit) в использование и буфер размещается на границе страниц в конце первой. Следующая страница недействительная. Любая попытка выхода за пределы буфера повлечет немедленно исключение нарушения доступа:
    [​IMG]
    Для большей надежности оставшаяся часть страницы заполняется шаблоном, который потом проверяется на целостность.
    Примерный код для аллокации и освобождения (откомментирован):
    Code:
    #define OVERFLOW_GUARD   1
    #define UNDERFLOW_GUARD  2
    
    #define BUFFER_GUARD OVERFLOW_GUARD
    //#define BUFFER_GUARD UNDERFLOW_GUARD
    
    //#define SIMULATE_MEMORY_LACK  1
    #define NUMBERS_OF_MEMORY_REQUESTS_TO_FAIL_AT 2
    
    struct __allocation {
    	void* mem;
    	int len;
    	char guard_type;
    } __allocs [1024];
    
    // new[] operator handler for the overflow-guard protection mode
    // Allocates buffer at the end of the page, next page will be marked as invalid.
    // Any access behind the end of the buffer will be failed
    void* AllocateOverflowGuardedBuffer(int size)
    {
    	SYSTEM_INFO si = {0};
    	GetSystemInfo( &si );
    	if( size > (signed)si.dwPageSize ) return NULL;  // can't allocate buffer greater than one page now...
    
    	void* mem = VirtualAlloc( NULL, si.dwPageSize*2, MEM_RESERVE, PAGE_NOACCESS );  // reserve pages
    	if( !mem ) return NULL;
    	mem = VirtualAlloc( mem, si.dwPageSize, MEM_COMMIT, PAGE_READWRITE );  // commit first page
    
    	// fill template
    	DWORD ps = si.dwPageSize - size; 
    	__asm
    	{
    		mov al, 0xfd
    		mov ecx, ps
    		mov edi, mem
    		rep stosb
    	}
    
    	mem = (LPVOID)( (DWORD)mem + si.dwPageSize - size );
    
    	// save info about allocation
    	for( int i=0; i<sizeof(__allocs)/sizeof(*__allocs); i++ )
    		if( !__allocs[i].mem )
    		{
    			__allocs[i].guard_type = OVERFLOW_GUARD;
    			__allocs[i].len = size;
    			return (__allocs[i].mem = mem);
    		}
    	
    	// not enough memory to save allocation info, free pages and exit with error
    	VirtualFree( mem, si.dwPageSize,  MEM_DECOMMIT );
    	VirtualFree( mem, si.dwPageSize*2, MEM_RELEASE );
    	return NULL;
    }
    
    // delete[] operator handler for the overflow-guard protection mode
    void FreeOverflowGuardedBuffer(void* mem)
    {
    	SYSTEM_INFO si = {0};
    	GetSystemInfo( &si );
    	
    	// find our allocation in allocation table
    	for( int i=0; i<sizeof(__allocs)/sizeof(*__allocs); i++ )
    		if( __allocs[i].mem == mem )
    		{
    			if( __allocs[i].len == -1 )  // special value indicating freed buffer. don't clear entry in table.
    			{
    				DebugMessage(
    					"Attempt to free already freed buffer 0x%08x.\n"
    					"Possible it's a result of incorrect destructor call."
    					,
    					mem
    					);
    				return;
    			}
    
    			// check pattern
    			long ps = si.dwPageSize - __allocs[i].len;
    			LPVOID addr = (LPVOID)( (DWORD)mem + __allocs[i].len - si.dwPageSize );
    
    			for( int j=0; j < ps; j++ )
    				if( ((unsigned char*)addr)[j] != 0xfd )
    					break;
    			ps -= j;
    			
    			// pattern mismatch
    			if( ps )
    			{
    				DebugMessage(
    					"Buffer underflow detected while overflow-guard protection at address 0x%08x.\n"
    					"It's highly recommended to test program in underflow-guard mode to detect faulting instruction."
    					,
    					mem
    					);
    			}
    
    			// free buffer and return
    			VirtualFree( mem, si.dwPageSize,  MEM_DECOMMIT );
    			VirtualFree( mem, si.dwPageSize*2, MEM_RELEASE );
    
    			__allocs[i].len = -1;
    			return;
    		}
    }
    Функция DebugMessage выводит диалоговое окно с предложениями завершить программу, отладить программу или проигнорировать сообщение. Ее объявление выглядит так: void DebugMessage(char* s, ...), а ее тело можно найти в исходнике к статье.

    б) для обнаружения выхода за границы буфера с другой стороны ("недополнение" буфера) аналогичная методика: резервируются две страницы, вторая из них передается и буфер выделяется в нее начале, предыдущая страница недействительная. Попытки записи перед буфером повлекут исключение нарушения доступа. Оставшаяся часть страницы снова заполняется шаблоном, который при освобождении проверяется на целостность. Код выглядит похоже на предыдущий:
    Code:
    // new[] operator handler for the underflow-guard protection mode
    // Allocates buffer at the beginning of the page, previous page will be marked as invalid.
    // Any access before the beginning of the buffer will be failed
    void* AllocateUnderflowGuardedBuffer(int size)
    {
    	SYSTEM_INFO si = {0};
    	GetSystemInfo( &si );
    	if( size > (signed)si.dwPageSize ) return NULL;
    
    	void* mem = VirtualAlloc( NULL, si.dwPageSize*2, MEM_RESERVE, PAGE_NOACCESS );
    	if( !mem ) return NULL;
    	mem = (LPVOID)( (DWORD)mem + si.dwPageSize );
    	mem  = VirtualAlloc( mem, si.dwPageSize, MEM_COMMIT, PAGE_READWRITE );
    
    	DWORD ps = si.dwPageSize - size;
    	LPVOID addr = (LPVOID)( (DWORD)mem + size );
    	__asm
    	{
    		mov al, 0xfd
    		mov ecx, ps
    		mov edi, addr
    		rep stosb
    	}
    
    	for( int i=0; i<sizeof(__allocs)/sizeof(*__allocs); i++ )
    		if( !__allocs[i].mem )
    		{
    			__allocs[i].guard_type = UNDERFLOW_GUARD;
    			__allocs[i].len = size;
    			return (__allocs[i].mem = mem);
    		}
    
    	VirtualFree( (LPVOID)( (DWORD)mem + si.dwPageSize ), si.dwPageSize, MEM_DECOMMIT );
    	VirtualFree( mem, si.dwPageSize*2, MEM_RELEASE );
    	return NULL;
    }
    
    // delete[] operator handler for the underflow-guard protection mode
    // Checks memory for overflowing too. But complete debugging of buffer overflow should be performed in
    // overflow-guard mode
    void FreeUnderflowGuardedBuffer(void* mem)
    {
    	SYSTEM_INFO si = {0};
    	GetSystemInfo( &si );
    
    	for( int i=0; i<sizeof(__allocs)/sizeof(*__allocs); i++ )
    		if( __allocs[i].mem == mem )
    		{
    			if( __allocs[i].len == -1 )
    			{
    				DebugMessage(
    					"Attempt to free already freed buffer 0x%08x.\n"
    					"Possible it's a result of incorrect destructor call."
    					,
    					mem
    					);
    				return;
    			}
    			long ps = si.dwPageSize - __allocs[i].len;
    			LPVOID addr = (LPVOID)( (DWORD)mem + __allocs[i].len );
    
    			for( int j=0; j < ps; j++ )
    				if( ((unsigned char*)addr)[j] != 0xfd )
    					break;
    			ps -= j;
    			
    			if( ps )
    			{
    				DebugMessage(
    					"Buffer overflow detected while underflow-guard protection at address 0x%08x.\n"
    					"It's highly recommended to test program in overflow-guard mode to detect faulting instruction."
    					,
    					mem
    					);
    			}
    
    			VirtualFree( (LPVOID)( (DWORD)mem + si.dwPageSize ), si.dwPageSize, MEM_DECOMMIT );
    			VirtualFree( mem, si.dwPageSize*2, MEM_RELEASE );
    
    			__allocs[i].len = -1;
    
    			return;
    		}
    } 
    Чтобы отловить нарушение доступа, установим свой обработчик и предупредим программиста о возникшей ошибке:
    Code:
    // exception handler
    LONG __stdcall MyUnhandledExceptionFilter(EXCEPTION_POINTERS* ep)
    {
    	if( ep->ExceptionRecord->ExceptionCode == STATUS_ACCESS_VIOLATION )
    	{
    		for( int i=0; i<sizeof(__allocs)/sizeof(*__allocs); i++ )
    		{
    			long addr = ep->ExceptionRecord->ExceptionInformation[1];
    			long dist;
    
    			if( 
    				(addr >= (long)__allocs[i].mem + __allocs[i].len) &&
    				(addr <= (long)__allocs[i].mem + __allocs[i].len + 10) &&
    				__allocs[i].guard_type == OVERFLOW_GUARD
    				)
    			{
    				dist = addr - (long)__allocs[i].mem - __allocs[i].len + 1;
    				DebugMessage(
    					"Buffer overflow detected!\n"
    					"Buffer starts at 0x%08x\n"
    					"Buffer size is %d byte(s) [0x%x]\n"
    					"Memory referenced: 0x%08x\n"
    					"Distance between end of buffer: %d byte(s) [0x%x]\n"
    					"%s"
    					,
    					__allocs[i].mem,
    					__allocs[i].len,
    					__allocs[i].len,
    					addr,
    					dist,
    					dist,
    					(dist==1) ?
    						"\nDistance equals to one byte.\n"
    						"It's very common case of overflow called one-byte buffer oveflow.\n"
    						"Possible it is a result of string allocation error (maybe you've forgotten to allocate space for terminating NULL-character?)"
    						: ""
    					);
    			}
    			else if( 
    				(addr >= (long)__allocs[i].mem - 10) &&
    				(addr <= (long)__allocs[i].mem) &&
    				__allocs[i].guard_type == UNDERFLOW_GUARD
    				)
    			{
    				dist = (long)__allocs[i].mem - addr;
    				DebugMessage(
    					"Buffer underflow detected!\n"
    					"Buffer starts at 0x%08x\n"
    					"Buffer size is %d byte(s) [0x%x]\n"
    					"Memory referenced: 0x%08x\n"
    					"Distance between beginning of buffer: %d byte(s) [0x%x]\n"
    					,
    					__allocs[i].mem,
    					__allocs[i].len,
    					__allocs[i].len,
    					addr,
    					dist,
    					dist
    					);
    			}
    		}
    	}
    	return EXCEPTION_CONTINUE_SEARCH;
    		
    }
    Будем считать переполнением тут любое обращение в пределах 10 байт от границы буфера и будем показывать окошки с сообщениями. Обращения после 10 байт будут просто вызывать Access Violation. В любом случае вернем EXCEPTION_CONTINUE_SEARCH, т.к. мы не предпринимали никаких действий, а только предупредили об ошибке. Управление скорее всего получит системный обработчик, который выдаст до боли знакомое сообщение "программа выполнила недопустимую операцию..." или отладчик, у которого будет второй шанс обработать исключение (second-chance exception) и он остановится на сбойной команде (первый шанс обработать исключение (first-chance exception) был при самом его возникновении).
    Устанавливать обработчик и выбирать способ аллокации мы будем в нашем переопределенном операторе new:
    Code:
    // operator new
    void* operator new(size_t s)
    {
    	static bool except_handler_set = false;
    
    	if( !except_handler_set )
    	{
    		except_handler_set = true;
    		SetUnhandledExceptionFilter( MyUnhandledExceptionFilter );
    	}
    
    #if SIMULATE_MEMORY_LACK
    	srand(GetTickCount());
    	if( rand() % NUMBERS_OF_MEMORY_REQUESTS_TO_FAIL_AT == 0 )
    		return NULL;
    #endif
    
    #if BUFFER_GUARD == OVERFLOW_GUARD
    	return AllocateOverflowGuardedBuffer( s );
    
    #elif BUFFER_GUARD == UNDERFLOW_GUARD
    	return AllocateUnderflowGuardedBuffer( s );
    
    #else
    #	error Unknown BUFFER_GUARD value
    
    #endif
    }
    
    // operator delete
    void operator delete(void* p)
    {
    #if BUFFER_GUARD == OVERFLOW_GUARD
    	FreeOverflowGuardedBuffer( p );
    
    #elif BUFFER_GUARD == UNDERFLOW_GUARD
    	FreeUnderflowGuardedBuffer( p );
    
    #else
    #	error Unknown BUFFER_GUARD value
    
    #endif
    }
    Как можно заметить, мы еще случайно будем отклонять запросы на выделение памяти для тестирования того, как себя будет вести программа в этом случае. Это все задается опционально. Для расширения возможностей отладки можно перехватить и функции выделения памяти в куче: HeapAlloc, LocalAlloc, GlobalAlloc.

    Для "отловки" утечек памяти будем после критического региона, работающего с памятью, проверять, освободил ли он всё, что выделял. Для этого нам потребуются две функции: EnterMemoryCheckRegion() и LeaveMemoryCheckRegion(), код которых выглядит следующим образом:
    Code:
    // Clear allocation log __allocs
    void EnterMemoryCheckRegion()
    {
    	void* p = __allocs;
    	int l = sizeof(__allocs) / 4;
    
    	__asm
    	{
    		mov edi, p
    		xor eax, eax
    		mov ecx, l
    		rep stosd
    	}
    }
    
    // Check for memory leak. Should be called at the end of guarded region of code
    void LeaveMemoryCheckRegion()
    {
    	for( int i=0; i<sizeof(__allocs)/sizeof(*__allocs); i++ )
    	{
    		if( __allocs[i].mem && __allocs[i].len != -1 )
    		{
    			DebugMessage(
    				"Possible memory leak detected. "
    				"Buffer at address 0x%08x is not freed at the end of the memory-check region."
    				,
    				__allocs[i].mem
    				);
    		}
    	}
    }
    
    Вызовами этих функций можно обрамлять участки кода, которые не должны оставлять за собой выделенные участки памяти.
    В качестве примера рассмотрим программу, в которой присутствуют несколько ошибок памяти:
    Code:
    // Test class
    class A
    {
    public:
    	A(int c)
    	{
    		printf("!! CONSTRUCTOR [%d] !!\n", c);
    	}
    };
    
    int main()
    {
    	// Clear log
    	EnterMemoryCheckRegion();
    
    	// Create dynamic object
    	printf("Creating class object\n");
    	A *b = new A (4);
    	if( !b )
    		return printf("Allocation failed (not enough memory?)\n"), 0;
    
    	// Allocate dynamic buffer
    	printf("Allocating\n");
    	char *a = new char[10];
    	if( !a )
    		return printf("Allocation failed (not enough memory?)\n"), 0;
    
    	// Valid copying
    	printf("Valid copying\n");
    	strcpy(a, "123456789");
    	printf("Copied, value: '%s'\n", a);
    
    	// Invalid copying
    	printf("Invalid copying\n");
    	strcpy(a-1, "1234567890");
    	printf("Copied, value: '%s'\n", a);
    
    	printf("Deleting\n");
    
    	// Now the overflow will be detected
    	delete a;
    
    	// Second freeing of freed memory
    	delete a;
    
    	// no delete call for the A* b. Memory leak
    	// It will be detected now
    	LeaveMemoryCheckRegion();
    
    	return 0;
    }
    При включенной защите от переполнения будет обнаружено:
    - "недополнение" буфера (запись перед его началом);
    - попытка освобождения уже освобожденной памяти;
    - утечка памяти (указатель A* b не был освобожден перед выходом)
    Если заменить строчку strcpy(a-1, "1234567890") на strcpy(a, "1234567890"), то произойдет попытка записи 11 байт (10 символов + завершающий ноль) в 10байтный буфер, что будет немедленно обнаружено:
    [​IMG]
    Как видно, программа заботливо сообщает нам о однобайтовом переполнении в динамической памяти.
    К сожалению, это не спасает нас от переполнения локальных буферов в стеке, но это уже отдельная тема. А здесь было рассмотрено обнаружение ошибок манипуляции с динамической памятью. На этом, пожалуй, я и закончу. Пока

    Полный исходный код: http://gr8.cih.ms/uploads/dynamic_memory.cpp
     
    11 people like this.
  2. ZaCo

    ZaCo Banned

    Joined:
    20 Jun 2005
    Messages:
    737
    Likes Received:
    336
    Reputations:
    215
    поскольку на практике работа с памятью ограничивается стандартными функциями, будет все же проще перегружать стандартные функции работы с памятью, под юникс тут может пригодится библиотека типа dmalloc. но идея использования самого процессора в качестве детектора переполнений хорошая, вот только на каждый new делать VirtualAlloc() расточительно..
     
    1 person likes this.
  3. _Great_

    _Great_ Elder - Старейшина

    Joined:
    27 Dec 2005
    Messages:
    2,032
    Likes Received:
    1,119
    Reputations:
    1,139
    Расточительно, конечно, в связи с чем аллокации можно комбинировать. Например, страницы выделяются через одну, на каждой валидной странице вначале расположен буффер с защитой от "недополнений", а в конце - с защитой от переполнений. ВОобще идея классная, я буду ее развивать далее.
     
    1 person likes this.
  4. gribodemon

    gribodemon New Member

    Joined:
    7 Jun 2009
    Messages:
    0
    Likes Received:
    2
    Reputations:
    4
    Я немного модифицировал код, чтобы можно было выделять больше 0x1000 байт под динамический буфер.

    Code:
    
    #include <stdio.h>
    
    #define STATUS_ACCESS_VIOLATION          ((NTSTATUS)0xC0000005L)    // winnt
    
    #define OVERFLOW_GUARD   1
    
    #define BUFFER_GUARD OVERFLOW_GUARD
    
    //#define SIMULATE_MEMORY_LACK  1
    #define NUMBERS_OF_MEMORY_REQUESTS_TO_FAIL_AT 2
    
    #define ALLOC_SIZE 10000
    
    struct __allocation {
    	void* mem;
    	int len;
    	char guard_type;
    } __allocs [ALLOC_SIZE], __allocs_bck [ALLOC_SIZE];
    
    void DebugMessage(char* s, ...);
    
    // exception handler
    LONG __stdcall MyUnhandledExceptionFilter(EXCEPTION_POINTERS* ep)
    {
    	if( ep->ExceptionRecord->ExceptionCode == STATUS_ACCESS_VIOLATION )
    	{
    		for( int i=0; i<sizeof(__allocs)/sizeof(*__allocs); i++ )
    		{
    			long addr = ep->ExceptionRecord->ExceptionInformation[1];
    			long dist;
    
    			if( 
    				(addr >= (long)__allocs[i].mem + __allocs[i].len) &&
    				(addr <= (long)__allocs[i].mem + __allocs[i].len + 10) &&
    				__allocs[i].guard_type == OVERFLOW_GUARD
    				)
    			{
    				dist = addr - (long)__allocs[i].mem - __allocs[i].len + 1;
    				DebugMessage(
    					"Buffer overflow detected!\n"
    					"Buffer starts at 0x%08x\n"
    					"Buffer size is %d byte(s) [0x%x]\n"
    					"Memory referenced: 0x%08x\n"
    					"Distance between end of buffer: %d byte(s) [0x%x]\n"
    					"%s"
    					,
    					__allocs[i].mem,
    					__allocs[i].len,
    					__allocs[i].len,
    					addr,
    					dist,
    					dist,
    					(dist==1) ?
    						"\nDistance equals to one byte.\n"
    						"It's very common case of overflow called one-byte buffer oveflow.\n"
    						"Possible it is a result of string allocation error (maybe you've forgotten to allocate space for terminating NULL-character?)"
    						: ""
    					);
    			}
    		}
    	}
    	return EXCEPTION_CONTINUE_SEARCH;
    		
    }
    
    // Clear allocation log __allocs
    void EnterMemoryCheckRegion()
    {
    	void* p = __allocs;
    	int l = sizeof(__allocs) / 4;
    
    	__asm
    	{
    		mov edi, p
    		xor eax, eax
    		mov ecx, l
    		rep stosd
    	}
    }
    
    // Check for memory leak. Should be called at the end of guarded region of code
    void LeaveMemoryCheckRegion()
    {
    	for( int i=0; i<sizeof(__allocs)/sizeof(*__allocs); i++ )
    	{
    		if( __allocs[i].mem && __allocs[i].len != -1 )
    		{
    			DebugMessage(
    				"Possible memory leak detected. "
    				"Buffer at address 0x%08x is not freed at the end of the memory-check region."
    				,
    				__allocs[i].mem
    				);
    		}
    	}
    }
    
    // Show debug message
    void DebugMessage(char* s, ...)
    {
    	char t[ 1024 ];
    	va_list va;
    	va_start( va, s );
    	vsprintf( t, s, va );
    
    	char msg[ 1024 ];
    	sprintf(msg,
    		"Memory Debugging Message:\n"
    		"\n"
    		"%s\n"
    		"\n"
    		"If you want to abort execution press 'Abort'\n"
    		"If you want to debug application press 'Retry'\n"
    		"If you want to continue execution press 'Ignore'\n"
    		,
    		t
    		);
    
    	OutputDebugString( msg );
    
    	switch( MessageBox( NULL, msg, "Memory Debugging Message", MB_ABORTRETRYIGNORE|MB_ICONERROR ) )
    	{
    	case IDRETRY:
    		__asm int 3;
    		break;
    
    	case IDABORT:
    		ExitProcess(0);
    		
    	};
    }
    
    // new[] operator handler for the overflow-guard protection mode
    // Allocates buffer at the end of the page, next page will be marked as invalid.
    // Any access behind the end of the buffer will be failed
    void* AllocateOverflowGuardedBuffer(int size)
    {
    	SYSTEM_INFO si = {0};
    	GetSystemInfo( &si );
    	/*if( size > (signed)si.dwPageSize ) return NULL;*/
    
    	void* mem = VirtualAlloc( NULL, /*si.dwPageSize*2*/ size + si.dwPageSize, MEM_RESERVE, PAGE_NOACCESS );
    	if( !mem ) {
    		OutputDebugStringEx(__FUNCTION__" : ERROR : VirtualAlloc(... %d ...) fails! : LasrErr = %08X", size + si.dwPageSize, GetLastError());
    		return NULL;
    	}
    	mem = VirtualAlloc( mem, /*si.dwPageSize*/ size, MEM_COMMIT, PAGE_READWRITE );
    
    	DWORD dwSmth = size % si.dwPageSize ? size % si.dwPageSize : si.dwPageSize;
    	DWORD ps = /*si.dwPageSize - size*/ si.dwPageSize - dwSmth;
    	__asm
    	{
    		mov al, 0xfd
    		mov ecx, ps
    		mov edi, mem
    		rep stosb
    	}
    
    	mem = (LPVOID)( (DWORD)mem + /*si.dwPageSize - size*/si.dwPageSize - dwSmth );
    
    	int i;
    	for( i=0; i<sizeof(__allocs)/sizeof(*__allocs); i++ )
    		if( !__allocs[i].mem )
    		{
    			__allocs[i].guard_type = OVERFLOW_GUARD;
    			__allocs[i].len = size;
    			return (__allocs[i].mem = mem);
    		}
    	
    	DWORD dwWholeRegionSize = (size - dwSmth) + si.dwPageSize;
    
    	VirtualFree( mem, /*si.dwPageSize*/ dwWholeRegionSize,  MEM_DECOMMIT );
    	VirtualFree( mem, /*si.dwPageSize*2*/ dwWholeRegionSize + si.dwPageSize, MEM_RELEASE );
    
    	OutputDebugStringEx(__FUNCTION__" : ERROR : RESULT == NULL : i == %d", i);
    
    	return NULL;
    }
    
    // delete[] operator handler for the overflow-guard protection mode
    void FreeOverflowGuardedBuffer(void* mem)
    {
    	SYSTEM_INFO si = {0};
    	GetSystemInfo( &si );
    	
    	for( int i=0; i<sizeof(__allocs)/sizeof(*__allocs); i++ )
    		if( __allocs[i].mem == mem )
    		{
    			if( __allocs[i].len == -1 )
    			{
    				DebugMessage(
    					"Attempt to free already freed buffer 0x%08x.\n"
    					"Possible it's a result of incorrect destructor call."
    					,
    					mem
    					);
    				return;
    			}
    
    			DWORD dwSmth = __allocs[i].len % si.dwPageSize ? __allocs[i].len % si.dwPageSize : si.dwPageSize;
    			long ps = /*si.dwPageSize - __allocs[i].len*/ si.dwPageSize - dwSmth;
    			LPVOID addr = (LPVOID)( (DWORD)mem + /*__allocs[i].len - si.dwPageSize*/ dwSmth - si.dwPageSize );
    
    			int j;
    			for( j=0; j < ps; j++ )
    				if( ((unsigned char*)addr)[j] != 0xfd )
    					break;
    			ps -= j;
    			
    			if( ps )
    			{
    				DebugMessage(
    					"Buffer underflow detected while overflow-guard protection at address 0x%08x.\n"
    					"It's highly recommended to test program in underflow-guard mode to detect faulting instruction."
    					,
    					mem
    					);
    			}
    
    			DWORD dwWholeRegionSize = (__allocs[i].len - dwSmth) + si.dwPageSize;
    
    			VirtualFree( mem, /*si.dwPageSize*/ dwWholeRegionSize,  MEM_DECOMMIT );
    			VirtualFree( mem, /*si.dwPageSize*2*/ dwWholeRegionSize + si.dwPageSize, MEM_RELEASE );
    
    			__allocs[i].len = -1;
    			__allocs[i].mem = NULL;
    			return;
    		}
    }
    
    А вообще, меня жутко напрягает вот это: #define ALLOC_SIZE 10000
    Надо бы в виде динамического односвязного списка это всё строить, по идее.
     
    1 person likes this.
  5. gribodemon

    gribodemon New Member

    Joined:
    7 Jun 2009
    Messages:
    0
    Likes Received:
    2
    Reputations:
    4
    ALSO

    А так же, в этом коде никак не обрабатывается ситуация, если мы хотим освободить память по invalid-указателю.
    В реальной программе, с функцией free это привидёт к исключению. Поэтому, я рекомендую добавить

    Code:
    __asm int 3
    В конец функции FreeOverflowGuardedBuffer

    P.S.:

    Наверно вы зададите вопрос - как это вообще возможно - попытаться освободить память по неверному указателю ?
    Если вы используете замечательный код TC - TransferProgramEx, то эта ошибка может возникнуть. Будьте уверены.
    Типа:

    Code:
    PBYTE gl_pBuffer = NULL;
    
    ...
    void f()
    {
      if (gl_pBuffer) delete[] gl_pBuffer;
      gl_pBuffer = new BYTE[100];
    }
    
    Что произойдёт, если вы вызовите неск. раз f(), затем FreeOverflowGuardedBuffer(), и снова попытаетесь вызвать f(), будучи в коде уже другого процесса? Хехе. =)

    А как решить эту проблему, не прибегая к полному отказу от оператора delete в функции типа f() ?
    Я так понимаю, нужно во временном указателе, до вызова TransferProgramEx запомнить адрес на gl_pBuffer, затем обнулить gl_pBuffer, потом вызвать TransferProgramEx, после её вызова, восстановить gl_pBuffer.

    Ну, примерно таким образом это выглядит для случая с блоком по выявлению ошибок памяти ( дополнительный массив структур __allocs_bck & модифицированный код функции TransferProgramEx) :

    Code:
    
    struct __allocation {
    	void* mem;
    	int len;
    	char guard_type;
    } __allocs [ALLOC_SIZE], __allocs_bck [ALLOC_SIZE];

    Code:
    	// move memory
    #ifdef _DEBUGLITE
    	CopyMemory(__allocs_bck, __allocs, sizeof(__allocs));
    	ZeroMemory(__allocs, sizeof(__allocs));
    #endif
    
    	__CopyMemoryAcrossProcesses( hProcess, (char*) hModule, (char*) Allocated );
    
    #ifdef _DEBUGLITE
    	CopyMemory(__allocs, __allocs_bck, sizeof(__allocs));
    	ZeroMemory(__allocs_bck, sizeof(__allocs));
    #endif


    Кто знает более лаконичное решение?
     
    #5 gribodemon, 6 Apr 2010
    Last edited: 6 Apr 2010
    1 person likes this.
  6. gribodemon

    gribodemon New Member

    Joined:
    7 Jun 2009
    Messages:
    0
    Likes Received:
    2
    Reputations:
    4
    Странно что ещё никто не сказал, но:

    MSDN:
     
  7. Sajeys

    Sajeys Banned

    Joined:
    24 Aug 2010
    Messages:
    206
    Likes Received:
    35
    Reputations:
    5
    Полезно... :)