[llvm-dev] Catching exceptions while unwinding through -fno-exceptions code

Everett Maus via llvm-dev llvm-dev at lists.llvm.org
Wed Dec 23 00:24:45 PST 2020


The nature of "halting" is in cxa_personality.ccp, which is slightly more
complex than __clang_call_terminate but not

, and then either call the terminate handler for the exception directly or
call std::terminate if it is a foreign exception.  So that behavior largely
matches the noexcept behavior as far as I can tell (which calls
__clang_call_terminate, which sets exception context via __cxa_begin_catch
and then calls std::terminate, which checks the global exception context
and if an exception handler is present, calls it, and if one is not simply
calls the default termination handler--so basically they have the same
outcome).

At least in the absence of optimizations, I don't think we actually /need/
to add the invoke--as long as we change the behavior for when the
personality function and the exception table is emitted to force them to be
emitted for a noexcept function.  A call to a function marked "noexcept" +
"nounwind" is still guaranteed to not pass an exception out (the
semantics of "call", at least as I understand them?).

At the moment, that's what my very raw prototype does--instead of changing
the logic around when a call vs. invoke is emitted, I simply changed the
code generation logic in LLVM for the exception tables for itanium style
exceptions, so that they emit appropriately empty (or
gapped/incomplete/etc.) tables appropriately in the presence of
exceptions.  However I'm not completely sure if that'll be valid in the
face of all optimizations--I'm still working out some details there.

--EJM


On Tue, Dec 22, 2020 at 9:30 PM Fāng-ruì Sòng <maskray at google.com> wrote:

> On Tue, Dec 22, 2020 at 9:08 PM Hubert Tong via llvm-dev <
> llvm-dev at lists.llvm.org> wrote:
> >
> > On Tue, Dec 22, 2020 at 7:57 PM Everett Maus via llvm-dev <
> llvm-dev at lists.llvm.org> wrote:
> >>
> >> So, I've been digging into this a bit more over the past week or so.
> >>
> >> I think both options are viable.
> >>
> >> A custom personality function is definitely easier (and does seem to be
> necessary for C code); so it may actually be ideal to do a mixed version of
> the proposal (custom personality for C code, normal personality + different
> tables for C++ code and noexcept functions).
> >>
> >> The work to re-do how exception tables are emitted for noexcept
> functions is much larger, but does seem like it would work pretty well &
> give the desired behavior.
> >>
> >> The general idea of the second approach would be:
> >> - We add a new attribute (noexcept/haltonunwind/name still in process)
> to LLVM IR.
> >> - In Clang: by default, noexcept functions receive this attribute.
> Also, so do all functions in a translation unit compiled with
> -fno-exceptions + -fterminate-exceptions. (So we would have a number of
> functions with "nounwind noexcept" attributes on them or similar.)  This
> would affect the code generation for noexcept functions as well (removing
> the need for the explicit terminate handler block).
> >> - In LLVM, when emitting unwind tables, if a function that has this
> particular attribute, we only emit regions for explicit try/catch blocks,
> and intentionally leave other areas out of the exception table (but we do
> still emit the default personality function/CFI/LSDA/etc. for the method).
> This leads to there being a "gap" in the table (and for functions compiled
> with -fno-exceptions, this means basically emitting a table that has no
> call sites/ranges in it at all). That then causes __gxx_personality_v0 to
> halt.
> >
> > What is the nature of this "halting"? If it's not calling a
> terminate_handler, then the behaviour is not correct to use by default for
> noexcept functions.
>
> The default action of the personality (i.e. when LSDA exists but PC
> belongs to a gap in the LSDA). For __gxx_personality_v0, it is
> __cxa_begin_catch + std::terminate (for a foreign exception the
> std::get_terminate() handler).
>
> A function attribute is definitely useful - which can give us the GCC
> eh_must_not_throw size optimization benefits. I haven't thought a lot about
> whether `invoke ... unwind default` (default action for invoke) is needed.
> I may be a bit conservative on whether a new personality should be
> introduced.
>
> >>
> >>
> >> I'm still trying to figure out if/how this would work for Windows/other
> exception models like SJLG or WASM, though, which adds some complexity to
> this proposal that I'd need to figure out.
> >>
> >> Open question about process--now that I've done enough prototyping to
> potentially put together a bit more of a proposal, is it best to just start
> a new thread for discussion of the two options?
> >>
> >> Thanks,
> >> Everett Maus
> >>
> >> On Thu, Dec 10, 2020 at 8:44 PM Everett Maus <evmaus at google.com> wrote:
> >>>>
> >>>> In short, if you just require the feature to work like a poor man's
> sanitizer,
> >>>> -fno-exceptions -fno-asynchronous-unwind-tables already work.
> >>>
> >>> &
> >>>>
> >>>> I agree we probably do need a flag for this change in behavior, but I
> think this flag is going to be very difficult to name and explain, because
> there are currently different behaviors depending on the target and other
> flags you pass.
> >>>
> >>>
> >>> I guess my take is that the way it should behave is "If exceptions are
> disabled on a TU via -fno-exceptions, and this flag is passed to that TU,
> then we should guarantee that any method in it that an exception handler
> passes through is guaranteed to exit".
> >>>
> >>> I think the complexity of the state of -fno-asynchronous-unwind-tables
> vs. -fasynchronous-unwind-tables that you highlighted makes an additional
> case for something that ideally won't be affected by those flags but does
> work well with -fno-exceptions.
> >>>
> >>> That absolutely seems, at least to me, to imply that we need something
> other than just -fno-asynchronous-unwind-tables (consider that:
> >>> -fno-asynchronous-unwind-tables + -fno-exceptions does exit, but
> -fno-asynchronous-unwind-tables + -funwind-tables + -fno-exceptions does
> not).  It'd be nice to have something that guaranteed this behavior for all
> translation units compiled with -fno-exceptions (and perhaps implied that
> unwind tables are set up in the correct way; becoming incompatible with
> -fno-asynchronous-unwind-tables + -fno-unwind-tables).
> >>>
> >>> The other concern I have is the current error message for this
> behavior from the existing personality function; it'd be good to surface
> not just "an uncaught exception caused the code to terminate" but that the
> exception handler had to traverse through code that was not compiled with
> exceptions in mind (or with Jame's proposal, where we also change noexcept
> to use this, where we have -fterminate-exceptions + fno-exceptions --> all
> methods are implied to be noexcept; which might be neater, to be honest.).
> >>>
> >>>> There should be no compiler-rt side change. __gcc_personality_v0 is
> in compiler-rt/libgcc_s
> >>>> simply because it is used by C :( and it cannot use
> libsupc++/libc++abi :)
> >>>
> >>> &
> >>>>
> >>>> There's the additional complication of C code.  Historically, before
> -fasynchronous-unwind-tables became a common default, throwing through C
> functions would also abort, by default. That's why Glibc builds C functions
> that might call user C++ code with -fexceptions (example: qsort) -- that
> way it can ensure that unwinding a C++ exception does actually work
> properly.
> >>>>
> >>>> Unfortunately, unlike in C++, we don't have a ready-made personality
> function we can use for C functions which has "terminate" behavior. The
> _gcc_personality_v0 doesn't terminate upon failing to find a LSDA table
> entry.
> >>>
> >>> This is unfortunate, actually, because compatibility with code being
> compiled as C and linked with C++ that an exception may pass through is
> definitely something it'd be good to detect.  The prototype implementation
> I have using custom personality functions that I have does enable this at
> the moment by just adding a separate compiler-rt personality function.  I
> suppose we could also modify __gcc_personality_v0 to handle this case in
> some way.
> >>>
> >>> After doing some prototyping, at least initially I think the following
> is a reasonable proposal for using  that should meet the initial
> requirements I'd want for this feature:
> >>>
> >>> 1) Adding a new personality routine to libcxxabi (tentatively named
> "__cxx_noexceptions_terminate_personality_v0") which will exit with a
> message containing the detail that an exception was passed through code
> compiled without exception handling, along with exception type (if
> libcxxabi is compiled without LIBCXXABI_SILENT_TERMINATE; otherwise it
> would simply invoke std::terminate & exit).
> >>> 2) Adding a new personality routine to compiler-rt (tentatively named
> "__noexceptions_terminate_personality_v0") which will exit with a less
> verbose message, but provide the same 'exit the program with some sort of
> message' functionality for C code compiled with -fno-exceptions if an
> exception is passed through it. (E.x. if something is compiled with all
> methods that are in an extern "C" { ... } block or similar, this would
> insert the C handler for those methods.)
> >>> 3) Adding a new flag, -fterminate-exceptions (=strict or =none (the
> default)), to clang, which when set to 'strict' will add these new
> personality functions to all methods when -fno-exceptions is also set (and
> would have no effect otherwise).  I'm opting for an enumeration instead of
> a boolean flag here as one could imagine wanting to add an intermediate
> state in the future where it only halts if it would result in cleanups not
> occurring (but I propose not doing that work in the initial version).  This
> flag shouldn't change the behavior of code compiled with -fexceptions, and
> it should default to not affecting the behavior of code compiled with
> -fno-exceptions.
> >>>
> >>> Other notes:
> >>> - This shouldn't result in changes to functions marked with
> 'noexcept(true)' unless they are also compiled with -fno-exceptions (in
> which case they would get the new personality function like all other
> methods).  The current "add a landing pad to the caller that invokes
> __clang_call_terminate" behavior would still be present.
> >>> - I expect that the new personality routine would wind up wanting to
> call a handler instead of terminating itself (to support the optional
> demanging).  That would imply also adding a new member
> "__cxa_noexcept_terminate_handler"  or similar that has its behavior
> defined behind a compilation flag (like the other handlers in
> src/cxa_default_handlers.cpp).
> >>> - I think it's reasonable to defer printing stack information or
> information about where the exception was thrown/where it encountered a
> function compiled with -fno-exceptions and this flag until later. (That
> would potentially be an output message change, though--I don't know what
> the requirements are around text-output behavior changes. Potentially, I
> could see just gating that behind further restrictions or a separate
> compiler flag/etc. since calling out to the symbolizer seems like it might
> be expensive.)
> >>> - This seems to have an adverse effect on binary size, but it's less
> than 5% without optimizations, which is probably OK.
> >>> - My take is that this sort of feature is likely one that you'd want
> during test/debug/fuzzing builds, but probably would drop during an
> optimized build. From that perspective, I'm not as concerned about size
> bloat or the fact that inlining/optimizations will work less well.
> >>>
> >>> Option 2:
> >>>
> >>> I'm also going to look at prototyping something like what James is
> suggesting tomorrow, and I'll write up that as an alternate proposal.
> >>>
> >>> It does seem like it'd be a win to clean up the current issue with
> noexcept functions needing to add landing pads everywhere.  However, one
> thing I dislike with the current proposal of just leaving the LSDA empty,
> aside from the fact that it'll be hard to make it work with the C
> personality function, is that I don't think there's a good way (via just
> leaving the LSDA missing) to flag he difference between "it's missing from
> the table because it wasn't compiled in with -fasynchronous-unwind-table or
> -funwind-table" versus "it was explicitly removed because we want this to
> be required to halt." I feel like for debugging/etc. purposes, it would
> probably be good to flag the difference (as the exception would 'seem' like
> it should be caught to the programmer).
> >>>
> >>> Thanks,
> >>> Everett Maus
> >>>
> >>>
> >>> On Thu, Dec 10, 2020 at 4:05 PM James Y Knight <jyknight at google.com>
> wrote:
> >>>>
> >>>>
> >>>>
> >>>> On Tue, Dec 8, 2020 at 2:26 PM Everett Maus via llvm-dev <
> llvm-dev at lists.llvm.org> wrote:
> >>>>>
> >>>>> That makes sense.  Really appreciate the feedback, all.
> >>>>>
> >>>>> I think the approach I'll look at implementing is probably to:
> >>>>> - Implement a dedicated 'termination' personality function (in
> compiler-rt?) that does appropriate logging + exit.
> >>>>
> >>>>
> >>>> I still think we don't actually need to introduce a new personality
> function -- just reusing the standard C++ one, instead.
> >>>>
> >>>>> - Add a flag (-fterminate-exceptions?). This is because this is a
> very clear behavior change, so at least providing an opt-in/opt-out
> mechanism seems important. Possible option: make this an enum, have
> 'strict' = all exceptions crash, 'normal' = exceptions passing through
> methods that would require a cleanup /etc. terminate, none (default) =
> current behavior where things just leak/etc.
> >>>>
> >>>>
> >>>> I agree we probably do need a flag for this change in behavior, but I
> think this flag is going to be very difficult to name and explain, because
> there are currently different behaviors depending on the target and other
> flags you pass. This existing behavior is very inconsistent and confusing...
> >>>>
> >>>> 1. With -fno-asynchronous-unwind-tables -fno-exceptions, no unwind
> info is emitted, and therefore, attempting to throw an exception through
> such a function already aborts. This is the historically-common behavior.
> >>>>
> >>>> 2. With -fasynchronous-unwind-tables -fno-exceptions, unwind info is
> emitted for functions, and that unwind info currently specifies that
> exceptions can be thrown through such functions transparently.
> >>>>
> >>>> To make matters worse, the default of -fasynchronous-unwind-tables is
> different per-platform. In Clang, it's enabled for x86-64-linux, and off
> for i386-linux and ppc64-linux. And, in GCC, the default value even depends
> on other flags, and is customized in some distro's builds of the compiler.
> Whether it's on or off for i386-linux depends on whether you specify
> -fomit-frame-pointer or not...except that Debian (and maybe other distros?)
> have patched the spec file to enable async unwind tables, regardless.
> >>>>
> >>>>
> >>>>> - During code generation, when -fno-exceptions is turned on, if
> -fterminate-exceptions was passed, it changes the personality function from
> being not-present to being the dedicated -fno-exceptions termination
> personality function.
> >>>>
> >>>>
> >>>> We'll also probably want to adjust the inliner, because it otherwise
> prohibits inlining between functions with different personality routines
> (which will be relevant with cross-TU inlining with LTO).
> >>>>
> >>>> Anyways -- what I'd really like to see done here is to BOTH fix the
> handling of C++ "noexcept" functions to have minimal overhead, and then
> simply use the same mechanism to implement -fno-exceptions. This means we
> do in general need to be able to implement this behavior with the
> __gxx_personality_v0 personality, because noexcept functions can have
> try/catch blocks inside.
> >>>>
> >>>> I believe that the current design of noexcept -- which requires
> explicit invoke and landingpads saying that they terminate -- should be
> revisited. It adds a lot of unnecessary overhead.
> >>>>
> >>>> What we need is a way to explicitly declare that we want the
> personality-routine's default action to be taken on unwind -- implemented
> via leaving a gap in the LSDA table. I think we might want to do this both
> as syntax on invoke [e.g. `invoke void @xyz() to label %l unwind default`],
> as well as a function attribute declaring that any call in the function
> should be treated as having the default action of the personality function.
> >>>>
> >>>> "nothrow" would emit this new function attribute in combination with
> nounwind. Inlining a function having this attribute into a function not
> having the attribute would need to convert `call ...` into `invoke ...
> unwind default` -- but since the inliner already has such code, that's not
> a big deal.
> >>>>
> >>>>>> Not sure how much binary size balances with other concerns, but it
> sounds to me that the methods proposed are ones that would result in false
> positives where unwinding through the frame would have resulted in no
> action even when compiled with exceptions fully on.
> >>>>>>
> >>>>>> Perhaps leaving functions that would otherwise be "transparent" to
> exception handling alone is already implied?
> >>>>>
> >>>>>
> >>>>> So I think this is actually not ideal behavior, at least for the use
> case I have in mind.
> >>>>>
> >>>>> I think I'd prefer (and the team I partner with would prefer) /any/
> exception passing from code compiled with -fexceptions to code compiled
> with -fno-exceptions to halt & return an error, even if the method wouldn't
> have participated in exception handling (e.x. no classes that need to have
> destructors called, etc.)  I think the most desirable behavior is "the
> program halts if exceptions pass through code compiled without exceptions
> enabled".
> >>>>
> >>>>
> >>>> I agree that is a desirable behavior.
> >>>>
> >>>> There's the additional complication of C code.  Historically, before
> -fasynchronous-unwind-tables became a common default, throwing through C
> functions would also abort, by default. That's why Glibc builds C functions
> that might call user C++ code with -fexceptions (example: qsort) -- that
> way it can ensure that unwinding a C++ exception does actually work
> properly.
> >>>>
> >>>> Unfortunately, unlike in C++, we don't have a ready-made personality
> function we can use for C functions which has "terminate" behavior. The
> _gcc_personality_v0 doesn't terminate upon failing to find a LSDA table
> entry.
> >>>
> >>>
> >>>
> >>> --
> >>> --EJM
> >>
> >>
> >>
> >> --
> >> --EJM
> >> _______________________________________________
> >> LLVM Developers mailing list
> >> llvm-dev at lists.llvm.org
> >> https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-dev
> >
> > _______________________________________________
> > LLVM Developers mailing list
> > llvm-dev at lists.llvm.org
> > https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-dev
>
>
>
> --
> 宋方睿
>


-- 
--EJM
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20201223/7807cb12/attachment.html>


More information about the llvm-dev mailing list