为什么这个动态库加载代码与gcc一起工作?

背景:

我发现自己将一个C ++ GNU / Linux应用程序移植到Windows上是不值得的任务。 这个应用程序所做的一件事就是搜索特定路径上的共享库,然后使用posix dlopen()和dlsym()调用动态加载它们。 我们有一个非常好的理由来做这种装载,我不会在这里进入。

问题:

要使用dlsym()或GetProcAddress()动态发现由C ++编译器生成的符号,必须通过使用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, "%sn", dlerror());
        exit(EXIT_FAILURE);
    }

    dlerror();

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

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

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

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

    dlclose(handle);

    exit(EXIT_SUCCESS);
}

Aschepler说,因为你很幸运。

事实证明,用于x86和x64的gcc(以及大多数其他编译器)使用的ABI通过向该函数传递额外的“隐藏”指针arg返回“大”结构(太大而不适合寄存器),该函数使用该指针作为存储返回值的空间,然后返回指针本身。 所以事实证明,这是一种形式的功能

struct foo func(...)

大致等于

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

在那里调用者需要为'foo'分配空间(可能在堆栈上)并向它传递一个指针。

所以恰巧发生的事情是,如果你有一个期望被这种调用的函数(期望返回一个结构体),而是通过返回一个指针的函数指针来调用它,它可能会工作 - 如果垃圾它它获取额外的参数(调用者留下的随机寄存器内容)碰巧指向可写的地方,被调用的函数将愉快地在其中写入返回值,然后返回该指针,所以被调用的代码将返回看起来像一个指向它所期望的结构的有效指针。 所以这个代码可能表面上似乎正常工作,但它实际上可能会破坏随机的一点内存,这在以后可能很重要。

链接地址: http://www.djcxy.com/p/50911.html

上一篇: Why does this dynamic library loading code work with gcc?

下一篇: Querying Raven with Where() only filters against the first 128 documents?