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

How do I get GNU __attribute__((constructor)) to work in a library?

提问人:Louis Strous 提问时间:2/22/2015 最后编辑:Louis Strous 更新时间:6/14/2018 访问量:3657

问:

如果我将所有目标文件链接在一个链接中,我可以让 GNU 工作(对于 C++ 程序),但如果我将包含构造函数的目标文件存储在库中,然后链接库而不是目标文件,它就不再工作了。我做错了什么?__attribute__((constructor))

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], [[email protected]])
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 编译成存储在 liblibrary.a 中的 myfunc.o,该 myfunc.o 与其他对象文件链接到“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

(首先是 just ,后来是 too)并添加到 configure.ac,但这并没有改变结果。我将尝试您提到的 C++ 的“具有显式构造函数的静态数据”技巧,但我仍然有兴趣知道如何让我的示例使用 .-shared-fPICLT_INIT__attribute__((constructor))

已编辑 2015-02-23我尝试了“带有显式构造函数的静态数据”技巧,但得到了与以前相同的结果:如果所有对象文件都显式地链接在一起到可执行文件中,则它有效,但如果我想要自动构造的东西通过库链接到可执行文件,则不会。

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

我会看看我是否可以让它工作。dlopen

C++ Linux Makefile GNU AutoTools

评论


答:

2赞 Basile Starynkevitch 2/22/2015 #1

我猜你有一个 Linux 系统。然后,确保将库构建为共享库(请参阅此处),而不是静态库。

函数 with 将在加载该共享库时调用,例如,如果库是加载的插件,则在 time 调用。__attribute__(constructor)ld.sodlopen

顺便说一句,在 C 中比在 C++ 中更有用。在 C++ 中,您实际上并不需要它,因为您可以在其中使用一些显式定义的构造函数的数据来实现相同的结果。__attribute__(constructor)staticclass

有关详细信息,请阅读 Drepper 的论文:如何编写共享库

评论

0赞 Brandon 2/22/2015
请注意:有一次我尝试在C&Pascal程序中使用C++共享库。而且因为我使用了静态而不是显式实例化它,所以我得到了一堆崩溃和奇怪的行为。不是说这会发生,但它可以 ={ 专门用于 iostreams 和字符串。.遇到了一些_IosBase问题。
0赞 Louis Strous 2/24/2015
尝试了构造函数技巧,但得到了与以前相同的行为。请参阅编辑后的帖子。接下来我会尝试dlopen。
0赞 Martin 6/17/2018
我认为 linux 的基本思想是将共享库视为可执行文件,而库存档只是:对象文件的集合。因此,图书馆存档被认为是一个您可以明确选择要包含的内容的地方。而共享库的行为类似于没有 main 的可执行文件。因此,可以从其导出的符号未引用的任何符号中清除共享库。而存档将拥有一切。这就是为什么我认为您必须使用 .a 文件明确指定您想要包含的内容。甚至构造函数。
2赞 David Grayson 2/23/2015 #2

默认情况下,GCC 的链接器只会在您的静态库 () 中链接,前提是您的程序实际上引用了其中的某些符号。liblibrary.a

只需使用库

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

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

或者,您可以手动调用库中的某些初始化函数。在你这样做的时候,可能没有理由再使用了。__attr__((constructor))

添加链接器选项

或者,可以尝试使用链接器的选项,如此所述。为此,您需要将以下行添加到 src/Makefile.am:-Wl,--whole-archive

hello_LDFLAGS = -Wl,--whole-archive

但是,这导致我的 GCC 版本为 中的各种符号输出大量多个定义错误,所以我不知道这是否是一个真正的解决方案。libgcc.a

评论

0赞 Louis Strous 2/24/2015
我希望避免在我的主程序中显式引用新对象的代码,但也许我不能。对我来说,链接器选项会导致您提到的链接器错误。Automake 似乎不是为了解决我的问题而设计的。请参阅编辑后的帖子。
0赞 Martin 6/13/2018
定义库后,还需要使用 -Wl,--no-whole-archive。但是,我仍然没有弄清楚如何在使用 whole-archive 时使 --gc-sections 正常工作......
1赞 Martin 6/14/2018 #3

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

因此,当您链接时:

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

可以根据应用程序的选定功能自动生成所需的构造函数列表。虽然我还没有弄清楚如何使用自动工具自动执行此操作。