5

I am using Mingw-w64 v7 and g++ 10.2 on Windows built with the arguments --mode=gcc-10.2.0 --arch=x86_64 --buildroot=/c/mingw-builds/BuildRoot --update-sources --exceptions=seh --threads=posix --enable-languages=c++ --jobs=48 --rt-version=v7. I have downloaded and unpacked bzip2 1.0.6 from here: https://sourceforge.net/projects/bzip2/. Now I build boost 1.76.0 like this in a MSys2 console:

./bootstrap.sh --with-libraries=iostreams --prefix=c:/Libraries/boost_1_76_0/InstallRelease
./b2 --layout=system variant=release link=shared address-model=64 threading=multi optimization=speed runtime-link=shared -s BZIP2_SOURCE=C:/Libraries/bzip2-1.0.6 install

I would like use Boost.Iostreams to compress some streams with bzip2. Therefore I create a test file main.cpp with this content:

#include <iostream>
#include <fstream>
#include <boost/iostreams/filtering_stream.hpp>
#include <boost/iostreams/filter/bzip2.hpp>

int main() 
{
    {
        std::ofstream file("hello.z", std::ios_base::out | std::ios_base::binary);
        boost::iostreams::filtering_ostream out;
        out.push(boost::iostreams::bzip2_compressor());
        out.push(file);
        
        out << "Hello World!" << std::endl;
    }

    {
        std::ifstream file_in("hello.z", std::ios_base::in | std::ios_base::binary);
        boost::iostreams::filtering_istream in;
        in.push(boost::iostreams::bzip2_decompressor());
        in.push(file_in);
        
        std::string content(std::istreambuf_iterator<char>(in), {});
        std::cout << content << std::endl;
    }
    
    return 0;
}

If I compile it with

 g++ main.cpp -o main.exe --std=c++11  -lboost_iostreams -I/c/libraries/boost_1_76_0/InstallRelease/include -L/c/libraries/boost_1_76_0/InstallRelease/lib

set the path on an Msys2 console with

export PATH=/c/Libraries/boost_1_76_0/InstallRelease/lib:/c/mingw-w64-10.2/mingw64/bin

and run it, I get

$ ./main.exe
Hello World!

Now if I delete the directories bin.v2 and InstallRelease from the boost_1_76_0 directory and build boost again, but this time with cflags=-flto like this:

./b2 --layout=system variant=release link=shared address-model=64 threading=multi optimization=speed runtime-link=shared cflags="-flto" -s BZIP2_SOURCE=C:/Libraries/bzip2-1.0.6 install

and run main.exe again, I get:

$ ./main.exe
Segmentation fault

This is not the case, if I use boost::iostreams::zlib_(de)compressor instead. If I build boost with debug information like this:

./b2 --layout=system variant=debug link=shared address-model=64 runtime-link=shared cflags="-flto" -s BZIP2_SOURCE=C:/Libraries/bzip2-1.0.6 install

and run main.exe within gdb, the output is like this:

Starting program: C:\...\main.exe #
[New Thread 7460.0x1dec]
[New Thread 7460.0x2508]
[New Thread 7460.0x2cb4]

Thread 1 received signal SIGSEGV, Segmentation fault.
0x0000000000000000 in ?? ()
(gdb)

Trying to print a backtrace is not successful, even if main.exe has been compiled with the arguments -O0 -g:

(gdb) bt
#0  0x0000000000000000 in ?? ()
Backtrace stopped: previous frame identical to this frame (corrupt stack?)

What does that mean? Is link-time optimization not as harmful as I had thought? Where is the bug? In the compiler? In Boost.Iostreams? Have I made an error? Which one? Or how can I figure out what actually has caused the segmentation fault?

Benjamin Bihler
  • 1,612
  • 11
  • 32
  • I faced with the similar thing, but in another context. What I found in my case is [HeapAlloc](https://learn.microsoft.com/en-us/windows/win32/api/heapapi/nf-heapapi-heapalloc) from msvcrt.dll was a root cause of the issue. Then I've changed memory allocator from default to jemalloc - and error gone. Interesting thing, beside lto same effect can be replicated with havy multi-threading calls (i.e. up 16 threads) to HeapAlloc. Default malloc and free on MinGW uses msvcrt heap allocation. 2. Also you can try to switch distribution of MinGW, for example MSYS2 it links to universal crt. – Victor Gubin Aug 10 '21 at 10:59
  • You can try to add `-g` flag to main.exe build command line (to add debug info) and invoke `bt` command in gdb when segfault happens to see the call stack. Maybe it will show some something useful. – dewaffled Aug 10 '21 at 11:07
  • @dewaffled I have tried that and added the output to the question. The output is: `Backtrace stopped: previous frame identical to this frame (corrupt stack?)`. – Benjamin Bihler Aug 10 '21 at 11:23
  • @VictorGubin The debugger output seems to show that the segmentation fault occurs at application startup, doesn't it? In this case no heap allocation has taken place. Do you still think that the MS VC++ runtime may be the culprit? – Benjamin Bihler Aug 10 '21 at 11:26
  • By process of elimination you have pretty much narrowed down the issue to one of boost's classes. That's pretty much as far as it can go, without diving into Boost's source. Given that the segfault occurs at startup, my bet would be on static initialization order fiasco being the problem. – Sam Varshavchik Aug 10 '21 at 11:58
  • @SamVarshavchik What should I do? Create a boost issue report? – Benjamin Bihler Aug 10 '21 at 12:23
  • You could do that, you have a short enough program, a reproducer, that you can attach to the bug report. If the crash requires a particular input file, that can be attached as well. You can also try building the code on Linux and see if it also crashes. Linux will have better runtime debugging information available. – Sam Varshavchik Aug 10 '21 at 12:26
  • @VictorGubin I highly doubt that `HeapAlloc` in multi-threaded applications fails, rather it was more likely a bug on your side. – ssbssa Aug 10 '21 at 12:39
  • @SamVarshavchik On my Linux here (g++ 8.3.0) I am not able to reproduce the error. I have reported an issue: https://github.com/boostorg/iostreams/issues/135 – Benjamin Bihler Aug 10 '21 at 12:49
  • @Benjamin Bihler look into CRT code, you'll see CRT actually allocates memory on startup. – Victor Gubin Aug 11 '21 at 07:39
  • @VictorGubin I have tried to build MinGW-w64 with ucrt to verify that. Unfortunately the build fails, I don't really understand why and I probably will not have the time to continue that. Anyway, what would be the outcome of such an experiment? On Linux the same code doesn't lead to a segmentation fault. But if Sam Varshavchik was right and there was a static initialization order fiasco, then many changes to the application might lead **by chance** to a working application. This does not prove that there has been a bug in the changed setting! – Benjamin Bihler Aug 12 '21 at 06:39

0 Answers0