簡體   English   中英

初始化文件范圍變量時出現分段錯誤

[英]Segmentation fault when initializing a file scoped variable

我遇到的情況是我試圖在共享對象構造函數中初始化文件范圍的變量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,-整個存檔/path/to/static.a -Wl,-無整體存檔-lz -lrt -ldl -Wl,-靜態-lboost_python -lboost_thread -lboost_regex- lboost_system -Wl,-Bdynamic -fPIC -shared -o mymodule.so [大量將靜態內容包裝到python中的更多對象]

與較大的測試驅動程序相比,我必須跳過的較大項目使構建過程復雜得多。 這使我認為問題就在這里。 誰能告訴我我所缺少的東西嗎?

謝謝,安迪

我認為值得給出我找到的答案。 該問題是由於共享庫加載的復雜性所致。 經過一番挖掘,我發現在啟用優化的情況下編譯代碼時,可以在測試平台程序中重現該問題。 這證實了這樣的假設:在構造函數訪問變量時,該變量確實不存在。

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()的調用之外,所有內容僅發生一次,並且所有內容都保留在翻譯單元中。

我希望這對將來遇到此問題的人有所幫助。

安迪

也許您可以嘗試在程序上運行valgrind

在上面的自發布解決方案中,您已經將»interface«(用於讀取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;
}

當然,這種方法不會使您的單例模式具有線程安全性。 但是有時候,在某些情況下,我們可以確保沒有並發線程訪問(例如,在加載共享庫時的初始化時)。 如果此條件適合您,則此方法可能是繞過線程安全問題而不使用線程鎖定(mutex ...)的一種方法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM