https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67918
Bug ID: 67918 Summary: -fdevirtualize causes binary to crash with Segmentation Fault (CryptoPP involved) Product: gcc Version: 5.2.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c++ Assignee: unassigned at gcc dot gnu.org Reporter: 11throwaway11 at outlook dot com Target Milestone: --- Created attachment 36473 --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=36473&action=edit preprocessed file Let me preface this bug report by saying that the following code compiles just fine on clang with -O3, on MSVC with their maximum optimizations and on g++ with -O1. And notice what weird steps (mentioned below) make the gcc build a binary that works well. I'll try to make this report as useful as I can, but keep in mind that I'm kinda rookie with compiling/debugging on Linux. I've removed as much code from my project as possible to still be able reproduce the bug. Then I noticed something strange: removing declarations&definitions of functions that are not even called anywhere (but they were used in my original project) fixes the problem, so I made a file called fix1.cpp w/o these functions. After using valgrind with my original project, I remember it pointed to the destructors of CryptoPP::*::(En-/De-)cryption objects. So I thought, what if I allocate those dynamically instead of statically and do not use 'delete' (=> never call the destructors), it worked. Then I added delete operators, and it still worked. Weird. So I made a file called fix2.cpp containing this approach. (but in this minimal testcase valgrind points to the destructor of the basic_string, although dynamically allocating CryptoPP's objects and still statically allocating std::string does the job, thus STL has NOTHING to do with it). Then I learned about -fsanitize=undefined, tried it: "execution reached __builtin_unreachable() call", tried to search what exactly could cause this, didn't find anything remotely useful. So I thought, since clang compiles it just fine, what if I use their -fsanitize=undefined, got: "null pointer passed as argument" and it pointed to a /cryptopp/misc.h, line #149. They were calling memcpy without nullptr check. Alright, fine, thought I. Added such a check. Recompiled with clang, fsanitize was silent this time. So it's time to recompile with gcc, thought I. No, still crashes. Still the same "__builtin_unreachable()". But it's silent for binaries created from fix1.cpp and fix2.cpp. -Wall -Wextra shows no warnings. -fno-strict-aliasing -fwrapv -fno-aggressive-loop-optimizations makes no difference. Although I checked what's the difference between -O1 and -O2 and narrowed it down to -fdevirtualize. -O1 -fdevirtualize makes testcase.cpp create a corrupted binary. And it makes the same thing with fix1.cpp, but it worked fine with -O2. Alright, -O1 -fdevirtualize -fdevirtualize-speculatively makes fix1.cpp create a correct binary. Here's what I use to compile: g++ --std=c++11 -O3 -g -Wall -Wextra -save-temps -lcryptopp -lpthread Seems I can't attach more than 1 file (I'll try to add remaining .ii files in the comments though), so here's the link to a dropbox folder with everything (temp files, binaries compiled with -g, sources, makefile, gdb/valgrind/fsanitize outputs): https://www.dropbox.com/sh/0vr8i0mwuf2guk4/AADgQDd3S24v2Z7JzNejD71ja?dl=0 Below are the outputs of gcc -v, gdb bt and valgrind. $ g++ -v Using built-in specs. COLLECT_GCC=g++ COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-unknown-linux-gnu/5.2.0/lto-wrapper Target: x86_64-unknown-linux-gnu Configured with: /build/gcc/src/gcc-5.2.0/configure --prefix=/usr --libdir=/usr/lib --libexecdir=/usr/lib --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=https://bugs.archlinux.org/ --enable-languages=c,c++,ada,fortran,go,lto,objc,obj-c++ --enable-shared --enable-threads=posix --enable-libmpx --with-system-zlib --with-isl --enable-__cxa_atexit --disable-libunwind-exceptions --enable-clocale=gnu --disable-libstdcxx-pch --disable-libssp --enable-gnu-unique-object --enable-linker-build-id --enable-lto --enable-plugin --enable-install-libiberty --with-linker-hash-style=gnu --enable-gnu-indirect-function --disable-multilib --disable-werror --enable-checking=release --with-default-libstdcxx-abi=gcc4-compatible Thread model: posix gcc version 5.2.0 (GCC) (gdb) file testcase Reading symbols from testcase...done. (gdb) run Starting program: /home/nick/Documents/CryptoPP/TESTCASE/testcase [Thread debugging using libthread_db enabled] Using host libthread_db library "/usr/lib/libthread_db.so.1". Program received signal SIGSEGV, Segmentation fault. _Unwind_Resume (exc=exc@entry=0x0) at /build/gcc/src/gcc-5.2.0/libgcc/unwind.inc:229 229 /build/gcc/src/gcc-5.2.0/libgcc/unwind.inc: No such file or directory. (gdb) bt #0 _Unwind_Resume (exc=exc@entry=0x0) at /build/gcc/src/gcc-5.2.0/libgcc/unwind.inc:229 #1 0x0000000000408c97 in std::string::_Rep::_M_dispose (__a=..., this=<optimized out>) at /usr/include/c++/5.2.0/bits/basic_string.h:2636 #2 AESEncrypt (source="test", key=key@entry=0x7fffffffe7f0 "123456789012345", keylength=keylength@entry=16, iv=iv@entry=0x7fffffffe800 "1234567890123456") at /usr/include/c++/5.2.0/bits/basic_string.h:2943 #3 0x00000000004086f6 in main () at testcase.cpp:23 $ valgrind ./testcase ==1227== Memcheck, a memory error detector ==1227== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al. ==1227== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info ==1227== Command: ./testcase ==1227== ==1227== Invalid read of size 8 ==1227== at 0x5CFB3D7: _Unwind_Resume (unwind.inc:229) ==1227== by 0x408C96: _M_dispose (basic_string.h:2636) ==1227== by 0x408C96: AESEncrypt(std::string const&, unsigned char*, unsigned long, unsigned char*) (basic_string.h:2943) ==1227== by 0x4086F5: main (testcase.cpp:23) ==1227== Address 0x10 is not stack'd, malloc'd or (recently) free'd ==1227== ==1227== ==1227== Process terminating with default action of signal 11 (SIGSEGV) ==1227== Access not within mapped region at address 0x10 ==1227== at 0x5CFB3D7: _Unwind_Resume (unwind.inc:229) ==1227== by 0x408C96: _M_dispose (basic_string.h:2636) ==1227== by 0x408C96: AESEncrypt(std::string const&, unsigned char*, unsigned long, unsigned char*) (basic_string.h:2943) ==1227== by 0x4086F5: main (testcase.cpp:23) ==1227== If you believe this happened as a result of a stack ==1227== overflow in your program's main thread (unlikely but ==1227== possible), you can try to increase the size of the ==1227== main thread stack using the --main-stacksize= flag. ==1227== The main thread stack size used in this run was 8388608. ==1227== ==1227== HEAP SUMMARY: ==1227== in use at exit: 72,769 bytes in 4 blocks ==1227== total heap usage: 4 allocs, 0 frees, 72,769 bytes allocated ==1227== ==1227== LEAK SUMMARY: ==1227== definitely lost: 0 bytes in 0 blocks ==1227== indirectly lost: 0 bytes in 0 blocks ==1227== possibly lost: 0 bytes in 0 blocks ==1227== still reachable: 72,769 bytes in 4 blocks ==1227== of which reachable via heuristic: ==1227== stdstring : 57 bytes in 2 blocks ==1227== suppressed: 0 bytes in 0 blocks ==1227== Rerun with --leak-check=full to see details of leaked memory ==1227== ==1227== For counts of detected and suppressed errors, rerun with: -v ==1227== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)