Is there a better way to implement this memory management?

Refresh

December 2018

Views

264 time

2

This is a follow-up to my previous question regarding exceptions.

I have some legacy code that I am attempting to maintain. It has a custom memory management component that I am having difficulty understanding.

My understanding of the system is as follows:

Calling function asks for a some memory to be allocated for it, providing an initial amount of memory needed (needed), and a maximum amount (max). This calls:

base = VirtualAlloc(0, max, MEM_RESERVE, PAGE_NOACCESS);

Which I understand reserves the memory but does not provide access. In other words, if I try to write to the reserved segment, I would get an access violation.

It then calls:

VirtualAlloc(base, needed, MEM_COMMIT, PAGE_READWRITE);

Which makes needed amount of memory starting at base accessible.

The sticky part comes when trying to detect when more memory needs to be made accessible. My understanding is that the system attempts to catch access violation exceptions when they happen and call VirtualAlloc on the address to make the memory accessible.

It does this by declaring the following method:

unsigned long __cdecl
exceptionCatch(struct _EXCEPTION_RECORD* er, void*, struct _CONTEXT* cr, void*)
{
    if( er->ExceptionCode == EXCEPTION_ACCESS_VIOLATION
      && ExtendBuffer( (void*)er->ExceptionInformation[1] ) )
        return ExceptionContinueExecution;

    return ExceptionContinueSearch;
}

Then, it registers this as the exception handler for the top of the stack (I think), using this particularly horrible piece of code:

void __cdecl SetHandler(bExceptionRegistration& v)
{
    __asm
    {
        mov     eax, 8[ebp]     ; get exception register record to install
        mov     ecx, fs:[0]     ; get current head of chain

        cmp     ecx, eax        ; should we be at head?
        jb      search
        mov     [eax], ecx      ; save current head
        mov     fs:[0], eax     ; install new record at head
        jmp     short ret1
search:
        cmp     [ecx], eax      ; at proper location yet?
        ja      link
        mov     ecx, [ecx]      ; get next link
        jmp     search
link:
        mov     edx, [ecx]
        mov     [eax], edx      ; point to next
        mov     [ecx], eax
ret1:
    }
}

This method is called by instantiating a particular class in a method scope. It looks like it only applies the handler to the current stack context; as in, exceptions thrown in called functions are not handled by the current method if the exception is not propagated to the current method.

The result of all this is that not only is the access violation not caught, but it disables exception handling at the current top of the stack. I have set break points in the exceptionCatch function and execution doesn't appear to enter it.

I suppose my main questions are:

  1. Is there any particular reason why this shouldn't work? Edit: based on my own testing and comments here, I think the assembly code is the problem area.
  2. More importantly, is there a better way to do what I think the code is attempting to do?

I don't think something like set_unexpected is feasible, since the memory management is applying only to this particular library and the client application may (and in our case, does) have its own unexpected exception handler.

Edit:

The setting and unsetting of the handler per stack is done by declaring a class bExceptionRegistration with the following class constructor and destructor:

bExceptionRegistration :: bExceptionRegistration() : function(exceptionCatch)
{
    SetHandler(*this);
}

bExceptionRegistration :: ~bExceptionRegistration()
{
    UnsetHandler(*this);
}

So, to actually set the handler for a particular stack scope, you would have:

void someFunction()
{
    bExceptionRegistration er;
    // do some stuff here
}

Edit: I'm guessing that probably the most appropriate solution to all this is to replace the bExceptionRegistration declarations in the code with __try, __except blocks. I was hoping to avoid this however, as it is in a lot of places.

2 answers

2

Я не 100% уверен в этом , не видя больше кода. Он не регистрирует обработчик исключений в верхней части стеки , но он использует трюк , чтобы вставить обработку исключений , где EXCEPTION_REGISTRATIONопределена структура. Так, например , (возможно , в вашем случае это реализовано немного по- другому):

void function3(EXCEPTION_REGISTRATION& handler)
{
    SetHandler(handler);
    //Do other stuff
}
void function2(EXCEPTION_REGISTRATION& handler)
{
    __try
    {
        //Do something
        function3(handler);
    }
    __except(expression)
    {
        //...
    }
}

void function()
{
    EXCEPTION_REGISTRATION handler;
    //..Init handler
    function2(handler)
}

При вызове SetHandlerон вставит обработку , как это было в рамках функции исключения. Так что в этом случае в данный момент вы называете SetHandlerэто будет выглядеть так , если есть __try __except блок в функции.

Therefor если есть исключение внутри function3 обработчика в функции первого будет называться , и если этот обработчик не обрабатывает его, обработчик установлен SetHandlerбудет называться.

2

Я не 100% уверен в этом , не видя больше кода. Он не регистрирует обработчик исключений в верхней части стеки , но он использует трюк , чтобы вставить обработку исключений , где EXCEPTION_REGISTRATIONопределена структура. Так, например , (возможно , в вашем случае это реализовано немного по- другому):

void function3(EXCEPTION_REGISTRATION& handler)
{
    SetHandler(handler);
    //Do other stuff
}
void function2(EXCEPTION_REGISTRATION& handler)
{
    __try
    {
        //Do something
        function3(handler);
    }
    __except(expression)
    {
        //...
    }
}

void function()
{
    EXCEPTION_REGISTRATION handler;
    //..Init handler
    function2(handler)
}

При вызове SetHandlerон вставит обработку , как это было в рамках функции исключения. Так что в этом случае в данный момент вы называете SetHandlerэто будет выглядеть так , если есть __try __except блок в функции.

Therefor если есть исключение внутри function3 обработчика в функции первого будет называться , и если этот обработчик не обрабатывает его, обработчик установлен SetHandlerбудет называться.