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

James Y Knight via llvm-dev llvm-dev at lists.llvm.org
Thu Dec 10 16:05:10 PST 2020


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.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20201210/51959e07/attachment.html>


More information about the llvm-dev mailing list