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

Fangrui Song via llvm-dev llvm-dev at lists.llvm.org
Wed Dec 9 23:28:49 PST 2020


On 2020-12-09, Everett Maus via llvm-dev wrote:
>>
>> It depends on the amount of functionality here and how language specific
>> this is. As it stands I don’t think this is C++ specific and could extend
>> to other llvm language targets which makes it a better candidate with
>> compiler-rt. It would be good to flesh out what behavior you want out of
>> the personality function then go from there.
>>
>
>That makes sense.
>
>I think there's one "must have" behavior and a few "very nice-to-have but
>not required" behaviors.
>
>The "must have" for the handler would be "it terminates the program when an
>exception passes through code compiled with -fno-exceptions, hopefully in a
>way that can be easily identified as different from a normal/healthy
>program exit".

If you don't need .eh_frame and .eh_frame_hdr otherwise (no need to
appease debuggers/profilers/crash reporters/other stack unwinders),
you can simply add -fno-asynchronous-unwind-tables.

Clang behavior:

* `-fno-exceptions` && `-fno-asynchronous-unwind-tables` => no `.eh_frame`
* `-fno-exceptions` => no `.gcc_except_table`
* noexcept && -fexceptions => call __clang_call_terminate

libgcc_s/libunwind libsupc++/libc++abi behavior:

* no .eh_frame => __cxa_throw calls std::terminate since _Unwind_RaiseException returns
* .eh_frame + empty .gcc_except_table => __gxx_personality_v0 calls std::terminate since no call site code range matches
* .eh_frame without .gcc_except_table => pass-through

In short, if you just require the feature to work like a poor man's sanitizer,
-fno-exceptions -fno-asynchronous-unwind-tables already work.

>The nice-to-have list is probably longer:
>1. Printing the exception type that was thrown

I believe this requires a new personality as Reid mentioned.

>2. Recovering the place the exception was thrown /from/ and printing the
>function name & offset.

If there is a custom personality, it can call _Unwind_GetIP (Itanium C++ ABI)
or _Unwind_GetIPInfo (GCC extension, implemented in llvm-project libunwind)
to retrieve the instruction pointer, then you'll need a symbolizer.

>3. Printing the function name that the unwinder reached that led to it
>exiting.

This is more complex because the personality will have to actually unwind the stack
(pass control to Level 1 libunwind, i.e. it cannot simply return _URC_FATAL_PHASE1_ERROR).

>4. (Extra fancy/pie in the sky "what I'd want if I were debugging this
>situation"): printing the full demangled stack to the location the
>exception was thrown from or the method that led to the exit.

_Unwind_Backtrace, which exists as a nongnu libunwind/GCC extension.

>I expect that (1) requires some language-specific handling (RTTI in
>C++/etc.), so that'd imply that it might be good to have a libcxxabi
>implementation even if there's also a compiler-rt version (it looks like
>this is the case with __gxx_personality_v0 which is defined both in
>compiler-rt here:
>https://github.com/llvm/llvm-project/blob/main/compiler-rt/lib/builtins/gcc_personality_v0.c#L172
>and in libcxxabi here:
>https://github.com/llvm/llvm-project/blob/main/libcxxabi/src/cxa_personality.cpp#L953
>
>I also think that possibly (2)/(3)/(4) might require some non-compiler-rt
>components, but I'm not certain if they would (I haven't dug into what's in
>compiler-rt in depth quite yet).  That may also be reasonable to defer or
>to have a sanitizer handle.

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

>--EJM
>
>
>On Wed, Dec 9, 2020 at 12:15 PM Reid Kleckner via llvm-dev <
>llvm-dev at lists.llvm.org> wrote:
>
>> Bleh, early send. We'd also have to overcome the issue of functions being
>> nounwind with -fno-exceptions, which means LLVM would optimize those
>> invokes to calls immediately.
>>
>> On Wed, Dec 9, 2020 at 12:14 PM Reid Kleckner <rnk at google.com> wrote:
>>
>>> Using existing personality functions requires emitting an LLVM IR cleanup
>>> around every function when building in this -fterminate-exceptions mode.
>>> Then all functions would have IR like this:
>>>
>>> invoke void @foo(...) unwind to %terminate
>>> invoke void @bar(...) unwind to %terminate
>>> ...
>>> landingpad cleanup ...
>>>   call void @myterminate()
>>>
>>> On Tue, Dec 8, 2020 at 12:56 PM James Y Knight <jyknight at google.com>
>>> wrote:
>>>
>>>> Why is adding a new personality function useful? Can't you share a
>>>> single LSDA table for every noexcept function in a TU (both those
>>>> implicitly noexcept due to -fno-exceptions and for those marked "noexcept")?
>>>>
>>>> On Tue, Dec 8, 2020 at 1:05 PM Reid Kleckner via llvm-dev <
>>>> llvm-dev at lists.llvm.org> wrote:
>>>>
>>>>> I would suggest using a custom personality function for this. It will
>>>>> optimize better and be much smaller than using a standard personality
>>>>> function. It saves the LSDA tables.
>>>>>
>>>>> LLVM supports custom personality functions, so only clang changes are
>>>>> required. You could either do something like add a flag to override the EH
>>>>> personality with a custom one, or come up with a new dedicated
>>>>> fno-exceptions termination personality and add it to compiler-rt.
>>>>>
>>>>> On Mon, Dec 7, 2020 at 3:31 PM Modi Mo via llvm-dev <
>>>>> llvm-dev at lists.llvm.org> wrote:
>>>>>
>>>>>> If you don’t need to capture more information and can just terminate,
>>>>>> you can directly register std::terminate as the personality routine as
>>>>>> opposed to __gxx_personality_v0 or __CxxFrameHandler3/4 (Windows) which
>>>>>> lets you omit other metadata and work cross-platform.
>>>>>>
>>>>>>
>>>>>>
>>>>>> Modi
>>>>>>
>>>>>>
>>>>>>
>>>>>> *From: *llvm-dev <llvm-dev-bounces at lists.llvm.org> on behalf of
>>>>>> Everett Maus via llvm-dev <llvm-dev at lists.llvm.org>
>>>>>> *Reply-To: *Everett Maus <evmaus at google.com>
>>>>>> *Date: *Monday, December 7, 2020 at 12:47 PM
>>>>>> *To: *"llvm-dev at lists.llvm.org" <llvm-dev at lists.llvm.org>
>>>>>> *Subject: *[llvm-dev] Catching exceptions while unwinding through
>>>>>> -fno-exceptions code
>>>>>>
>>>>>>
>>>>>>
>>>>>> Hey all:
>>>>>>
>>>>>>
>>>>>>
>>>>>> I wanted to bring up something that was discussed a few years ago
>>>>>> around the behavior of exceptions when interacting with code compiled with
>>>>>> -fno-exceptions. (In
>>>>>> https://lists.llvm.org/pipermail/llvm-dev/2017-February/109992.html
>>>>>> and
>>>>>> https://lists.llvm.org/pipermail/llvm-dev/2017-February/109995.html)
>>>>>>
>>>>>>
>>>>>>
>>>>>> It's possible to compile (and link/etc.) code with -fexceptions for
>>>>>> some compilation units and -fno-exceptions for others.  Unlike the behavior
>>>>>> of noexcept (which requires termination), this doesn't have a specified
>>>>>> behavior in the C++ standard as far as I can tell.  However, it can lead to
>>>>>> memory leaks & other issues (e.x. with TSAN, it messes up the tracking of
>>>>>> the current stack frame).
>>>>>>
>>>>>>
>>>>>>
>>>>>> I'd be interested in looking into potentially doing the work to add an
>>>>>> option to clang/etc. to terminate when an exception traverses code compiled
>>>>>> with -fno-exceptions, instead of simply allowing the unwinder to walk
>>>>>> through the stack frame & leak memory/etc. (possibly behind a flag?).  This
>>>>>> particular issue bit a team I work closely with, and I'd imagine it could
>>>>>> be causing subtle issues for other clang users.
>>>>>>
>>>>>>
>>>>>>
>>>>>> I'm mostly concerned with solving this on Linux/x86_64, although if
>>>>>> there's a way to solve it more generally I'm open to looking into doing
>>>>>> that instead.
>>>>>>
>>>>>>
>>>>>>
>>>>>> I /think/ the right place to change this (from the discussions I
>>>>>> linked) would be in the LLVM -> assembly layer, adding an appropriate
>>>>>> .gcc_except_table for functions that are determined to be unable to throw
>>>>>> exceptions (either due to noexcept or due to -fno-exceptions). Then the
>>>>>> unwinder would find .eh_frame but no entry in the .gcc_except_table and
>>>>>> should terminate (via  __gxx_personality_v0).
>>>>>>
>>>>>>
>>>>>>
>>>>>> Am I understanding that correctly?  What's the best way to propose
>>>>>> this sort of change to clang? (document/just try to look at putting
>>>>>> together a PR/other?)
>>>>>>
>>>>>>
>>>>>>
>>>>>> Alternatively--one other thing that occurred to me is that it could be
>>>>>> reasonably cheap to simply add try/catch blocks that report an UBSAN error
>>>>>> in all methods that shouldn't be able to throw an exception.  This
>>>>>> obviously doesn't fix the code-generation problem and would lead to larger
>>>>>> binary sizes, but that seems less bad for an UBSAN build in particular.
>>>>>> That would likely meet my needs around wanting a way to automatically
>>>>>> detect this behavior/problem, but might not address the more generic issue.
>>>>>>
>>>>>>
>>>>>>
>>>>>> Thanks,
>>>>>>
>>>>>> --
>>>>>>
>>>>>> --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
>>>>>
>>>> _______________________________________________
>> LLVM Developers mailing list
>> llvm-dev at lists.llvm.org
>> https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-dev
>>
>
>
>-- 
>--EJM

>_______________________________________________
>LLVM Developers mailing list
>llvm-dev at lists.llvm.org
>https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-dev



More information about the llvm-dev mailing list