[RFC] libgfortran dll i/o redirection lossage caused by order-of-termination issue

Dave Korn dave.korn.cygwin@googlemail.com
Thu Jul 9 23:31:00 GMT 2009

Andy Koppe wrote:

> Ouch. That violates the C++98 standard as well, which requires that
> atexit handlers and static destructors are invoked in the opposite
> order that the atexit handlers were registered and the corresponding
> static constructor were invoked, whereby: "A function registered with
> atexit before an object obj1 of static storage duration is initialized
> will not be called until obj1's destruction has completed. A function
> registered with atexit after an object obj2 of static storage duration
> is initialized will be called before obj2's destruction starts."
> In other words: there should be a single stack for both atexit
> handlers and static destructors, with atexit handlers getting pushed
> by atexit(), and static destructors getting pushed at entry to their
> corresponding constructors. No idea whether that's actually feasible
> in connection with DLLs.

  Well, everything's possible, it's just a matter of code.  We basically
almost do have a stack already; first DLL .ctors get run in order of load
dependence, then the exe's .ctors get run, and at exit it's the reverse - with
the single exception of cygwin/newlib, which gets destroyed first of all
libraries, instead of last.

Christopher Faylor wrote:
>>  That's bad.  The call to newlib's __cleanup() hook shuts down stdio
>> facilities, and so when libgfortran DLL's dtors are finally called, they
>> attempt to flush the buffer down already-closed stdio channels, and it gets
>> silently dropped on the floor.
> Yep.  Longstanding issue.  I thought you couldn't guarantee that a
> global destructor would be able to do I/O.

  Do you have a reference?  I couldn't see anything in gABI, n2800 or cxx-abi
that really obviously looked like it said that to me.

> I've fiddled with this code on and off for twelve years now and run into
> the "newlib issue" a couple of times.  This is one of those places where
> I habitually say "Why are we using newlib anyway?" It's one of those
> places where you want the newlib library to somehow know more about
> Cygwin.
> You can't hook important things destructors into atexit() since my
> reading of the linux man page at least (I sense a looming authoratitive
> POSIX citation coming) says that hooks only get called when a function
> calls exit, not when a function calls _exit.  I believe that dtors
> should always be called unless the program is being terminated by a
> signal.

  I may be misinterpreting it, but n2800#

"  Calling the function std::abort() declared in <cstdlib> terminates the
program without executing any destructors and without calling the functions
passed to std::atexit() or std::at_quick_exit().  "

suggests to me it's ok.  abort() really is a quick bail-out, I think.

Corinna Vinschen wrote:
> What speaks technically against calling _GLOBAL_REENT->__cleanup after
> dll_global_dtors?

Christopher Faylor wrote:
> Right now, _GLOBAL_REENT->__cleanup is called by newlib's exit. So we'd
> have to preemptively call it somewhere. Where would that be? Do we move the
> running out of the dtors out of do_exit()?

  I think that could well be the solution.  DLL dtors should be handled by
atexit functionality exactly like the global dtors.

Corinna Vinschen wrote:
> That's just an example of course, but the dtors function of a library
> are usually responsible for the graceful exit, doing the high-level
> cleanup stuff.  But that's exactly not what _exit is supposed to do.
> The idea is an immediate process exit with only the OS-specific cleanup
> like closing file descriptors.  Which is Cygwin's job only.  As a
> worst-case scenario, the dtors function of a database library should
> not be covered by _exit, afaics.

Christopher Faylor wrote:
> True, and that makes me wonder about the fortran implementation.

  I think the fortran implementation is entirely within its rights.  The
fortran runtime for the application exits via exit(), not _exit(), and so it
expects its destructors to get run - and since _exit() doesn't run dtors, it
expects them to be run before _exit().


says clearly that first atexit() functions are run, then file descriptors are
closed.  It doesn't talk about C++ dtors of course, but the cxx-abi says that
__cxa_atexit is required for full ABI conformance, and this must be why; so
that an integrated mechanism can be used for all destruction, and the language
runtime doesn't have to worry about when .dtors sections get run.

  Libfortran on the other hand uses an explicit __attribute__((destructor)) on
the shutdown function.  It should probably be using an atexit registration.

  So... I think the thing I'm going to try is indeed removing
dll_global_dtors() from do_exit(), and I'm going to add an "atexit
(dll_global_dtors)" in __main(), and see how that goes.

  (Also, as a future improvement, implementing __cxa_atexit and making
dlopen/dlclose aware of it might be a good trick).


More information about the Cygwin-developers mailing list