Search code examples
c++autotoolslibtool

libtool linkage - global state initalization of convenience libraries


I have a setup that does not work, and I have no idea what I am doing wrong here - I am trying to convert a project from handcrafted Makefiles to autotools, and I think I have most of it set up correctly, as the application and all its convenience libraries builds and links correctly, but there is some trouble with the global state initializers of the convenience libraries.

Some of the libraries follow a pattern like this in the code:

// in global scope of somemodule.cpp
namespace {
  bool registered  = ModuleShare::registerModule<SomeModule>("SomeModule");
}

this code, along with the actual module source, is compiled into a convenience library using libtool

// libsomething Makefile.am
noinst_LTLIBRARIES = libsomething.la

libsomething_la_SOURCES = \
  [ ... ]
  moduleshare.cpp moduleshare.h \
  somemodule.cpp somemodule.h \
  [ ... ] 

and this library is built, and referenced in the application Makefile.am as follows:

// someapp Makefile.am
bin_PROGRAMS = someapp

someapp_SOURCES = someapp.c someapp.h
someapp_CPPFLAGS = -I ${top_srcdir}/something
someapp_LDADD = ${top_srcdir}/something/libsomething.la

I have modified ModuleShare::registerModule to verify it is not called:

template<typename T>
static bool registerModule(const std::string &module){
  printf("%s\n", module.c_str());

  [ ... ]

  return true;
}

What could be the reason for this?

EDIT:

At this point, I have figured out that this problem is related to the linker that is allowed to remove unused symbols during linkage. If I link manually using --whole-archive, everything works as expected.

Coming from a C background, I also tried

static void
__attribute__((constructor))
register (void)
{
  ModuleShare::registerModule<SomeModule>("SomeModule");
}

but that also does not produce the behaviour that I expected, which is wierd, considering that I rely on this construct a lot in my private C projects.

At this point, I am open to suggestions in any direction. I know that libtool does not provide per-library flags in LDADD, and I can't, and simply don't want to compile everything with --whole-archive just to get rid of these symptoms. I have only limited control over the codebase, but I think what I really need to ask here is, what is a good and reliable way to initialize program state in a convenience library using autotools?

EDIT2:

I think I am a step closer - it seems that the application code has no calls to the convenience library, and hence the linker omits it. The application calls into the library only via a templated function defined in a header file of SomeModule, which relies on the static initializers called in the convenience libraries. This dependency reversion is screwing over the whole build.

Still, I am unsure how to solve this :/

Thanks, Andy


Solution

  • This answer might require too many code changes for you, but I'll mention it. As I mentioned before, the convenience library model is a kind of static linkage where libraries are collated together before final linkage to an encapsulating library. Only in this case the "library" is an executable. So I'd imagine that stuff in the unnamed namespace (static variables essentially), especially unreferenced variables would be removed. With the above code, it worked as I kind of expected it to.

    I was able to get registerModule to print it's message without special linker tricks in a convenience library like this:

    somemodule.cpp

    // in global scope
    namespace registration {
      bool somemodule  = ModuleShare::registerModule<SomeModule>("SomeModule");
    }
    

    someapp.cpp

    // somewhere
    namespace registration {
        extern bool somemodule;
        ... // and all the other registration bools
    }
    
    // in some code that does initialization, possibly
    if (!(registration::somemodule && ...)) {
        // consequences of initialization failure
    }