防止导出的函数被链接器删除

Posted

技术标签:

【中文标题】防止导出的函数被链接器删除【英文标题】:Keep an exported function from being deleted by the linker 【发布时间】:2013-06-12 16:22:32 【问题描述】:

我有一个程序静态链接到几个导出一些函数的 c++ 库:

extern "C" 
 
    KSrvRequestHandler* CreateRequestHandler( const char* name );
    bool                DestroyRequestHandler( KSrvRequestHandler* handler );
    const char**        ListRequestHandlerTypes();

然后主程序使用 GetProcAddress/dlsym 调用这些函数:

#ifdef WIN32

   HINSTANCE hDll = GetModuleHandle( NULL );

   mCreateHandler   = GetProcAddress( hDll, createFuncName  );
   mDestroyHandler  = GetProcAddress( hDll, destroyFuncName );
   mGetHandlerTypes = GetProcAddress( hDll, listFuncName    );

#else // POSIX

   void* handle = dlopen( NULL, 0 );

   mCreateHandler   = dlsym( handle, createFuncName  ); 
   mDestroyHandler  = dlsym( handle, destroyFuncName ); 
   mGetHandlerTypes = dlsym( handle, listFuncName    ); 
   dlclose( handle );

#endif // !POSIX

所以这里的关键是我正在使用动态链接在我自己的主程序中调用一个函数。

(我为什么这样做超出了问题的范围,但简短的回答:这是一个插件架构,但我有一些直接链接到主二进制文件的标准插件 - 但我仍然想通过相同的插件加载接口。例如,对于内置插件,我通过传入当前可执行文件作为插件接口的源来加载它们。)

问题是:链接器不知道我将需要这些函数,也没有链接它们。

如何强制链接这些函数?对于动态库,导出它们就足够了。但是对于exe来说,连dll导出的函数都会被链接器删除。

我知道我可以通过使主二进制文件将这些函数地址分配给某些东西或其他类似的黑客来强制链接。有正确的方法吗?

@UPDATE:所以我有一个可行的解决方案 - 但它的内部确实很难看。仍在寻找更好的方法。

所以我必须以某种方式在加载内置接口的对象中定义我需要的符号。我认为没有办法强制链接器以其他方式链接符号。例如。我不知道如何构建一个具有始终链接的函数的库,无论它看起来是否需要。这完全取决于可执行文件的链接步骤。

所以在可执行文件中我有一个宏来定义我需要的内置接口。每个内置插件都有其所有接口函数的前缀,因此,在文件的顶部,我这样做:

DEFINE_BUILT_IN_PLUGIN( PluginOne )
DEFINE_BUILT_IN_PLUGIN( PluginTwo )

这将强制定义我需要的函数。但是这样做的宏太丑了,以至于我充满了愤怒和自我怀疑的感觉(为了便于阅读,我已经从宏中删除了尾部的斜杠):

#define FORCE_UNDEFINED_SYMBOL(x) 
    void* _fp_ ## x ## _fp =(void*)&x; 
    if (((ptrv) _fp_ ## x ##_fp * ( rand() | 1 )) < 1 ) 
        exit(0);

#define DEFINE_BUILT_IN_PLUGIN( PREFIX )  

extern "C" 
                                                                                                   
    KSrvRequestHandler* PREFIX ## CreateRequestHandler( const char* name );
    bool                PREFIX ## DestroyRequestHandler( KSrvRequestHandler* handler );      
    const char**        PREFIX ## ListRequestHandlerTypes();
  

class PREFIX ## HandlerInterfaceMagic
      
public:
    PREFIX ## HandlerInterfaceMagic()
    
        FORCE_UNDEFINED_SYMBOL( PREFIX ## CreateRequestHandler );
        FORCE_UNDEFINED_SYMBOL( PREFIX ## DestroyRequestHandler );
        FORCE_UNDEFINED_SYMBOL( PREFIX ## ListRequestHandlerTypes ); 
    
;               
PREFIX ## HandlerInterfaceMagic PREFIX ## HandlerInterfaceMagicInstance;

由于编译器是一个优化天才,在 FORCE_UNDEFINED_SYMBOLS 中,我将竭尽全力欺骗编译器链接一个未引用的函数。该宏仅在函数内部有效。所以我必须创建这个虚假的魔法类。一定有更好的办法。

无论如何 - 它确实有效。

【问题讨论】:

我可能误解了您的情况,但是您能否将导出的函数移动到单独编译的库中,然后根据需要从库中链接它们? 如果我将内置插件放入他们自己的动态库中,这将起作用 - 基本上只是将它们编写为始终与可执行文件一起提供的常规插件。但这是次优的,因为我希望它们都在同一个文件中。但更大的问题是内置插件需要访问主程序的内部。例如。其中之一是报告内部程序状态的诊断插件。我必须创建一个完整的反向接口(例如 exe->plugin)才能将 diag 插件用作单独的 dyn。库。 请不要在符号名称中使用前导双下划线,这些是为编译器保留的。如果您担心名称冲突,请将它们放在命名空间中。 【参考方案1】:

我见过至少两种不同的方法来解决类似的任务。

    例如,在 Qt 中,您可以通过调用特定的宏将静态插件“导入”到主可执行文件中:

    https://qt-project.org/doc/qt-4.8/qtplugin.html#Q_IMPORT_PLUGIN

    它创建一个自定义类的静态实例,其构造函数调用从静态插件导出的初始化函数。

    Poco 家伙在 Linux 上使用 extern "C" 声明和在 Windows 上使用 pragma 强制从静态库中导出特定符号:

    __pragma(comment (linker, "/export:CreateRequestHandler"))

    在 Linux 上使用相同的 extern "C" 声明和在 Windows 上使用链接器编译指示强制链接到静态库:

    __pragma(comment (linker, "/include:CreateRequestHandler"))

    您可以在此blog post 中找到详细信息。

【讨论】:

谢谢。令人放心的是,Qt 以与我在自己非常丑陋的导入宏中所做的类似的方式解决了这个问题。 是的,但是如果我正确理解了您的情况,您的 extern "C" 函数只是一个虚拟函数。在Qt中,实际上需要调用该函数(在导入可执行文件或共享库的静态初始化期间)并注册静态插件。 似乎在 QT 示例中,QT 向插件导出了一个注册 api,并在静态初始化时调用它们以使插件自行安装。在我的情况下,系统会按照配置文件中的配置加载插件。因此系统可以控制是否加载插件。所以它所需要的只是插件函数在插件动态库中可用,或者编译到主系统中。我现在通过虚假使用函数指针地址来强制链接来强制包含它们。如果优化器变得更聪明,它将停止再次链接它们。【参考方案2】:

您不能为您的主要可执行链接器提供一个 .def 文件吗?该文件应该导出有问题的函数,这样可以防止它们被删除。

我似乎记得很久以前我做过这样的事情。

【讨论】:

【参考方案3】:

问题:在 Windows 上,STATIC LIB 包含一个 OBJ 文件,该文件具有标记为 __decl-spec(dll¬export) 的函数,但如果在 EXE 中未使用该函数,则不会从 EXE 导出函数。在其他平台上,我们也有同样的问题,但是我们有编译器选项,比如 --whole-archive / -force_load,一定要让它工作。

链接:Link1Link2

我想到的唯一解决方案是不创建静态库,而是在可执行文件中包含所有代码(静态库): 1. 它适用于 Windows 2。它适用于没有 --whole-archive 的 Linux 3。它可以在没有 -force_load 4 的 Mac OS X 上运行。我们也不必担心 2 和 3 是否包含死代码、exe 膨胀等。

这是唯一的解决方案,直到链接器变得智能并丢弃所有未使用的符号,除了那些专门标记为外部使用的符号,即标记为导出。

【讨论】:

以上是关于防止导出的函数被链接器删除的主要内容,如果未能解决你的问题,请参考以下文章

防止 GCC LTO 删除函数

链接器下——链接器实战

如何导出链接器选项?

如何防止 qmake 在链接器命令行上添加控制台子系统?

Xamarin iOS 链接器问题

linux中链接器的对应选项