2011-02-23 39 views
7

背景:爲什麼這個動態庫加載代碼與gcc一起工作?

,我發現自己與移植C++的GNU/Linux的應用程序在Windows的不值得羨慕的任務。這個應用程序所做的一件事就是搜索特定路徑上的共享庫,然後使用posix dlopen()和dlsym()調用動態加載它們。我們有一個非常好的理由來做這種裝載,我不會在這裏進入。

問題:

動態地發現由C++編譯器使用dlsym()或GetProcAddress的產生的符號()它們必須使用一個extern 「C」 鏈接塊中未重整。例如:

#include <list> 
#include <string> 

using std::list; 
using std::string; 

extern "C" { 

    list<string> get_list() 
    { 
     list<string> myList; 
     myList.push_back("list object"); 
     return myList; 
    } 

} 

此代碼是完全有效的C++,可編譯並在Linux和Windows上的許多編譯器上運行。但是,它不會使用MSVC進行編譯,因爲「返回類型無效C」。我們已經想出瞭解決辦法是改變函數返回一個指針列表,而不是列表對象:

#include <list> 
#include <string> 

using std::list; 
using std::string; 

extern "C" { 

    list<string>* get_list() 
    { 
     list<string>* myList = new list<string>(); 
     myList->push_back("ptr to list"); 
     return myList; 
    } 

} 

我一直試圖找到的GNU/Linux加載程序的最佳解決方案,既可以使用新功能,也可以使用舊功能原型,或者至少檢測何時遇到棄用功能併發出警告。對於我們的用戶來說,如果代碼在嘗試使用舊庫時只是暫停了,那將是不合時宜的。我最初的想法是在調用get_list期間設置一個SIGSEGV信號處理程序(我知道這是icky - 我願意接受更好的想法)。所以只是爲了確認加載一箇舊的庫會發生段錯誤,我認爲它會使用舊的函數原型(返回一個列表對象)通過新的加載代碼運行一個庫(它期望一個指向列表的指針),令我驚訝的是剛剛工作。我的問題是爲什麼

以下加載代碼適用於上面列出的兩個函數原型。我已經證實它可以在Fedora 12,RedHat 5.5和RedHawk 5.1上使用gcc版本4.1.2和4.4.4。使用g ++和-shared和-fPIC編譯庫,並且需要將可執行文件鏈接到dl(-ldl)。

#include <dlfcn.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <list> 
#include <string> 

using std::list; 
using std::string; 

int main(int argc, char **argv) 
{ 
    void *handle; 
    list<string>* (*getList)(void); 
    char *error; 

    handle = dlopen("library path", RTLD_LAZY); 
    if (!handle) 
    { 
     fprintf(stderr, "%s\n", dlerror()); 
     exit(EXIT_FAILURE); 
    } 

    dlerror(); 

    *(void **) (&getList) = dlsym(handle, "get_list"); 

    if ((error = dlerror()) != NULL) 
    { 
     printf("%s\n", error); 
     exit(EXIT_FAILURE); 
    } 

    list<string>* libList = (*getList)(); 

    for(list<string>::iterator iter = libList->begin(); 
      iter != libList->end(); iter++) 
    { 
     printf("\t%s\n", iter->c_str()); 
    } 

    dlclose(handle); 

    exit(EXIT_SUCCESS); 
} 
+3

因爲你很幸運。我懷疑如果你用更復雜的程序來嘗試這種事情,你會開始看到一個被搗毀的堆棧或類似的效果。 – aschepler 2011-02-23 21:31:24

+0

我發佈的代碼被簡化了。實際的應用程序大約有10萬行代碼,我運行了一些相當廣泛的測試用例,這些測試用例似乎都可以工作。我同意,但這不應該工作,除非在這種情況下有GCC的一些怪癖。 – bckohan 2011-02-23 21:42:57

+1

我不確定'他們必須解開'是否屬實。如果你問dlsym()這個錯位的名字,它會找不到它。 – 2011-02-23 22:28:02

回答

4

正如aschepler說的那樣,因爲你幸運。

事實證明,用於x86和x64的gcc(以及大多數其他編譯器)所使用的ABI通過將額外的「隱藏」指針arg傳遞給「大」結構(太大而不適合寄存器)函數,它使用該指針作爲存儲返回值的空間,然後返回指針本身。因此,原來的是,當呼叫者被預期要分配給「富」(可能在堆棧上)空間,並通過在形式

struct foo func(...) 

的功能大致equivlant到

struct foo *func(..., struct foo *) 

一個指向它的指針。

所以它只是發生,如果你有一個函數,期望被稱爲這種方式(期待返回一個結構),而是調用它通過函數指針返回一個指針,它可能會工作 - 如果它爲額外的arg(隨機寄存器內容由調用者留下的內容)發生的垃圾位碰巧指向某處可寫,被調用的函數將愉快地在那裏寫入它的返回值,然後返回該指針,所以被調用的代碼將返回一些東西它看起來像一個指向它期望的結構的有效指針。所以這個代碼可能表面上似乎正常工作,但它實際上可能會破壞隨機的一點內存,這在以後可能很重要。

相關問題