1

I'm currently browsing through RTTI information available in an MSVC++2003 executable (writing an IDAPython script to recreate structs for the class hierarchy). Apparently, as visualized here, the RTTI Type Descriptor stores something like the name of the classes or their constructor:

.?AVexception@@
    .?AUescaped_list_error@boost@@
    .?AVruntime_error@stlp_std@@

However, it sports a mangling scheme I do not recognize yet. The name starts with a . which, according to this wiki, is not even a valid start for a mangled MSVC name. IDA and an online name demangler also cannot demangle these names. According to these scripts (s. ms_rtti4.idc) these should map to:

typeid(struct exception)
    typeid(struct boost::escaped_list_error)
    typeid(struct stlp_std::runtime_error)

I tried removing the leading dot to get a valid start at least, but it is still invalid. Quickly writing an overly simplistic python line to at least fix namespaced class names...

return "::".join(reversed(name[4:-2].split("@")))

...it of course fails with generic type names, as with these classes here:

ns::FunctionBase             (.?AVFunctionBase@ns@@)
    ns::Z::P6AXPAX::?$FunctionT  (.?AV?$FunctionT@P6AXPAX@Z@ns@@)
    ns::Z::P6AHH::?$FunctionT    (.?AV?$FunctionT@P6AHH@Z@ns@@)
    ns::Z::P6AHPB_W::?$FunctionT (.?AV?$FunctionT@P6AHPB_W@Z@ns@@)
    ns::Z::P6AHI::?$FunctionT    (.?AV?$FunctionT@P6AHI@Z@ns@@)

I noticed removing the .?AU or .?AV prefix from those yields kinda useful results (missing the namespace sadly):

FunctionT@ns@@
    FunctionT<void (__cdecl*)(void *)>
    FunctionT<int (__cdecl*)(int)>
    FunctionT<int (__cdecl*)(wchar_t const *)>
    FunctionT<int (__cdecl*)(unsigned int)>

...but again that one now doesn't work for non-generic names.

I wonder if the RTTI Type Descriptor name scheme is documented, or if there is logic turning this into an actual RTTI name I can demangle with the usual tools?

  • I think CodeXplorers source code could contain some hint: but they annotate AV as function and AU as structure but that does not seem to match your example – Nordwald Jan 31 at 13:55
  • That's interesting though, I found out (via Igor Skochinsky's RTTI scripts), that these type "names" are actually equivalent to typeid(struct xyz) as added to my question now. I found a way to abuse demangling a valid ctor from it and then stripping out the class name which I post as an answer for now. – Ray Koopa Jan 31 at 13:57
  • Check out the clang for windows source code. They claim binary compatibility with msvc rtti iirc. – mrexodia Feb 1 at 20:51
  • @mrexodia Interesting, I get back to it when working on this part of the script again. – Ray Koopa Feb 2 at 14:32
1

vc++filt seems to demangle it

?AVexception@@
 ?? AVexception
?AUescaped_list_error@boost@@
 ?? boost::AUescaped_list_error
?AVruntime_error@stlp_std@@
 ?? stlp_std::AVruntime_error
?AUescaped_list_error@boost@@
 ?? boost::AUescaped_list_error

src slightly edited to strip . in front

cat cpp.cpp
#include <windows.h>
#include <stdio.h>
#include <dbghelp.h>
#pragma comment(lib,"dbghelp.lib")
#define MAXLEN 0x1000
char buff[MAXLEN];
int main (int argc , char * argv[])
{
memset(buff,0,MAXLEN);
if( argc !=2 )
    {
        printf ("usage %s undemsym",argv[0]);
        exit(-1); 
    }
    char *nodot;
    if( *(char *) argv[1] == '.'  )
    {
        nodot = argv[1]+1;
    }
    else if (*(char *) argv[1] == '?')
    {
        nodot = argv[1];
    }
    else
    {        
        printf("not a valid msvc mangled name\n");
        exit(-2);
    }
    if ( ( UnDecorateSymbolName(nodot , buff,MAXLEN,UNDNAME_COMPLETE)) !=0 )
        {
            printf ("%s\n",buff);
        }
        else
        {
            printf("error %x\n" , GetLastError());
        }
}

compile link

cl /Zi /W4 /analyze /Od cpp.cpp /link /release

test

cpp.exe
usage cpp.exe sym
cpp.exe .?AVruntime_error@stlp_std@@
.?AVruntime_error@stlp_std@@

cpp.exe ?AVruntime_error@stlp_std@@
 ?? stlp_std::AVruntime_error

edit for comment (add these lines to src above)

if ( ( UnDecorateSymbolName(nodot , buff,MAXLEN,  UNDNAME_NO_ARGUMENTS )) !=0 )
{
    printf ("%s\n",buff);
}
else
{
    printf("error %x\n" , GetLastError());
}

result

cppundem.exe .?AUescaped_list_error@boost@@
 ?? boost::AUescaped_list_error
struct boost::escaped_list_error

full results

:\cat mang.txt
.?AVFunctionBase@ns@@
.?AV?$FunctionT@P6AXPAX@Z@ns@@
.?AV?$FunctionT@P6AHH@Z@ns@@
.?AV?$FunctionT@P6AHPB_W@Z@ns@@
.?AV?$FunctionT@P6AHI@Z@ns@@
:\for /F %i in (mang.txt) do cppundem.exe %i

:\cppundem.exe .?AVFunctionBase@ns@@
 ?? ns::AVFunctionBase
class ns::FunctionBase

:\cppundem.exe .?AV?$FunctionT@P6AXPAX@Z@ns@@
?AV?$FunctionT@P6AXPAX@Z@ns@@
class ns::FunctionT<void (__cdecl*)(void *)>

:\cppundem.exe .?AV?$FunctionT@P6AHH@Z@ns@@
?AV?$FunctionT@P6AHH@Z@ns@@
class ns::FunctionT<int (__cdecl*)(int)>

:\cppundem.exe .?AV?$FunctionT@P6AHPB_W@Z@ns@@
?AV?$FunctionT@P6AHPB_W@Z@ns@@
class ns::FunctionT<int (__cdecl*)(wchar_t const *)>

:\cppundem.exe .?AV?$FunctionT@P6AHI@Z@ns@@
?AV?$FunctionT@P6AHI@Z@ns@@
class ns::FunctionT<int (__cdecl*)(unsigned int)>

:\
  • Hey there, thanks for your work on this; however, the resulting names are not fully correct, AV and AU are not part of the name but output in your sample. – Ray Koopa Jan 31 at 13:36
  • well if you need type pass UNDNAME_NO_ARGUMENTS – blabb Jan 31 at 17:38
  • I see, yeah that looks better. – Ray Koopa Jan 31 at 17:48
0

Via Igor Skochinsky's classic IDA RTTI scripts, I found out that the names I posted are equivalent to typeid(struct xyz) (as I've added to my question).

Abusing creating a valid mangled ctor name from a substring of them and then stripping the ctor parts from the result yields great class names.

Here's my Python script and the results:

def demangle(name):
    # Check if this even is a problematic typeid.
    if name.startswith(".?A"):
        # Remove the .?AU or .?AV prefix.
        name = name[4:]
        # Demangle it as a default public constructor call.
        ctor_name = "??0" + name + "QAE@XZ"
        name = your_default_msvc_demangler.demangle(ctor_name)
        # Strip constructor pre and suffixes again (accessor / cconv and parameter list).
        name = name[len("public: __thiscall "):name.rfind("(")]
        # Remove ctor name.
        parts = name.split("::")
        return "::".join(parts[:-1])
    else:
        return your_default_msvc_demangler.demangle(name)

And here are sample results for the names given in my question:

exception
    boost::escaped_list_error
    stlp_std::runtime_error

ns::FunctionBase::FunctionBase
    ns::FunctionT<void (__cdecl *)(void *)>
    ns::FunctionT<int (__cdecl *)(int)>
    ns::FunctionT<int (__cdecl *)(wchar_t const *)>
    ns::FunctionT<int (__cdecl *)(unsigned int)>

Probably not very elegant, but working.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.