Assembly routines break Windows 64-bit SEH

E. Madison Bray erik.m.bray at gmail.com
Thu May 9 14:14:46 UTC 2019


Hello, thank you for the response

On Thu, May 2, 2019 at 11:44 PM Niels Möller wrote:
>
> "E. Madison Bray" writes:
>
> > This is because the assembly routines do not include the metadata that
> > is necessary on 64-bit Windows [1] for stack unwinding to work
> > properly during exception handling.
>
> It would be a good start to document what's the bare minimum to get an
> exception, like a null pointer dereference, to reliable cause a crash
> saying what type of crash it was.

I'm not really sure what you're asking here: There are myriad ways an
exception can occur in a program, both locally and non-locally.

> It somewhat surprising to me if stack-unwinding meta data is required
> for non-recoverable exceptions. Say, you have an executable, and you
> replace some part of the object code with random garbage data. It will
> most likely crash in an unrecoverable way when you run it, and not carry
> any consistent metadata. But it ought to be the operating system's
> responsibility to report the failure, without assuming the executable is
> particularly cooperative.

Sure, there are all kinds of corner cases one could invoke such that
stack unwinding does not succeed.  There are a few possibilities that
can occur here: If the OS does not find the expected metadata it may
just assume that either the executable is corrupt, that the stack is
irrecoverably corrupt, or it an unhandled exception may occur in the
stack-undwinding code itself.  In such cases the OS does not run
user-provided exception handlers (it does however still run vectored
continue handlers [1]).  Instead it falls back on some default
exception handling.

The point is that in general you *do* want user-provided exception
handlers to be able to run, and many exceptions, including some of the
most common (e.g. null pointer dereference) are recoverable if
exception handlers can be run.

> >  For example a
> > `push_reg(<reg>)` macro would emit (on Windows 64):
> >
> >     push <reg>
> >     .seh_pushreg <reg>
> >
> > whereas on all other platforms it would just emit the plain `push
> > <reg>` instruction.  This part I believe is easy.
>
> Sounds like reasonable approach, if we want to do this. But a bit
> intrusive if there are lots of pushes.

This is only needed during function prologues that push non-volatile
registers, not for all pushes.  Of course it would be a pretty large
diff, unfortunately.  But a repetitive one; an almost completely
automatic replacement.

> > The trickiest part is just ensuring that some register is available to
> > establish a frame pointer, when necessary (it doesn't necessarily have
> > to be RBP; any nonvolatile register will do).
>
> We strive to not spend registers on frame pointers. I think most gmp
> assembly functions are leaf functions. mpn_divrem_1 is an unusual case.
> And the mpn_invert function it calls is unlikely to crash for any input;

That's good then! I haven't done a thorough examination but leaf
functions are simpler and should not ever require a frame pointer.
They do still need unwind info if they save non-volatile registers
though, but that's generally it.

> I don't see any way it can crash, except if the object code is corrupted
> or the relocation for mpn_invert_limb_table or the function itself is
> somehow buggy and makes it point into nowhere.

Agreed; as far as I can tell there was no obvious way to make it
crash.  Nevertheless I would consider using a frame pointer at least
where possible on non-leaf functions.

> I also wonder if any other architectures (ARM?) needs anything similar?

It appears so, and thankfully reasonably documented too [2]. Otherwise
I don't think any other architectures are relevant. I don't know
anything about Windows on ARM(64) nor do I have a readily available
means of testing. So if I work on this I will focus for now just on
x86-64.  I will need to do more research to be sure but I think much
of what is done for x86-64 would be transferable.  The general
concepts appear to be the same, but with differences and complications
in the details.


[1] https://reverseengineering.stackexchange.com/a/14993/23723
[2] https://docs.microsoft.com/en-us/cpp/build/arm64-exception-handling?view=vs-2019


More information about the gmp-bugs mailing list