1

我的情况是我试图在共享对象构造函数中初始化文件范围变量 std::string。在代码中可能会更清楚:

#include <string>
#include <dlfcn.h>
#include <cstring>
static std::string pathToDaemon; // daemon should always be in the same dir as my *.so
__attribute__((constructor))
static void SetPath()
{
    int lastSlash(0):
    Dl_info dl_info;
    memset(&dl_info, 0, sizeof(dl_info));

    if((dladdr((void*)SetPath, &dl_info)) == 0)
        throw up;

    pathToDaemon = dl_info.dli_fname; // **whoops, segfault here**
    lastSlash = pathToDaemon.find_last_of('/');
    if(std::string::npos == lastSlash)
    {
        // no slash, but in this dir
        pathToDaemon = "progd";
    }
    else
    {
        pathToDaemon.erase(pathToDaemon.begin() + (lastSlash+1), pathToDaemon.end());
        pathToDaemon.append("progd");
    }

    std::cout << "DEBUG: path to daemon is: " << pathToDaemon << std::endl;
}

我有一个非常简单的程序可以做同样的事情:如果你愿意的话,一个用于概念的测试驱动程序。其中的代码看起来像这样:一个“共享对象 ctor”,它在加载文件时使用 dladdr() 来存储 *.so 文件的路径。

我尝试过的修改:

namespace {
    std::string pathToDaemon;
    __attribute__((constructor))
    void SetPath() {
        // function def
    }
}

或者

static std::string pathToDaemon;
__attribute__((constructor))
void SetPath() { // this function not static
    // function def
}

std::string pathToDaemon; // variable not static
__attribute__((constructor))
void SetPath() { // this function not static
    // function def
}

您在上面看到的示例位于一个文件中,该文件同时编译为静态对象库和 DLL。编译过程:

  • static.a 的选项:--std=C++0x -c -Os。
  • shared.so 的选项:-Wl,--whole-archive /path/to/static.a -Wl,--no-whole-archive -lz -lrt -ldl -Wl,-Bstatic -lboost_python -lboost_thread -lboost_regex - lboost_system -Wl,-Bdynamic -fPIC -shared -o mymodule.so [将静态内容包装到 python 中的更多对象]

在更大的项目中,我必须经历的障碍使得构建过程比我的小测试驱动程序所需的复杂得多。这让我觉得问题出在那儿。任何人都可以请阐明我所缺少的吗?

谢谢,安迪

4

3 回答 3

2

我认为值得给出我找到的答案。问题是由于共享库加载的复杂性造成的。经过一番挖掘后,我发现在启用优化的情况下编译代码时,我可以在我的测试床程序中重现该问题。这证实了变量在被构造函数访问时确实不存在的假设。

GCC 包括一些用于 C++ 的额外工具,允许开发人员在代码初始化期间强制某些事情在特定时间发生。更准确地说,它允许某些事情以特定的顺序而不是特定的时间发生。

例如:

int someVar(55) __attribute__((init_priority(101)));

// This function is a lower priority than the initialization above
// so, this will happen *after*
__attribute__((constructor(102)))
void SomeFunc() {
    // do important stuff
    if(someVar == 55) {
        // do something here that important too
        someVar = 44;
    }
}

即使启用了优化,我也能够使用这些工具在测试台程序中取得成功。当应用于我更大的图书馆时,随之而来的幸福是短暂的。归根结底,问题是由于如此大量代码的性质以及使变量存在的方式存在问题。使用这些机制是不可靠的。

因为我想避免重复调用评估路径,即

std::string GetPath() {
    Dl_info dl_info;
    dladdr((void*)GetPath, &dl_info);
    // do wonderful stuff to find the path
    return dl_info.dli_fname;
}

结果证明解决方案比我尝试的要简单得多:

namespace {
    std::string PathToProgram() {
        Dl_info dl_info;
        dladdr((void*)PathToProgram, &dl_info);
        std::string pathVar(dl_info.dli_fname);

        // do amazing things to find the last slash and remove the shared object
        // from that path and append the name of the external daemon
        return pathVar;
    }

    std::string DaemonPath() {
        // I'd forgotten that static variables, like this, are initialized
        // only once due to compiler magic.
        static const std::string pathToDaemon(PathToProgram());
        return pathToDaemon;
    }
}

正如你所看到的,这正是我想要的,更少的混乱。一切都只发生一次,除了对 DaemonPath() 的调用,一切都保留在翻译单元中。

我希望这对将来遇到此问题的人有所帮助。

安迪

于 2013-06-03T17:32:37.517 回答
0

在上面您自己发布的解决方案中,您已将您的»接口«(用于读取 pathToDaemon / DaemonPath() 的代码)从»访问文件范围变量«更改为»在匿名命名空间中调用函数« - 到目前为止还可以。

但是 DaemonPath() 的实现不是以线程安全的方式完成的。我认为线程安全很重要,因为您在问题中写了 »-lboost_thread«。所以你可能会考虑改变线程安全的实现。有很多关于单例模式和线程安全性的讨论和解决方案,例如:

事实是,您的 DaemonPath() 将在库加载完成后调用(可能很远)。请注意,在多线程环境中,只有第一次调用单例模式是至关重要的。

作为替代方案,您可以向 DaemonPath() 函数添加一个简单的 »early« 调用,如下所示:

namespace {
    std::string PathToProgram() {
        ... your code from above ...
    }

    std::string DaemonPath() {
        ... your code from above ...
    }

    __attribute__((constructor)) void MyPathInit() {
        DaemonPath();
    }
}

或者像这样更便携的方式:

namespace {
    std::string PathToProgram() {
        ... your code from above ...
    }

    std::string DaemonPath() {
        ... your code from above ...
    }

    class MyPathInit {
    public:
        MyPathInit() {
            DaemonPath();
        }
    } myPathInit;
}

当然,这种方法不会使您的单例模式线程安全。但有时,在某些情况下,我们可以确定没有并发线程访问(例如在加载共享库的初始化时)。如果此条件适合您,则此方法可能是一种无需使用线程锁定(互斥锁...)即可绕过线程安全问题的方法。

于 2013-10-27T18:04:49.337 回答
0

也许你可以尝试在你的程序上运行 valgrind

于 2013-05-31T00:55:34.963 回答