[Win64] Accessing register in custom exception-handler

Started by
1 comment, last by Juliean 1 year, 6 months ago

Hello,

for my JIT-compiled scripting-backend, I've been implementing custom exception-handler according to the specc (https://docs.microsoft.com/en-us/cpp/build/exception-handling-x64?view=msvc-170).

However, I'm having trouble accessing some of the register-values inside this handler. All my compiled functions store an “ExexecutionState”-variable inside the RBX register, which is needed to interface with the different bindings etc… so what I need to do, is access that “state” inside the exception-handler as well. I've been doing it like this:

 EXCEPTION_DISPOSITION handleException(PEXCEPTION_RECORD exceptionRecord, ULONG64 establisherFrame, PCONTEXT contextRecord, PDISPATCHER_CONTEXT dispatcherContext)
{
 auto& state = *reinterpret_cast<ExecutionStateJIT*>(contextRecord->Rbx);

The main problem here is that this is the RBX at the function that raises the exception, which is mostly a c++-function that might override rbx as it sees fit.

So, how do I access the RBX inside the function where the exception should be handled? From the limited documentation, I assumed that it would the ContextRecord stored in DispatcherContext:

 EXCEPTION_DISPOSITION handleException(PEXCEPTION_RECORD exceptionRecord, ULONG64 establisherFrame, PCONTEXT contextRecord, PDISPATCHER_CONTEXT dispatcherContext)
{
 auto& state = *reinterpret_cast<ExecutionStateJIT*>(dispatcherContext->ContextRecord->Rbx);

Which does not seem to be the same contextRecord as is passed to the function, however, here RBX is “0”. I'm assuming that an exception-handler has to restore the non-volatile registers it uses, via the UNWIND_OPCODES, so RBX should then be at the old value when my exception-handler is invoked - correct? Or am I missing something?

Advertisement

I just wanted to post a quick update, that I found a solution. So please don't consider this a necro-post. In case anybody has the misfortune of having to deal with win64 exception-handling that deep, here's how you do it:

You are supposed to walk the stack with RtlVirtualUnwind, until you arive at the desired function. In my case, I had to identify which address belongs to any function that is from my own compiled code, which looks like this:

//! address-ranges should have been set earlier
AE_ASSERT(impl::addressStart != 0);
AE_ASSERT(impl::addressEnd > 0);

CONTEXT context;
RtlCaptureContext(&context);

while (context.Rip)
{
	DWORD64 imgbase;
	const auto func = RtlLookupFunctionEntry((DWORD64)context.Rip, &imgbase, nullptr);
	if (func)
	{
		PVOID handlerdata;
		ULONG64 establisherFramePointer;

		RtlVirtualUnwind(UNW_FLAG_NHANDLER, imgbase, (DWORD64)context.Rip, func, &context, &handlerdata, &establisherFramePointer, nullptr);

		// function belongs to our JIT-compiled code => ExecutionState is stored in Rbx
		if (context.Rip >= impl::addressStart && context.Rip < impl::addressEnd)
			return *(ExecutionStateJIT*)context.Rbx;
	}
	else
	{
		Log::OutErrorFmt("Failed to find function for exception-handling inspection. Result will likely be a crash.");

		context.Rip = ULONG64(*(PULONG64)context.Rsp);
		context.Rsp += 8;
	}
}

While this makes exception handling a bit slower, its definately better than the alternative I had to use so far (store the execution-state globally before every JIT-call, and restore the last one afterwards).

The reason I found out, and an additional hint - if you are doing RtlUnwindEx to implement a custom “catch” routine - you'll also have to use RtlVIrtualUnwind to aquire the EstablisherFrame for the function that you want to resume from, unless its always the same that threw the exception.

Well, there you go.

This topic is closed to new replies.

Advertisement