[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:25:30 PST 2020


Whoops!  First sentence should read "slightly more complex than
__clang_call_terminate but looks like it has the same external behavior".

On Wed, Dec 23, 2020 at 12:24 AM Everett Maus <evmaus at google.com> wrote:

> 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
>


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


More information about the llvm-dev mailing list