iostream breaks plug-in DSO

From: Exillis (news_at_exillis.com)
Date: 09/08/04


Date: 7 Sep 2004 20:22:22 -0700

Hi,

I believe I've uncovered a bug in the <iostream> shipped with GCC
(which I presume is based off of STLport). The short story is that
including <iostream> in a DSO that is loaded at runtime with dlopen()
causes a segmentation fault on exit from main() after the DSO has
been unloaded with dlclose().

Using strace and Valgrind shows the DSO being loaded/unloaded and
then main() finishing, but not before seg-faulting. The stack
trace shows only the active frame __libc_start_main.

My environment:

  Linux kernel 2.4.21
  gcc 3.3.1
  ld 2.14.90.0.5
  glibc 2.3.2

What I believe is happening is that the global static object
defined in <iostream> is being constructed to initialize the
I/O library:

>From inside <iostream>:

  ...
  static ios_base::Init __ioint;
  ...

When my DSO (which includes <iostream>) is loaded, the constructor
for this static object is called. When the DSO is unloaded the
loader should destroy __ioint (perhaps by registering it's
destructor with atexit() or a similiar function such as __cxa_atexit?).

Here is the minimum code I needed to reproduce it. Please excuse
the lack of error checking, lack of include guards, etc. I've
tried to keep it bare-bones. There should not be typos (I cut and
paste from the listings):

'Makefile.dso' to build the DSO and a base class for my DSO plug-in:
-------------------->8--------------------------------------------------------
all: libPlugin.so Demo.dso

Plugin.o: Plugin.cc

libPlugin.so: Plugin.o
  g++ -Wl,-shared -o$@ Plugin.o

Demo.dso: Demo.o
  g++ -Wl,-shared -o$@ Demo.o

clean:
  rm -f *.o Demo.dso libPlugin.so

.cc.o:
  g++ -fuse-cxa-atexit -fpic -c $<
-------------------->8--------------------------------------------------------

'Makefile.driver' to build the test driver which shows the seg-fault:
-------------------->8--------------------------------------------------------
all: Driver

Driver.o: Driver.cc

Driver: Driver.o libPlugin.so
  g++ -o$@ Driver.o -L. -lPlugin -ldl

clean:
  rm -f *.o Driver.o

.cc.o:
  g++ -fuse-cxa-atexit -c $<
-------------------->8--------------------------------------------------------

'Plugin.h' which is the base class all DSOs (i.e. plug-ins):
-------------------->8--------------------------------------------------------
struct Plugin
{
  virtual ~Plugin() = 0;
  virtual void run() = 0;
};
-------------------->8--------------------------------------------------------

'Plugin.cc' which is simply the destructor definition:
-------------------->8--------------------------------------------------------
#include "Plugin.h"

Plugin::~Plugin()
{ }
-------------------->8--------------------------------------------------------

'Demo.h' is the interface for the sample DSO (subclass of Plugin):
-------------------->8--------------------------------------------------------
#include "Plugin.h"

class Demo : public Plugin
{
  public :

    Demo();
    ~Demo();

    void run();
};
-------------------->8--------------------------------------------------------

'Demo.cc' is the code for the DSO plug-in:
-------------------->8--------------------------------------------------------
#include "Demo.h"

#include <iostream>

extern "C" Plugin* factory()
{
  return new Demo;
}

Demo::Demo()
{
  std::cout << "Demo()\n";
}

void Demo::run()
{
  std::cout << "Running...\n";
}

Demo::~Demo()
{
  std::cout << "~Demo()\n";
}
-------------------->8--------------------------------------------------------

'Driver.cc' is the main test driver which loads the DSO and shows the bug:
-------------------->8--------------------------------------------------------
#include "Plugin.h"

#include <dlfcn.h>

int main()
{
  void* dso (dlopen ("Demo.dso", RTLD_NOW));

  Plugin* (*factory)();
  factory = (Plugin*(*)())(dlsym (dso, "factory"));

  Plugin* plugin (factory());

  plugin->run();

  delete plugin;

  dlclose (dso);

  return 0;
}
-------------------->8--------------------------------------------------------

Note that this is a minimal plug-in system with a factory method
for creating concrete DSO objects. It's as stripped down as it
can get (we're not talking COM here).

Running this driver will cause a crash when main() exits:

  $ Driver
  Demo()
  Running...
  ~Demo()
  Segmentation fault

There are three things I could do to work around this. Doing any of
these things causes the code to work without crashing:

1) Comment out the dlclose(). I don't want to do that.
   I really do want the DSO to be unmapped from memory.

2) Replace -fuse-cxa-atexit with -fno-use-cxa-atexit.
   I don't want to do this. I want this code to comply to
   the standard C++ ABI.

3) Remove the use of <iostream> from Demo.cc and replace it
   with <stdio.h>. (Which implies replacing the std::cout with printf).
   I should not have to do this.

None of these solutions should be necessary. In addition, this same
code works perfectly on Solaris 9 and FreeBSD 5.2.1 (they don't have
__cxa_atexit so I had to leave this flag off...that may explain why
this worked on those systems).

To remove any doubt from my mind that the code is at fault I built
this on BeOS (using image add-ons) and Windows XP (using LoadLibrary()
and company). Both platforms also worked without crashing as I had
expected, though the semantics of DSOs are slightly different on
these platforms.

So it seems that on Linux on or more of the following are the culprits:

1) ld is broken.
2) glibc is broken.
3) <iostream> should not create a global static object in file scope.

To remove doubt about the compiler version, I recompiled everything
with gcc 3.4.1. The crash still occurs. I don't think it's a code
generation bug.

Can anyone reproduce this crash? Anyone have ideas about how to
further work around this or why this shouldn't work? It's only my
assumption that <iostream> is the culprit. I've seen no postings
about <iostream> having bugs.

I'd appreciate any help with getting this working or pointing
out a flaw in my thought process. Maybe I'm making a few assumptions
I shouldn't make. I know that C++ has nothing to say of DSOs in
the standard, but it seems that static scoped objects should pose
no real problems for a loader. The dlopen() and dlclose() should
take care of everything (i.e. initializing and cleaning up after
static objects at file or function scope). It may not be in
"The Standard" but it seems a reasonable expectation to have.

Thanks,
-Michael