Форум АНТИЧАТ

Форум АНТИЧАТ (https://forum.antichat.xyz/index.php)
-   Авторские статьи (https://forum.antichat.xyz/forumdisplay.php?f=31)
-   -   Выявление ошибок памяти (https://forum.antichat.xyz/showthread.php?t=37379)

_Great_ 07.04.2007 11:58

Выявление ошибок памяти
 
Article: Выявление ошибок памяти
Author: Great
Date: 06.04.2007
Lang: C/C++ user mode
Note: В статье описаны действенные методы для выявления таких ошибок в программах, как переполнение буфера или утечек памяти.

Переполнение буфера - очень частая ошибка программистов, особенно начинающих - попытка записи в буфер за его пределами. Конечно, мы не сможем контролировать переполнение локального буфера, но мы сможем контролировать все операции с динамическими буферами. Ошибки такого типа трудноуловимы, потому что результат ошибки проявляется не немедленно, а перед ним может пройти значительное время. Например, программа выделяет два буфера - строку и управляющую структуру. Потом строка случайно перезаписыватся так, что происходит ее переполнение и затирается часть идущей за ней структуры, т.к. буфера выделяются в куче непосредственно друг за другом. Но эта ошибка проявится только при следующем использовании структуры. Таким образом, было бы неплохо отловить то место, когда происходит выход за границы буфера. Кстати, возможна и обратная ситуация: в результате неправильного вычисления индекса массива происходит обращение к элементу, находящемуся до начала массива. Такие ошибки менее часто встречаются, но их так же трудно отловить.
Существует один простой метод для "отлова" ошибок такого рода:
а) для обнаружения переполнений все буфера в программе выделяются особым образом: резервируется идущих подряд страницы виртуальной памяти, дальше первая из них передается (commit) в использование и буфер размещается на границе страниц в конце первой. Следующая страница недействительная. Любая попытка выхода за пределы буфера повлечет немедленно исключение нарушения доступа:
http://gr8.cih.ms/uploads/overflow.png
Для большей надежности оставшаяся часть страницы заполняется шаблоном, который потом проверяется на целостность.
Примерный код для аллокации и освобождения (откомментирован):
Код:

#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, ...), а ее тело можно найти в исходнике к статье.

б) для обнаружения выхода за границы буфера с другой стороны ("недополнение" буфера) аналогичная методика: резервируются две страницы, вторая из них передается и буфер выделяется в нее начале, предыдущая страница недействительная. Попытки записи перед буфером повлекут исключение нарушения доступа. Оставшаяся часть страницы снова заполняется шаблоном, который при освобождении проверяется на целостность. Код выглядит похоже на предыдущий:
Код:

// 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;
                }
}

Чтобы отловить нарушение доступа, установим свой обработчик и предупредим программиста о возникшей ошибке:
Код:

// 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:
Код:

// 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(), код которых выглядит следующим образом:
Код:

// 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
                                );
                }
        }
}

Вызовами этих функций можно обрамлять участки кода, которые не должны оставлять за собой выделенные участки памяти.
В качестве примера рассмотрим программу, в которой присутствуют несколько ошибок памяти:
Код:

// 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байтный буфер, что будет немедленно обнаружено:
http://gr8.cih.ms/uploads/grd.png
Как видно, программа заботливо сообщает нам о однобайтовом переполнении в динамической памяти.
К сожалению, это не спасает нас от переполнения локальных буферов в стеке, но это уже отдельная тема. А здесь было рассмотрено обнаружение ошибок манипуляции с динамической памятью. На этом, пожалуй, я и закончу. Пока

Полный исходный код: http://gr8.cih.ms/uploads/dynamic_memory.cpp

ZaCo 12.01.2008 15:38

поскольку на практике работа с памятью ограничивается стандартными функциями, будет все же проще перегружать стандартные функции работы с памятью, под юникс тут может пригодится библиотека типа dmalloc. но идея использования самого процессора в качестве детектора переполнений хорошая, вот только на каждый new делать VirtualAlloc() расточительно..

_Great_ 12.01.2008 23:39

Расточительно, конечно, в связи с чем аллокации можно комбинировать. Например, страницы выделяются через одну, на каждой валидной странице вначале расположен буффер с защитой от "недополнений", а в конце - с защитой от переполнений. ВОобще идея классная, я буду ее развивать далее.

gribodemon 03.04.2010 18:47

Я немного модифицировал код, чтобы можно было выделять больше 0x1000 байт под динамический буфер.

Код:


#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
Надо бы в виде динамического односвязного списка это всё строить, по идее.

gribodemon 06.04.2010 03:56

ALSO

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

Код:

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

P.S.:

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

Код:

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) :

Код:


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


Код:

        // 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



Кто знает более лаконичное решение?


Время: 18:21