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

bck*_*han 7 c++ gcc dlopen dlsym

背景:

我发现自己有一个将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;
    }

}
Run Code Online (Sandbox Code Playgroud)

此代码是完全有效的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;
    }

}
Run Code Online (Sandbox Code Playgroud)

我一直在努力为GNU/Linux加载器找到一个最佳解决方案,它既可以使用新函数也可以使用旧的遗留函数原型,或至少检测何时遇到不推荐使用的函数并发出警告.如果代码在他们尝试使用旧库时只是分段,那对我们的用户来说是不合适的.我最初的想法是在调用get_list期间设置一个SIGSEGV信号处理程序(我知道这很icky - 我对更好的想法持开放态度).所以只是为了确认加载一个旧库会发生段错误,我认为我会使用旧的函数原型(返回列表对象)通过新的加载代码(期望指向列表的指针)运行库,令我惊讶的是刚刚工作.我的问题是为什么

下面的加载代码适用于上面列出的两个函数原型.我已经确认它适用于使用gcc版本4.1.2和4.4.4的Fedora 12,RedHat 5.5和RedHawk 5.1.使用带有-shared和-fPIC的g ++编译库,并且可执行文件需要与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);
}
Run Code Online (Sandbox Code Playgroud)

Chr*_*odd 5

正如 aschepler 所说,那是因为你很幸运。

事实证明,用于 x86 和 x64 的 gcc(和大多数其他编译器)的 ABI 通过将额外的“隐藏”指针 arg 传递给函数,返回“大”结构(太大而无法放入寄存器),该函数使用那个指针作为空间来存储返回值,然后返回指针本身。所以事实证明,形式的函数

struct foo func(...)
Run Code Online (Sandbox Code Playgroud)

大致相当于

struct foo *func(..., struct foo *)
Run Code Online (Sandbox Code Playgroud)

期望调用者为“foo”(可能在堆栈上)分配空间并传入指向它的指针。

因此,如果您有一个期望以这种方式调用的函数(期望返回一个结构体),而是通过返回一个指针的函数指针调用它,那么它可能看起来工作 - 如果垃圾位它获取额外的 arg(调用者留在那里的随机寄存器内容)碰巧指向某个可写的地方,被调用的函数会很高兴地在那里写入它的返回值,然后返回该指针,因此被调用的代码将返回一些看起来像指向它期望的结构的有效指针。因此,该代码表面上似乎可以工作,但实际上它可能会破坏以后可能很重要的随机内存位。