Created attachment 14870 [details] simple test case to reproduce exception handling crash I've seen a number of crashes in the code for OpenOffice whose commonality is exception handling. This only occur when the code is compiled with -Os optimization and only on i386. The code operates correctly when compiled with -O2 optimization. If the code is compiled on amd64, it operates correctly with any optimization level. Compiling with either of these compiler versions results in crashing executables: clang version 3.7.0 (tags/RELEASE_370/final) Target: i386-unknown-freebsd11.0 Thread model: posix FreeBSD clang version 3.6.1 (tags/RELEASE_361/final 237755) 20150525 Target: i386-unknown-freebsd11.0 Thread model: posix Clang versions 3.4 and 3.5 do not exhibit this problem. The attached code fails with this stack trace when compiled with DEBUG undefined: % c++ -Os clangexceptionbug.cxx % gdb a.out GNU gdb 6.1.1 [FreeBSD] Copyright 2004 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-marcel-freebsd"...(no debugging symbols found)... (gdb) run Starting program: /home/dl/a.out (no debugging symbols found)...(no debugging symbols found)...(no debugging symbols found)...(no debugging symbols found)...(no debugging symbols found)...(no debugging symbols found)... Program received signal SIGSEGV, Segmentation fault. 0x08048808 in typeinfo for X () (gdb) bt #0 0x08048808 in typeinfo for X () #1 0x080487ba in main () (gdb) With DEBUG defined: % c++ -Os -DDEBUG clangexceptionbug.cxx % gdb a.out GNU gdb 6.1.1 [FreeBSD] Copyright 2004 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-marcel-freebsd"...(no debugging symbols found)... (gdb) run Starting program: /home/dl/a.out (no debugging symbols found)...(no debugging symbols found)...(no debugging symbols found)...(no debugging symbols found)...(no debugging symbols found)...(no debugging symbols found)...X ctor this=0x28612044 caught exception X dtor this=0x28612044 X dtor this=0xffffdbd8 Program received signal SIGSEGV, Segmentation fault. 0xffffdbf8 in ?? () (gdb) bt #0 0xffffdbf8 in ?? () #1 0xffffdbd8 in ?? () #2 0x0804879a in _start1 () Previous frame inner to this frame (corrupt stack?) (gdb) In the OpenOffice code I saw a case where the value of "this" in a destructor was offset by 4 bytes from the value of "this" in its matching constructor, but I was unable to produce a simple test case to reproduce that.
This is probably the mov -> push call frame conversion that's enabled under -Os.
Yikes. We generate the following sequence: .Ltmp0: subl $4, %esp pushl $_ZN1XD2Ev pushl $_ZTI1X pushl %esi calll __cxa_throw addl $16, %esp The problem is since that _cxa_throw does not return, adjusting esp back to where it should be never happens. GCC seems to restore the stack pointer once it finishes handling the exception, but I don't really understand EH well enough to make sense of how/where it should be restored. Reid, David, any pointers?
Make sure the DWARF CFI that describes how to unwind the stack is valid at the point of the call. If we really wanted to be rigorous and allow stack unwinding at each PC, we'd insert CFI after each new SP adjustment.
GCC doesn't appear to emit any CFI directives when pushing call parameters - including the parameters to __cxa_throw. Attaching the assembly produced by GCC, for reference.
Created attachment 14886 [details] Assembly produced by GCC
GCC is using a frame pointer, so it doesn't have to. Does LLVM use a frame pointer in this case?
No, we don't - and even if we force using an FP, that's still not enough, since by default we try to recover the original SP at function exit by adding to to the stack pointer, not by using the frame pointer. Forcing both does the right thing for the test case, but: a) I'm not sure whether changing the FP behavior really should be the fix. b) In any case, I'm not sure what the right condition to force using an FP would be. Right now, we force FP only for (looking at EH-related stuff) "MMI.callsUnwindInit() || MMI.hasEHFunclets() || MMI.callsEHReturn()".
Using the FP would fix the test case, IMO, because it changes the CFI we emit. Basically, if we're using an FP, the CFI is really simple. It only has to describe where the caller's return address is and where all the CSRs were pushed. If we're *not* using the FP, we need *really* complicated CFI that describes *every* adjustment to SP (each push and sub). The unwinder is going to be looking for the next return address at some offset relative to ESP instead of EBP in this case, and now ESP is changing dynamically.
I don't understand EH yet, so the below may be total nonsense, but: 1) I think in this simple case the problem isn't the unwinder - the missing stack adjustment is our own. That is, the issue isn't with CFI, it's that we directly generate bad stack adjustments in the landing pad. But we will have CFI issues in more complicated scenarios. 2) In general, that means that for any (non-leaf, but all of this is only relevant for non-leaf anyway) function that's *not* marked nounwind (is this the right condition?), we have to either: a) Use pushes, setting CFI up correctly for each push. b) Give up on the push optimization, unless we already have FP for another reason. c) Force FP. (a) sounds fairly horrible, and counter-productive. Out of the two remaining options, I'm not sure whether (b) or (c) are preferable.
The mechanism that the runtime uses to restore %esp to the proper value following an exception is the GNU_ARGS_SIZE setting. There is an GNU extension to the DWARF call frame information that sets GNU_ARGS_SIZE. Here is an excerpt from the 64-bit ABI: ----- Outgoing arguments area delta To maintain the size of the temporarily allocated outgoing arguments area present on the end of the stack (when using push instructions), operation GNU_ARGS_SIZE (0x2e) can be used. This operation takes a single uleb128 argument specifying the current size. This information is used to adjust the stack frame when jumping into the exception handler of the function after unwinding the stack frame. Additionally the CIE Augmentation shall contain an exact specification of the encoding used. It is recommended to use a PC relative encoding whenever possible and adjust the size according to the code model used. ----- You can see that the gcc-generated code is using this. From your gcc24792.s. The .cfi_escape is setting GNU_ARGS_SIZE to 16. ----- .LEHB0: .cfi_escape 0x2e,0x10 call __fprintf_chk .LEHE0: ----- I suspect that if you make sure GNU_ARGS_SIZE gets set correctly, you won't need to force a frame pointer.
Thanks for the info! I guess it is a CFI problem, but not the one I thought it was. :)
Thanks, Dave! It still doesn't make any sense to me, though. The .cfi_escape applies to the outgoing parameters of __fprintf_chk, but I think those get cleaned up once it returns. The problem is with the parameters to __cxa_throw itself - and there's no additional .cfi_escape there. What am I missing?
The GNU_ARGS_SIZE setting remains in effect until changed (in much the same way that the callee-save-register locations remain in effect until changed). So the GNU_ARGS_SIZE=16 setting prior to the call to __fprintf_chk also applies to the call to __cxa_throw. Note too that for synchronous EH, it is only necessary that the call frame information be accurate at call sites, so there is no need to reset GNU_ARGS_SIZE between the two calls.
Should be fixed in r249522.