This is an archive of the discontinued LLVM Phabricator instance.

[libc++][static linking] std streams are not initialized prior to their use in static object constructors
AbandonedPublic

Authored by ldionne on Sep 8 2015, 2:59 AM.

Details

Reviewers
eastig
Group Reviewers
Restricted Project
Summary

When an executable (e.g. for bare metal environment) is statically linked with libc++ the following code might not work as expected:

#include <iostream>
 
class Foo {
public:
  Foo(int n) {
    std::cout << "Hello World - " << n << std::endl;
  }
};
 
Foo f(5);
 
int main() {
  return 0;
}

The program hangs or crashes because 'std::cout' is not initialized before 'Foo f(5)'.

The standard says:

27.3 Standard iostream objects
Header <iostream> synopsis
(2) ... The objects are constructed, and the associations are established at some time prior to or during first
time an object of class basic_ios<charT,traits>::Init is constructed, and in any case before the body of main
begins execution [264]. The objects are not destroyed during program execution [265].

And footnote 265 says:

Constructors and destructors for static objects can access these objects to read input from stdin or write output
to stdout or stderr.

The similar issue was raised more than year ago. A patch was proposed but not committed. See discussion of it here:

initial patch
review, suggestion for #ifdef

The proposed patch caused initialization/deinitialization of the std streams as many times as the static Init objects were created.
The current patch fixes this issue. A number of uses is counted by means of __shared_count. It is used instead of a 'int' variable because it is thread-safe. The standard allows multi-threaded initialization of static objects from different compilation modules.

Diff Detail

Event Timeline

eastig updated this revision to Diff 34198.Sep 8 2015, 2:59 AM
eastig retitled this revision from to [libc++][static linking] std streams are not initialized prior to their use in static object constructors.
eastig updated this object.
eastig added a subscriber: cfe-commits.
rnk added a subscriber: rnk.Sep 10 2015, 8:41 AM

I think a better approach would be to use __attribute__((init_priority(101))) on Linux and #pragma init_seg(lib) on Windows to ensure that libc++'s iostream initializer runs earlier.

In D12689#243295, @rnk wrote:

I think a better approach would be to use __attribute__((init_priority(101))) on Linux and #pragma init_seg(lib) on Windows to ensure that libc++'s iostream initializer runs earlier.

Thank you for advice.

I found this discussion of init_priority: Clarification of attribute init_priority
https://gcc.gnu.org/ml/gcc-help/2011-05/msg00220.html

It looks like it is not reliable method (https://gcc.gnu.org/ml/gcc-help/2011-05/msg00221.html):

Note that although the documentation doesn't seem to mention it, the
init_priority attribute only works correctly when using the GNU linker
or gold.

Gcc libc++ does not use it.

rsmith added a subscriber: rsmith.Sep 10 2015, 11:46 AM

Can we instead fix this in Clang by ensuring that libc++ is put at the right position in the static link order so that its initializers run first? libc++'s avoidance of running iostreams init code from every translation unit is a significant startup performance feature, and we shouldn't abandon it unless we really have to.

A testcase would be good, regardless of which of the proposed fixes ends up being chosen.

Can we instead fix this in Clang by ensuring that libc++ is put at the right position in the static link order so that its initializers run first? libc++'s avoidance of running iostreams init code from every translation unit is a significant startup performance feature, and we shouldn't abandon it unless we really have to.

Let me give more details about the problem we have.

Clang generates a __cxx_global_var_init<X> function for each global variable that needs to be set-up (initialized) before firing the main routine of the user program. Those initializer functions are then grouped under a parent initializer function named after the corresponding translation unit: _GLOBAL__sub_I_<translation_unit>. Those _GLOBAL__sub_I_<translation_unit> entries then get stuffed into the .init_array tables of individual object files.

In ARM compiler toolchains armlink is used for linking, not GNU ld. libc++ is a part of the toolchain static system libraries. So armlink logic should be updated to change the order in which the .init_array entries from system libraries are added to the final image's .init_array table. This can be a problem because of an assumption that there are no dependencies among initializers from different translation units.

IMHO if the programming language has a means of resolving such problems it's better to use it instead of hacking a linker.

About impact on startup performance, I don't see why it will be significant. Initialization is done once. Other times it is simply a call to increase a counter. To be significant there should be millions of calls. Why does gnu libc++ use a similar way if it hurts performance?

In the patch the __APPLE__ macro is used to have the old behaviour. Maybe instead of it another macro, e.g. __STATIC_BUILD__, can be use when we want to build a static library.

A testcase would be good, regardless of which of the proposed fixes ends up being chosen.

I will write it.

eastig updated this revision to Diff 34973.Sep 17 2015, 2:51 AM

Added tests

rnk added a comment.Sep 21 2015, 8:46 AM

I think you need to address the feedback about avoiding dynamic
initialization on stock non-Mac systems.

Sent from phone

In D12689#249899, @rnk wrote:

I think you need to address the feedback about avoiding dynamic
initialization on stock non-Mac systems.

Sent from phone

In my comment above I proposed a special macro to distinguish between dynamic and static linking:

In the patch the __APPLE__ macro is used to have the old behaviour. Maybe instead of it another macro, e.g. __STATIC_BUILD__, can be use when we want to build a static library.

Is it good to have such a macro?

hfinkel added a subscriber: hfinkel.Mar 9 2017, 5:45 PM

We do need to fix this bug; I posted to the PR so we can discuss approach there.

ldionne commandeered this revision.Sep 16 2020, 10:31 AM
ldionne added a reviewer: eastig.
ldionne added a subscriber: ldionne.

This is superseded by https://reviews.llvm.org/D31413. Closing to clear up the review queue.

ldionne abandoned this revision.Sep 16 2020, 10:32 AM
ldionne added a reviewer: Restricted Project.
This comment was removed by eastig.

Abandoned in favour of https://reviews.llvm.org/D31413

Sorry, we had a race condition :-)