[cxx-abi-dev] gcc unwind ABI change for forced unwind

Dave Butenhof David.Butenhof at hp.com
Fri May 23 14:36:35 UTC 2003


Cary Coutant wrote:

>> Personally, I dislike the attempt to separate "cleanup" from 
>> "finalization" (handle/catch). I don't like the idea of an exception 
>> that can't be finalized, because it reduces the application's ability 
>> to control behavior.
>
> I didn't mean to imply that I liked this either, but it seemed a 
> useful compromise given the unlikelihood that C++ will ever add 
> try/finally.

Having just jumped into this particular discussion, I really didn't mean 
to imply that I thought anybody was implying anything. ;-)

Whether or not C++ ought to add finally is an interesting question on 
its own, though probably not relevant here. It'd be nice, in a way, 
because it more clearly specifies INTENT than the current catch(...) 
{throw;} syntax, and that's always nice. On the other hand, C++ cleanup 
is supposed to be done in destructors, whereas try/catch ought to be for 
finalization. (And I realize I'm trapping myself in terminology here, 
since the traditional "finally" keyword sounds like it ought to be 
associated with the generic term "finalization" whereas it's really 
non-finalizing cleanup. Oh well. Anyone who can handle signal and signal 
should be able to handle finalization and finally. ;-) )

And, on yet another hand, if C++ really didn't want anyone to be able to 
do cleanup in try/catch, the [re]throw syntax shouldn't exist. That it 
exists means C++ does explicitly and specifically support cleanup in 
try/catch, just with ugly syntax that obscures the intent.

> I'm still not entirely comfortable with the idea of thread 
> cancellation (and other forced unwinds) being a normal (catchable) C++ 
> exception, but if consensus has been reached outside this list, and 
> those on this list generally agree, I don't want to drag the 
> discussion out any more. 

I always tell people not to finalize a cancel or exit. I figure if they 
really know what they're doing, they'll ignore me; and if they're not 
sufficiently confident in their goal and implementation to ignore me, 
they shouldn't be playing with that stuff anyway. ;-)

So if the ABI wants to specify that there's something special about 
"forced unwind" exceptions, I won't really be too inclined to wander 
around mumbling about how it got all screwed up. Still, there ARE 
applications where the ability to take full control over unwind is 
important, and I don't like the idea of locking them out. As I said, one 
compromise would be to let forced unwind exceptions (individually and, 
ideally, also as a [super]class) be caught only BY NAME. Naive coders 
won't accidentally be finalizing someone else's cancel or 
longjmp_unwind, but someone who really needs to do it will be able to.

I don't think there's any real evidence of a broad industry consensus 
for this. POSIX doesn't have exceptions, and cleanup handlers are 
limited and inflexible. While I think I got everyone in the Austin IA64 
ABI work to pretty much agree in principle that cancel and exit ought to 
be exceptions, we never got into this level of detail so I can't guess 
what the reaction would have been.

I'm speaking mostly out of long experience with the OpenVMS condition 
handling facility and Tru64 UNIX libexc (which being originally based on 
code from MIPS CO ought to have some analogy somewhere else in the 
industry... perhaps IRIX?) They're basic calling standard and runtime 
library packages to support general stack unwind and handler searches. 
They're fundamentally DETACHED handlers (associating a handler routine 
with a procedure descriptor or stack frame rather than dispatching to 
code within the procedure), which are called without unwinding the 
stack... but of course can also be used to UNWIND to ATTACHED handlers 
as in our C "SEH" extension, and C++ or Ada catch clauses.

Both are fundamentally 2-phase exception mechanisms; a "virtual unwind" 
scan is made to find active exception handlers in the call stack, and 
the detached handlers are called to determine what should be done. The 
scan may eventually terminate with no handlers expressing interest, in 
which case the process is generally terminated with an unhandled 
exception. Or a handler may tell the unwinder to RESUME execution at the 
point of the exception (it hasn't been unwound at that point). Or some 
handler may choose to UNWIND to some point. That begins the second 
phase, as the stack is unwound frame by frame, and each handler (where 
any exists) is called again with special exception context indicating 
that the unwind is in progress. A semi-formalized (but unenforced and 
usually ignored) convention on OpenVMS is that cleanup should occur only 
on unwind. In principle, this gives the advantage that when a process 
dies with an unhandled exception the "core file" represents what 
happened up to the point of the exception -- which can be a big help in 
diagnosing the problem. Once cleanup (including unwind) has happened, 
the original problem is obscured.

Of course the C++ runtime need not provide the ability to continue from 
exceptions (most languages don't), or to run application code on the 
search phase (normally the runtime simply determines whether to start an 
unwind to call the application's handlers in the unwind phase). But in 
the context of an ABI discussion, I'd like to define a model for a 
"common exception library" (CEL) that's flexible enough to allow all that.

> What changes are needed in the C++ ABI document? Do we need to add 
> anything in the base (C) ABI to cover thread cancellation and exit so 
> that cleanups can be properly interleaved?

I'd like to see a full exception library ABI that supports enough 
flexibility to do all this, and to experiment and build other languages 
with different semantics.

I'd like to see some form of exception syntax in the C language, whether 
it's a full try/catch/finally, a C++-style try/catch/[re]throw, or a 
basic enabling syntax like the MS SEH syntax that our compiler borrowed 
(try/except, where the except clause just provides an expression, 
usually a routine call, to be evaluated to determine whether to continue 
the search or resume execution; or of course that routine can call 
'unwind' to begin the unwind phase). The pthread_cleanup_push() and 
pthread_cleanup_pop() macros can easily be written in terms of any of 
the alternatives. The important point is simply that C source code has 
some way to tap into the common exception library's unwind and handler 
mechanisms.

Like this C "SEH" extension, C++ destructors and try/catch, Ada, Java, 
and so forth ought also to be built on top of the CEL. The CEL will then 
simply see a chain of call frames, some of which have handler routines 
-- it doesn't need to know the language of any frame, so it doesn't 
matter how they're interleaved.

Cancel and thread exit are just exceptions. "By convention", nobody 
should do anything to these on the search phase, and should do only 
cleanup on the unwind phase. But the CEL wouldn't care, and everything 
will work consistently if someone does. Each language's handler will 
need to decide how to handle various exceptions though. The C++ handler 
could simply ignore some or all of these on search phase, and run only 
object destructors on unwind phase. The C runtime's 'except' clause 
exception handler might decide not to even evaluate the 'except' 
expression, and so forth. However, I would strongly prefer that there be 
some way to finalize at least cancel and exit in both C and C++. It 
allows for a modularization of cancel, where cancel really means "cancel 
this function" without terminating the thread, which might be a generic 
serial server thread fully capable of continuing to service other functions.

I'm not at all sure whether longjmp_unwind is a separate case. I just 
checked, and on Tru64 longjmp_unwind (exc_longjmp) skips the search 
phase (and therefore normal frame-based capture/continue/search 
decisions) entirely. I guess that makes sense, since it starts out with 
a target frame and knows the intent is to unwind. So it just calls 
exc_unwind(). However, frame handlers will still be called for UNWIND 
disposition, with the exception name EXC_LONGJMP_UNWIND, so the handlers 
can do cleanup. On OpenVMS, I believe that an unwind phase handler could 
actually raise a new exception or start a nested unwind -- I'm not 
convinced that the Tru64 package supports that much flexibility; the 
code is convoluted and I've never had occasion to experiment.

Anyway, this seems like a clear basis for distinction. Forced unwind 
only does the unwind phase, not search phase, and therefore cannot 
generally be finalized by normal language facilities. If C++ catch(...) 
triggers only on search phase, it won't even see these. Destructors 
would fire on unwind phase, and would run "transparently" on a forced 
unwind. If desired, the runtime could make a concession for forced 
unwind exceptions to run catch(...) {throw;} blocks, for example, or 
anything else determined to be a "finally" type construct, even though 
we skipped the search phase. (Though I'd be inclined to argue against 
it, I think.)

Armed with all this terminology and context, one could argue that cancel 
and exit, like longjmp, should skip the search phase entirely, making 
themselves immune to finalization. As I've said, I don't like this -- 
but it does avoid the risk of fools breaking the application with 
unprotected catch(...) blocks. If the runtime's handler routine made 
special cases to detect specific exception names during unwind phase, it 
could make 'catch (cancel)', or 'catch(longjmp)', or even 
'catch(all_forced_unwinds)' fire in the unwind phase to handle 
applications that really want to finalize.

-- 
/--------------------[ David.Butenhof at hp.com ]--------------------\
| Hewlett-Packard Company       Tru64 UNIX & VMS Thread Architect |
|     My book: http://www.awl.com/cseng/titles/0-201-63392-2/     |
\----[ http://homepage.mac.com/dbutenhof/Threads/Threads.html ]---/





More information about the cxx-abi-dev mailing list