Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 214
Andreas Grapentin Avatar asked Nov 21 '13 16:11

Andreas Grapentin


3 Answers

Since you're using autotools, you might be in a situation where exclusive use of gcc is feasible. In that case you can apply the `used' attribute to tell gcc to force the linker to include the symbol:

namespace {
    __attribute__ ((used))
    bool registered  = ModuleShare::registerModule<SomeModule>("SomeModule");
}
like image 70
creichen Avatar answered Nov 05 '22 12:11

creichen


Instead of using --whole-archive, have you tried to just add -u registered?

As ld manual states:

-u symbol
   --undefined=symbol
       Force symbol to be entered in the output file as an undefined symbol.  Doing this may, for example, trigger linking of additional modules from standard libraries.  -u may be repeated with different option arguments to
       enter additional undefined symbols.  This option is equivalent to the "EXTERN" linker script command.

Edit: I think that what you try to achieve is quite similar to Qt's plugin management. This article details it a bit. And this is 4.8's official documentation.

Taking into account that convenience libraries are statically build, maybe, it would be enough to create a dummy instance inside this convenience library (or a dummy use of registered) and declare it as extern where you use it (this is what Q_EXPORT_PLUGIN2 and Q_IMPORT_PLUGIN are doing).

like image 5
jcm Avatar answered Nov 05 '22 13:11

jcm


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
}
like image 4
ldav1s Avatar answered Nov 05 '22 13:11

ldav1s