编译器不可知的运行时类型信息?

时间:2015-02-19 23:25:06

标签: c++ rtti

如何生成和比较编译器不可知的运行时类型信息?

特别是,如何对函数指针类型进行此操作?

例如,以下程序包含类型错误。它无法在编译时检测到。如何在运行时检测到它?如何检测这类错误?

的main.cpp

#include <iostream>
#include <dlfcn.h>

typedef void (*FooPtrType)();
typedef void (*BarPtrType)();

int main()
{
    const auto dll = dlopen("functions.dylib", RTLD_LAZY);
    if (dll == NULL) {
        std::cerr << dlerror() << std::endl;
        return 1;
    }

    const auto foo_ptr = (FooPtrType)dlsym(dll, "bar");
    if (foo_ptr == NULL) {
        std::cerr << dlerror() << std::endl;
        return 1;
    }

    foo_ptr();
}

functions.cpp,编译为functions.dylib

#include <iostream>

extern "C" void foo() {
    std::cout << "foo()" << std::endl;
}

extern "C" int bar() {
    std::cout << "bar()" << std::endl;
    return 0;
}

2 个答案:

答案 0 :(得分:2)

您无法在运行时检测到此类错误。

dlsym处理的符号只是与dlopen的{​​{3}}共享对象中的某个地址相关联的符号。该符号中没有更多类型信息。

(如果使用.so编译-g,您可以解析ELF信息 - 它知道ELF文件中的类型 - 这很痛苦。

阅读DWARF,然后阅读C++ dlopen mini howto了解详情。

您可以使用Drepper's paper: How to write a Shared Library技术(C ++编译器使用它)。您可能会认为插件中的给定名称具有某些特定签名(例如,通过提供标题为#include - d通过插件代码声明特定的dlsym - ed名称。)

您可以通过数据提供一些自己的输入。例如,您可以确定每个 dlsym -ed指针具有相同的类型,可能是某个公共抽象超类的某个子类的某个实例;或者确定每个dlsym - ed指针指向一些内部name manglingstruct typedef void void0funsig_t (void); typedef int int0funsig_t (void); typedef void void1ifunsig_t (int); enum funtype_en { void0, int0, void1i; }; struct typefun_st { enum funtype_en typ; union { void0funsig_t* void0funptr; // when typ == void0 int0funsig_t* int0funptr; // when typ == int0 void1ifunsig_t* void1ifunptr; // when typ == void1i }; }; 。你可以使用例如

dlsym

并确定每个struct typefun_st - ed指针指向此类 extern "C" struct typefun_st foofun ={ void0, foo }; 。然后在插件代码中

 struct typefun_st *foofunp = dlsym(plugin,"foofun");

并在程序中执行

typeid

等。

你可以使用聪明的C宏来使这些代码更简单(在Emacs代码中寻找灵感)。

了解tagged union的灵感。它们使用Qt plugins提供的Qt(运行时)类型信息。您可以使用dlsymmoc ...

使用{{1}}时总是需要插件约定。在你的情况下,你希望他们提供&#34;提供&#34;插件加载时的某些类型信息。您可以自行设置所需的基础架构。

答案 1 :(得分:2)

多么深刻和发人深省的问题!不错的OP!

这个解决方案背后的基本思想是使用宏来生成类模板特化,它可以告诉你对象类型的名称,使用模板编程将这些类模板特化组合成函数指针的特化,并使用函数重载来搜索特。

type_string.h

#ifndef TYPE_STRING_H
#define TYPE_STRING_H

#include <type_traits>
#include <cstdlib>
#include <cstring>
#include <string>

// is_fun_ptr
// Dietmar Kühl
// http://stackoverflow.com/a/18667268/1128289

template <typename Fun>
struct is_fun_ptr
    : std::integral_constant<bool, std::is_pointer<Fun>::value
                                  && std::is_function<
                                         typename std::remove_pointer<Fun>::type
                                     >::value>
{
};


template<typename T>
struct TypeString;

// type_string<>() overload for non-function pointer objects
// caller must free
template<typename T>
typename std::enable_if<!is_fun_ptr<T>::value, const std::string>::type
type_string()
{
    const std::string name = TypeString<T>::value();
    char* name_c_str = (char*)malloc(name.length() + 1);
    strcpy(name_c_str, name.c_str());
    return name_c_str;
}

// getting type name into a template
// atzz and Steve Jessop
// http://stackoverflow.com/a/4485051/1128289

// There's likely a better way to handle CV qualifiers and pointer/ref
// not all combos covered here
#define ENABLE_TYPE_STRING(TYPE) \
template<> \
struct TypeString<TYPE> { \
    static const std::string value() { return #TYPE; } \
}; \
template<> \
struct TypeString<TYPE&> { \
    static const std::string value() { return #TYPE "&"; } \
}; \
template<> \
struct TypeString<TYPE*> { \
    static const std::string value() { return #TYPE "*"; } \
}; \
template<> \
struct TypeString<const TYPE> { \
    static const std::string value() { return "const " #TYPE; } \
}; \
template<> \
struct TypeString<const TYPE&> { \
    static const std::string value() { return "const " #TYPE "&"; } \
}; \
template<> \
struct TypeString<const TYPE*> { \
    static const std::string value() { return "const " #TYPE "*"; } \
}; \
template<> \
struct TypeString<const TYPE* const> { \
    static const std::string value() { return "const " #TYPE "* const"; } \
};

// some builtin types, add others and user-defined types as desired
ENABLE_TYPE_STRING(char)
ENABLE_TYPE_STRING(short)
ENABLE_TYPE_STRING(int)
ENABLE_TYPE_STRING(long)
ENABLE_TYPE_STRING(long long)

ENABLE_TYPE_STRING(signed char)
ENABLE_TYPE_STRING(unsigned char)
ENABLE_TYPE_STRING(unsigned short)
ENABLE_TYPE_STRING(unsigned int)
ENABLE_TYPE_STRING(unsigned long)
ENABLE_TYPE_STRING(unsigned long long)

ENABLE_TYPE_STRING(float)
ENABLE_TYPE_STRING(double)
ENABLE_TYPE_STRING(long double)

ENABLE_TYPE_STRING(std::string)

// void is a special case, no qualifiers, refs
template<>
struct TypeString<void>
{
    static const std::string value()
    {
        return "void";
    }
};
template<>
struct TypeString<void*>
{
    static const std::string value()
    {
        return "void*";
    }
};


// Function signature to string

// return_type
// angew
// http://stackoverflow.com/a/18695701/1128289
template <class F>
struct return_type;

template <class R, class... A>
struct return_type<R (*)(A...)>
{
    typedef R type;
};

// forward declaration so that this overload may be used in CommaSeparatedNames
template<typename T>
typename std::enable_if<is_fun_ptr<T>::value, const std::string>::type
type_string();

// Concatenating argument types with separating commas
template<typename T, typename... Us>
struct CommaSeparatedNames
{
    static const std::string value()
    {
        return std::string{type_string<T>()}
            + ", " + CommaSeparatedNames<Us...>::value();
    }
};

template<typename T>
struct CommaSeparatedNames<T>
{
    static const std::string value()
    {
        return type_string<T>();
    }
};

// Arguments to string
template <class F>
struct ArgNames;

template<class R>
struct ArgNames<R (*)()>
{
    static const std::string value()
    {
        return "";
    }
};

template<class R, typename A, typename... As>
struct ArgNames<R (*)(A, As...)>
{
    static const std::string value()
    {
        return CommaSeparatedNames<A, As...>::value();
    }
};

// overload type_string<>() for function pointers
// caller must free
template<typename T>
typename std::enable_if<is_fun_ptr<T>::value, const std::string>::type
type_string()
{
    const auto name =
        std::string{type_string<typename return_type<T>::type>()}
        + " (*)(" + ArgNames<T>::value() + ")";
    auto name_c_str = (char*)malloc(name.length() + 1);
    strcpy(name_c_str, name.c_str());
    return name_c_str;
}

// overload type_string<>() to deduce type from an object
// caller must free
template<typename T>
const std::string type_string(T) { return type_string<T>(); }

template<typename T>
const char* type_string_c_str()
{
    const auto name = type_string<T>();
    char* name_c_str = (char*)malloc(name.length() + 1);
    strcpy(name_c_str, name.c_str());
    return name_c_str;
}

template<typename T>
const char* type_string_c_str(T) { return type_string_c_str<T>(); }


#endif

使用示例:

#include "type_string.h"

#include <iostream>

void foo() {}
int bar() { return 0; }
float baz(char) { return 0.0f; }

class MyClass;
ENABLE_TYPE_STRING(MyClass)

double quux(const int*, MyClass*) { return 0.0; }

int main()
{
    typedef void (*FooTypePtr)();
    typedef int (*BarTypePtr)();
    typedef float (*BazTypePtr)(char);
    typedef double (*QuuxTypePtr)(const int*, MyClass*);
    FooTypePtr foo_ptr = foo;
    BarTypePtr bar_ptr = bar;
    BazTypePtr baz_ptr = baz;
    QuuxTypePtr quux_ptr = quux;

    QuuxTypePtr (*weird_ptr)(FooTypePtr, BarTypePtr, BazTypePtr) = NULL;

    std::cout << type_string(3) << std::endl;
    std::cout << type_string('a') << std::endl;
    std::cout << type_string(foo_ptr) << std::endl;
    std::cout << type_string(bar_ptr) << std::endl;
    std::cout << type_string(baz_ptr) << std::endl;
    std::cout << type_string(quux_ptr) << std::endl;
    std::cout << type_string(weird_ptr) << std::endl;
}

输出:

int
char
void (*)()
int (*)()
float (*)(char)
double (*)(const int*, MyClass*)
double (*)(const int*, MyClass*) (*)(void (*)(), int (*)(), float (*)(char))

需要手动启用对象类型的类型信息,但如果启用了所有相关类型的信息,则可以推断出函数指针的类型。


动态加载示例(也就是使用X来解决Y)

这是一种在运行时检测此类错误的方法。

safe_dl.h使用type_string.h来检查动态加载错误。

#ifndef SAFE_DL_H
#define SAFE_DL_H

#include "type_string.h"

#include <exception>
#include <stdexcept>
#include <sstream>
#include <string>

#include <dlfcn.h>

#define ENABLE_RUNTIME_FNC_PTR_TYPE_INFO(F) \
    extern "C" const char* F ## _fnc_ptr_type_() { \
        return type_string_c_str( F ); }

#define RUNTIME_TYPE_STRING_FNC_NAME(F) \
    F ## _fnc_ptr_type_

namespace {
extern "C" void in_dll_free(void* ptr)
{
    free(ptr);
}
}

class DynamicLibrary
{
public:
    explicit DynamicLibrary(const char* lib_name, const int mode=RTLD_LAZY)
        : filename_(lib_name)
    {
        handle_ = dlopen(lib_name, mode);
        if (handle_ == NULL) {
            throw std::runtime_error(dlerror());
        }
        free_ptr_ = find_in_dll_free();
    }

    DynamicLibrary(const std::string& lib_name)
    : DynamicLibrary(lib_name.c_str()) {}

    ~DynamicLibrary()
    {
        dlclose(handle_);
    }

    template<typename T>
    T safe_dynamic_load(const std::string& func_name) const
    {
        // The type of T* tells us the expected type.  A cooperating library tells
        // us the actual type.
        const auto expected_type = type_string<T>();
        const auto actual_type = symbol_type(func_name);
        if (strcmp(expected_type.c_str(), actual_type))
        {
            std::ostringstream msg;
            msg << "Function pointer type mismatch. Function " << func_name
                << " loaded from file " << filename() << " has expected type "
                << expected_type << " but the actual type is " << actual_type
                << ".";
            free_ptr_((void*)actual_type);
            throw std::runtime_error(msg.str());
        }

        free_ptr_((void*)actual_type);
        return (T)(symbol(func_name));
    }

    const std::string& filename() const { return filename_; }

private:
    // caller is responsible for freeing returned pointer
    const char* symbol_type(const std::string& name) const
    {
        const auto type_func_name = name + "_fnc_ptr_type_";
        typedef const char* (*TypeFuncPtrType)();
        TypeFuncPtrType type_func_ptr =
            (TypeFuncPtrType)dlsym(handle_, type_func_name.c_str());
        if (type_func_ptr == NULL) {
            const auto msg = "Safe dynamic loading not enabled for " + name;
            throw std::runtime_error(msg.c_str());
        }
        return type_func_ptr();
    }

    void* symbol(const std::string& name) const
    {
        void* p = dlsym(handle_, name.c_str());
        if (p == NULL) {
            throw(std::runtime_error{dlerror()});
        }
        return p;
    }

    // free from within the dll
    typedef void (*DllFreePtrType)(void*);
    DllFreePtrType find_in_dll_free() const
    {
        typedef void (*DllFreePtrType)(void*);
        DllFreePtrType free_ptr = (DllFreePtrType)dlsym(handle_, "in_dll_free");
        if (free_ptr == NULL) {
            throw std::runtime_error(dlerror());
        }
        return free_ptr;
    }

private:
    const std::string filename_;
    void* handle_;
    DllFreePtrType free_ptr_;
};

#endif

safe_dl.h工作到原始程序中:

main.cpp

#include "safe_dl.h"
#include <iostream>

#if defined(__clang__)
#define COMPILER "clang"
#elif defined(__GNUC__)
#define COMPILER "gcc"
#else
#define COMPILER "other"
#endif

typedef void (*FooPtrType)();
typedef int (*BarPtrType)();

int main()
{
    std::cout << "main()" << std::endl;
    std::cout << "compiler: " COMPILER << std::endl;

    const DynamicLibrary dll{"./functions.so"};
    // Works fine.
    const auto foo_ptr = dll.safe_dynamic_load<FooPtrType>("foo");
    foo_ptr();

    // Throws exception.
    const auto bar_ptr = dll.safe_dynamic_load<FooPtrType>("bar");
    bar_ptr();
}

functions.cpp,汇编为functions.dylib

#include "safe_dl.h"
#include <iostream>

#if defined(__clang__)
#define COMPILER "clang"
#elif defined(__GNUC__)
#define COMPILER "gcc"
#else
#define COMPILER "other"
#endif

extern "C" void foo() {
    std::cout << "foo()" << std::endl;
    std::cout << "compiler: " COMPILER << std::endl;
    return;
}
ENABLE_RUNTIME_FNC_PTR_TYPE_INFO(foo)

extern "C" int bar() {
    std::cout << "bar()" << std::endl;
    return 0;
}
ENABLE_RUNTIME_FNC_PTR_TYPE_INFO(bar)

用clang编译的main.cpp的输出和用g ++编译的functions.cpp

main()
compiler: clang
foo()
compiler: gcc
terminate called after throwing an instance of 'std::runtime_error'
  what():  Function pointer type mismatch. Function bar loaded from file
./functions.so has expected type void (*)() but the actual type is int (*)().
Aborted (core dumped)

由于type_name.h生成char *来编码类型,因此代码与编译器无关,safe_dl.h将与混合编译器一起使用。 (或者至少这个例子在混合使用gcc 4.9.2和clang 3.5时有效。)