如何将已解码的std::string作为std::string输出

4

我已经使用了一些反混淆代码来协助调试,而不需要编写成千上万行的动态转换代码或实现返回类名的虚函数。

template <class CLASS>
std::string getClassName(CLASS &theObject)
{
    int status = 0;

    // Convert real name to readable string
    char *realName = abi::__cxa_demangle(typeid(theObject).name(), nullptr,
                                         nullptr, &status);
    ASSERT(status == 0); // Assert for success
    VERIFY(realName, return std::string());
    // Return as string + prevent memory leaks!
    const std::string result(realName);
    free(realName);
    return result;
}

这段代码的想法很简单,输出我们实际使用的类。 尽管在切换到Ubuntu 14.04之后,我无法再使用clang和c++-11/c++-14标准进行编译,所以我转而使用libc++而不是libstdc++。 切换到libc++后,我注意到当我解开'mangle' 'std::string'时,它不再输出'std::string',而是输出:
std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >

当然,这是正确的,因为std :: string是std :: basic_string的typedef。 尽管我在libc ++和libstdc ++中都看到了相同的定义方式,但使用typedef定义。 因此,我真的不理解为什么通过切换到libc ++来更改此解缠。

有人知道为什么不同以及如何在CLASS为'std :: string'时获取'std :: string',而在CLASS为'myTemplate'时获取'myTemplate'吗?

提前感谢!

JVApen


1
阅读C++ itanium ABI文档。std::basic_string有一个特殊的名称重整,但是std::__1::basic_string没有。您将需要使用一些后处理。 - Marc Glisse
据我所知,Clang标准库实现将事物放在__1命名空间中(我猜是为了支持版本控制,然后通过内联命名空间将该命名空间连接到std)。由于它们使用的解码器是GCC的,因此该解码器不会考虑LLVM/Clang的命名约定。 - Manu343726
我有 /usr/include/c++/v1/cxxabi.h 这个文件,如果我理解正确的话,它是 libc++ 的版本,而不是 libstdc++ 的版本。所以要么它与错误的库链接,要么是 libc++-abi 无法对其自己的构造进行符号重整。 - JVApen
1个回答

11

libc++使用内联命名空间来为其ABI版本进行版本控制。它目前使用的内联命名空间是std::__1。这样做是为了使苹果可以同时发布gcc libstdc++和libc++。dylib A可能链接到libstdc ++,dylib B可能链接到libc ++,应用程序可能链接到两个dylib。当发生这种情况时,您不希望意外地将libstdc++的std::string与libc++的std::string混淆。

它们具有相同的API,因此很容易通过在dylib边界上传递std::string而意外地进行混淆。解决方案是告诉编译器以不同的方式操纵它们,这正是内联命名空间所做的(也是为此而发明的)。现在,如果它们在应用程序中被意外混合,链接时错误将导致链接器看到两种不同的类型,证明它们具有不同的mangled名称。

反向操作器的工作只是告诉您实际情况:您提供的符号的反编译名称是什么。它完美地运作。

在libc++中存在一种关闭ABI版本控制的方法。在<__config>中搜索_LIBCPP_BEGIN_NAMESPACE_STD_LIBCPP_END_NAMESPACE_STD。您可以看到某些平台如何定义此选项以打开内联命名空间,而某些则不会。这是解决问题的非常大的工具。如果以此方式更改libc++的ABI,则在您的平台上针对libc++编译和链接的所有内容都必须重新构建。

这里是我有时使用的简单部分解决方案:

#include <iostream>
#include <type_traits>
#include <memory>
#include <algorithm>
#include <cstdlib>
#include <string>
#include <cxxabi.h>

namespace
{

inline
void
filter(std::string& r, const char* b)
{
    const char* e = b;
    for (; *e; ++e)
        ;
    const char* pb = "std::__1::";
    const int pl = std::strlen(pb);
    const char* pe = pb + pl;
    while (true)
    {
        const char* x = std::search(b, e, pb, pe);
        r.append(b, x);
        if (x == e)
            break;
        r += "std::";
        b = x + pl;
    }
}

}  // unnamed namespace

template <typename T>
std::string
type_name()
{
    typedef typename std::remove_reference<T>::type TR;
    std::unique_ptr<char, void(*)(void*)> own
           (
                __cxxabiv1::__cxa_demangle(typeid(TR).name(), nullptr,
                                           nullptr, nullptr),
                std::free
           );
    std::string r;
    if (own)
    {
        if (std::is_const<TR>::value)
            r += "const ";
        if (std::is_volatile<TR>::value)
            r += "volatile ";
        filter(r, own.get());
        if (std::is_lvalue_reference<T>::value)
            r += "&";
        else if (std::is_rvalue_reference<T>::value)
            r += "&&";
    }
    else
        r = typeid(TR).name();
    return r;
}

它会在向您呈现之前过滤掉名称修饰符中的::__1。如果您想要的话,您也可以使用相同的技术将std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>>转换为std::string

Itanium ABI仅有少数几种“压缩”对应于类似这样的typedef,它们是std::stringstd::istreamstd::ostreamstd::iostream


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接