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