如何让GNU __attribute__((constructor))在库中工作?

3
我可以翻译这段文字。这是关于IT技术的内容。如果我将所有目标文件链接在一起进行单个链接,那么我可以让GNU __attribute__((constructor))(C++程序)正常工作,但是如果我将包含构造函数的目标文件存储在库中,然后链接库而不是目标文件,它就无法正常工作了。我做错了什么?以下是Makefile.am:
SUBDIRS = src

src/Makefile.am:

bin_PROGRAMS = hello
hello_SOURCES = hello.cc register.cc register.hh myfunc.cc

src/hello.cc:

#include <iostream>             // for cout
#include <map>

#include "register.hh"

int main(int argc, char* argv[])
{
  std::cout << "Hello, World!" << std::endl;
  std::cout << "Have " << functions.size() << " functions registered."
    << std::endl;
  for (Function_map::iterator it = functions.begin(); it != functions.end(); ++it) {
    std::cout << "Registered " << (*it).first << std::endl;
    (*it).second();
  }
  return 0;
}

src/register.cc:

#include <map>
#include <string>

#include "register.hh"

Function_map functions;

void register_function(const std::string& name, Function f)
{
  functions[name] = f;
}

src/register.hh:

#ifndef REGISTER_H_
#define REGISTER_H_

#include <map>
#include <string>

typedef void (*Function)();

typedef std::map<const std::string, Function> Function_map;
extern Function_map functions;

void register_function(const std::string& name, Function f);

#endif

src/myfunc.cc:

#include "register.hh"

#include <iostream>

void myfunc()
{
  std::cout << "This is myfunc!" << std::endl;
}

__attribute__((constructor))
void register_myfunc()
{
  register_function("MYFUNC", myfunc);
}

configure.ac:

AC_PREREQ([2.69])
AC_INIT([hello], [1.4], [bugs@my.domain])
AC_CONFIG_SRCDIR([src/hello.cc])
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_AUX_DIR([auxiliary])
AM_INIT_AUTOMAKE([-Wall -Werror])

AC_PROG_CXX
AM_PROG_AR

AC_CONFIG_FILES([Makefile
                 src/Makefile])
AC_OUTPUT

所有的C++文件都会被编译成目标文件,并链接到一起形成“hello”可执行文件。
由此产生的“hello”程序的输出为:
Hello, World!
Have 1 functions registered.
Registered MYFUNC
This is myfunc!

如果我修改src/Makefile.am为:
bin_PROGRAMS = hello
hello_SOURCES = hello.cc register.cc register.hh
hello_LDADD = liblibrary.a

noinst_LIBRARIES = liblibrary.a
liblibrary_a_SOURCES = myfunc.cc

即,myfunc.cc编译成myfunc.o,存储在liblibrary.a中,然后将其与其他目标文件链接到“hello”中,然后从“hello”输出

Hello, World!
Have 0 functions registered.

那么现在 'register_myfunc' 函数为什么没有被执行?

2015-02-22编辑(回应Basile Starynkevitch的答案):我正在使用GNU / Linux(Fedora 20)系统。我尝试使用libtools构建共享库,但没有成功。 我按如下方式调整了src/Makefile.am:

bin_PROGRAMS = hello
hello_SOURCES = hello.cc register.cc register.hh
hello_LDADD = liblibrary.la

noinst_LTLIBRARIES = liblibrary.la
liblibrary_la_SOURCES = myfunc.cc
liblibrary_la_LDFLAGS = -shared -fPIC

首先只使用-shared,后来也加上-fPIC,并在configure.ac中添加了LT_INIT,但结果没有改变。我将尝试你提到的用于C++的“具有显式构造函数的静态数据”技巧,但我仍然想知道如何使用__attribute__((constructor))使我的示例工作。

编辑 2015-02-23 我尝试了“具有显式构造函数的静态数据”技巧,但得到了与之前相同的结果:如果所有对象文件都明确链接到可执行文件中,它将起作用,但如果要自动链接到库中的可执行文件,则不起作用。

添加hello_LDFLAGS = -Wl,--whole-archive(由David Grayson建议)会导致许多“多重定义”错误。 Automake将这些标志放置在链接命令的开头附近,因此它不仅适用于库。 Automake不建议直接在指定要链接的库的hello_LDADD中包含链接器标志。可以使用显式Make规则覆盖Automake规则(在其中可以将链接器标志放在我想要它们的任何位置),但是那么我可能会冒着其他标准Make规则(由Automake提供)无法正常工作的风险。

我会尝试使用dlopen来使其工作。

3个回答

2
我猜你有一个Linux系统。那么请确保该库被构建为共享库(参见这里),而不是静态库。
带有__attribute__(constructor)的函数将在共享库加载时调用,例如在ld.so时间或者如果该库是已加载插件,则在dlopen时间调用。
顺便说一下,__attribute__(constructor)在C++中比在C中更有用。在C++中,你真的不需要它,因为你可以在class中拥有一些显式定义构造函数的static数据来实现相同的结果。
有关详细信息,请阅读Drepper的论文:如何编写共享库

只是提醒一下:有一次我试图在C和Pascal程序中使用C++共享库。因为我使用了静态而不是显式实例化,所以我遇到了一堆崩溃和奇怪的行为。并不是说这种情况一定会发生,但它确实可能发生。特别是对于iostreams和strings,我遇到了一些_IosBase问题。 - Brandon
尝试了你的构造函数技巧,但是得到了与之前相同的行为。请参见编辑后的帖子。我接下来会尝试dlopen。 - Louis Strous
我认为 Linux 中的基本思想是:共享库被视为可执行文件,而库归档只是一个对象文件的集合。因此,库归档被认为是一个你可以明确挑选要包含什么的地方。而共享库的行为类似于没有 main 函数的可执行文件。因此,共享库可以从其导出符号不引用的任何符号中清理出来。而库归档则会包含所有内容。这就是我为什么认为你必须使用 .a 文件明确指定要包含什么,甚至构造函数也是如此。 - Martin

2

默认情况下,GCC的链接器只有在程序实际引用库中某个符号时才会链接静态库(liblibrary.a)。

仅使用库

因此,让库被链接的一种方法是使用其中的一个符号。例如,您可以将以下内容添加到main.cc

void myfunc();
...
std::cout << (void *)&myfunc << std::endl;

你也可以手动调用库中的某些初始化函数。在这个时候,可能没有必要再使用__attr__((constructor))

添加链接器选项

另外,你可以尝试使用链接器的-Wl,--whole-archive选项,如此处所述。为此,你需要将以下行添加到src/Makefile.am文件中:

hello_LDFLAGS = -Wl,--whole-archive

然而,这导致我的GCC版本在libgcc.a中的各种符号输出了大量重复定义错误,因此我不知道这是否是一个真正的解决方案。

我本来希望能够避免在我的主程序中显式引用新对象的代码,但也许我不能这样做。对我来说,链接器选项导致了你提到的链接器错误。Automake似乎并不是为解决我的问题而设计的。请参见编辑后的帖子。 - Louis Strous
在定义库之后,您需要使用-Wl,--no-whole-archive。但是,当使用整个存档时,我仍然没有弄清楚如何使--gc-sections正常工作... - Martin

1

我最终使用了 -u 链接选项,并从驱动程序归档中包含实际需要的驱动程序初始化代码。这似乎是合理的,因为这也是一个不错的方式来构建所有内容,然后精确控制最终程序中包含的内容。我非常喜欢这种方式,因为我不再需要监督编译步骤中包含的内容。我可以编译和存档所有内容。

所以当您链接时:

gcc -Wl,-u,myconstructor1,-u,myconstructor2 -o prog ... -llib1 -llib2 

生成所需构造函数列表可以根据应用程序的选定功能进行自动化。尽管我还没有想出如何使用autotools自动化这一过程。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接