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
So yes--that would work.I think for that route, though, it would be probably useful to try to output some more information than just std::terminate does. (i.e. that would work as a proof of concept, but getting more debug information the way you do with noexcept functions or functions without a handler would be more user friendly).
The other reason I was hesitant to go that route is that I was worried about the impact of that on binary size (as that's... I think 4+ bytes per function added for the .gcc_except_table sections, as opposed to just 4 bytes for the empty section). That seems fine for a small binary, but seems likely to be expensive-to-prohibitive for large binaries/libraries.
_______________________________________________
LLVM Developers mailing list
llvm...@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-dev
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?
> - 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.
+1. This definitely needs to be an opt-in feature over the current default behavior.
> 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".
Fully agreed. Mixing -fno-exceptions code with -fexceptions code is already dangerous and an anti-pattern. Propagating an exception successfully past code which is -fno-exceptions is asking for problems when LLVM has been explicitly instructed to build with the knowledge that this scenario will not occur. To that end, a “non-strict” mode means the compiler has to reason about exceptions with -fno-exceptions passed which makes this seem more of a sub-flag for -fexceptions funnily enough.
> I have verified that changing the personality function to std::terminate during code generation (via a boolean flag/etc.) does exactly what I'm looking for (but also has an impact on intermediate sizes of about what I expected).
Nice! I’m curious about the impact of sizes. The relevant CIE + FDE with exceptions look like the following:
From llvm-dwarfdump:
Has handler:
000000a8 0000001c ffffffff CIE
Version: 1
Augmentation: "zPLR"
Code alignment factor: 1
Data alignment factor: -8
Return address column: 16
Personality Address: 00201a50 // gxx_personality_v0
Augmentation data: 03 50 1A 20 00 03 1B
DW_CFA_def_cfa: reg7 +8
DW_CFA_offset: reg16 -8
DW_CFA_nop:
DW_CFA_nop:
000000c8 00000024 00000024 FDE cie=00000024 pc=00001120...0000117f
LSDA Address: 002006b0
No Handler:
00000000 00000014 ffffffff CIE
Version: 1
Augmentation: "zR"
Code alignment factor: 1
Data alignment factor: -8
Return address column: 16
Augmentation data: 1B
DW_CFA_def_cfa: reg7 +8
DW_CFA_offset: reg16 -8
DW_CFA_nop:
DW_CFA_nop:
00000018 00000010 0000001c FDE cie=0000001c pc=fffffc00...fffffc2f
DW_CFA_advance_loc: 4
DW_CFA_undefined: reg16
Registering terminate/custom handler as a handler allows you to bypass the “L” Augmentation (since you don’t need an LSDA in such cases) so the total cost should be:
So it should add 6 bytes to every function. You can’t get away from adding an address to each function’s .eh_frame entry with how CIE/FDE works. I think the extreme that is possible is encoding this scenario in a new character in the Augmentation String but that will severely limit what you can do outside of directly calling abort() along with changing the ABI 😃.
> From looking at the current location of exception-handling code, would it make more sense to have the personality function in libcxxabi (where __gxx_personality_v0 and friends live) or in compiler-rt (which doesn't seem to have any exception handling code at the moment)?
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.
Modi
Actually a single CIE can host multiple FDEs corresponding to multiple functions so it should be less than 6 bytes per function.
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.
The gcc_personality_v0 in compiler-rt is actually kind of an oddball. It maps to a custom GCC extension `__attribute__((cleanup(xx)))` (https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html) which is “destructors but for C” which has to work if you have:
C++ code calling C code calling C++ code
To ensure that this attribute is correctly maintained when throwing from C++ to C++ code with C code sandwiched in the middle.
For (1) if the exception type desired is the C++ exception type, then that’ll definitely move this into libcxxabi. There’s a lower level code (https://github.com/llvm-project/llvm-project-dev/blob/master/libcxxabi/src/cxa_exception.hpp#L23) that exists to identify if an exception is from Clang/GCC/MSVC which would still be suitable for compiler-rt.
I think the rest of nice-to-have also move it towards libcxxabi. Stack unwinding and function context for C++ have ABI specifications that lock it into a C++ runtime library.
My suggestion would be to get a std::terminate implementation with acceptable size bloat tested and working. That by itself can be reviewed and is self-contained such that a more robust solution can easily take its place later. As Hubert points out, you can also override std::terminate with https://en.cppreference.com/w/cpp/error/set_terminate so there’s quite some flexibility there.
Adding runtime code is (in my experience) pretty difficult because of backwards-compatibility and the larger surface area.
Modi
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 :)
>>>>>> *From: *llvm-dev <llvm-dev...@lists.llvm.org> on behalf of
>>>>>> Everett Maus via llvm-dev <llvm...@lists.llvm.org>
>>>>>> *Reply-To: *Everett Maus <evm...@google.com>
>>>>>> *Date: *Monday, December 7, 2020 at 12:47 PM
>>>>>> *To: *"llvm...@lists.llvm.org" <llvm...@lists.llvm.org>
>>>>>> *Subject: *[llvm-dev] Catching exceptions while unwinding through
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.
- 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.
- 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.
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".
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.
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 :)
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.