Opened 8 years ago

Last modified 7 years ago

#15 new enhancement

Add own setjmp()/longjmp()

Reported by: dmik Owned by:
Priority: major Milestone: general enhancement
Component: odin Version:
Severity: Keywords:
Cc:

Description

The normal OS/2 LIBC version of setjmp() memorizes the current (top) exception frame at the time of the call which longjmp() then uses before the jump to unwind the exception chain up to (this is done to clean up the exception handlers possibly installed after setjmp()).

When these functions are used in the Windows code compiled with Odin32 SDK with enabled SEH (structured exceptions), this represents an expected problem: since the FS register points to the Win32 thread block at the time of the setjmp() call, it captures the Win32 exception frame (instead of the OS/2 frame) and longjmp() uses DosUnwindException?() on this frame which is wrong and will usually crash the application with something lile XCPT_INVALID_UNWIND_TARGET.

Change History (3)

comment:1 Changed 8 years ago by dmik

In r21474, I added support for setjmp()/longjmp() to our SEH (__try/__except/__finally) emulation by installing the same exception frame to both the W[in32 chain and the OS/2 chain. But it works only for the cases where the setjmp()/longjmp() pair is completely within the try block (i.e. longjmp() doesn't pass control outside try).

Though it's the most useful case, something like this is still possible:

...
if (!setjmp(buf))
{
  __try
  {
    longjmp(buf);
  }
  __except(...)
  {
    ...
  }
}
else
{
  // longjmp() target
  ...
}

In this case, setjmp() will still grab a raw Win32 exception frame and we can't do anything about that.

The only solution here is to provide our own setjmp()/longjmp() implementations in MINICRT.LIB and use them instead. These implementations will capture both the OS/2 and the Win32 exception frames and will unwind both chains on jump.

comment:2 Changed 8 years ago by dmik

Just for the record, some more details on how I made setjmp()/longjmp() work inside __try w/o overloading them. Installing our exception frame to both exception chains (which is possible at all because SEH in Win32 is really almost identical to SEH in OS/2) makes DosUnwindException?() called by longjmp() happy (it will unwind up to our handler and not further). However, there is a problem with the pointer to the previous exception frame: we have a single cell in the frame record that must point to the previous frame in both the OS/2 and Win32 chains (depending on what chain is currently being processed) but these frames obviously differ in many cases.

I solve this problem by detecting what chain is being processed (this is indicated by the FS value) and placing a correct pointer to the respective cell based on that. It seems that the system uses the value of this cell only after calling the exception handler associated with the given frame (e.g. when unwinding, or when walking through handlers when searching for one that will handle the exception) so we can change this cell to the right value before it gets used. There is however a potential problem: if one tries to walk the exception chain w/o calling the handlers along the go, there is a good chance that our exception frame will have an incorrect pointer to a frame from the other chain. Though I didn't find real situations showing this behavior.

comment:3 Changed 7 years ago by dmik

  • Milestone set to general enhancement

Note that actually doing kLIBC longjmp() should work correctly (after enabling unwind handlers in Odin, r21619) as long as the top exception handler is the SEH one (i.e. the one installed by try).

Note: See TracTickets for help on using tickets.