【问题标题】:How do I get GNU __attribute__((constructor)) to work in a library?如何让 GNU __attribute__((constructor)) 在库中工作?
【发布时间】:2015-04-23 12:12:54
【问题描述】:

如果我在一个链接中将所有目标文件链接在一起,我可以让 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);
}

配置.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'函数没有被执行。为什么不呢?

EDITED 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)并将LT_INIT 添加到configure.ac,但这并没有改变结果。我将尝试您为 C++ 提到的“具有显式构造函数的静态数据”技巧,但我仍然想知道如何让我的示例与 __attribute__((constructor)) 一起使用。

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

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

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

【问题讨论】:

    标签: c++ linux makefile gnu autotools


    【解决方案1】:

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

    带有__attribute__(constructor) 的函数将在加载该共享库时被调用,例如在ld.so 时间,或者在dlopen 时间,如果库是一个加载的插件。

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

    详情请阅读Drepper's paper: How to Write a Shared Library

    【讨论】:

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

    默认情况下,如果您的程序实际引用了某个符号,GCC 的链接器只会链接到您的静态库 (liblibrary.a)。

    只使用库

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

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

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

    添加链接器选项

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

    hello_LDFLAGS = -Wl,--whole-archive
    

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

    【讨论】:

    • 我希望避免在我的主程序中显式引用新对象的代码,但也许我不能。我的链接器选项会导致您提到的链接器错误。 Automake 似乎并非旨在解决我的问题。查看已编辑的帖子。
    • 您还需要在定义库后使用 -Wl,--no-whole-archive。但是我仍然没有弄清楚如何在使用整体存档时使 --gc-sections 正常工作......
    【解决方案3】:

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

    所以当你链接时:

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

    您需要的构造函数列表的生成可以根据应用程序的选定功能自动生成。虽然我还没有弄清楚如何使用自动工具来自动化这个。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-01-04
      • 2019-06-25
      • 2012-12-28
      • 1970-01-01
      • 1970-01-01
      • 2015-05-20
      • 2020-05-29
      相关资源
      最近更新 更多