Search code examples
c++symbolsdemangler

abi::__cxa_demangle cannot demangle symbols?


I am unsure why this fails to demangle symbols:

#include <cxxabi.h>
void _debugBacktrace(code_part part)
{
#if defined(WZ_OS_LINUX) && defined(__GLIBC__)
    void *btv[20];
    unsigned num = backtrace(btv, sizeof(btv) / sizeof(*btv));
    char **btc = backtrace_symbols(btv, num);
    unsigned i;
    char buffer[255];
    for (i = 1; i + 2 < num; ++i) 
    {
        int status = -1;
        size_t demangledLen = 0;
        memset(buffer, 0, 255);
        char *readableName = abi::__cxa_demangle(btc[i], buffer, &demangledLen, &status);
        if (status == 0)
        {
            _debug(0, part, "BT", "%s", readableName);
        }
        else
        {
            _debug(0, part, "BT", "%s [status: %i]", btc[i], status);
        }
        
    }
    free(btc);
#else
    // debugBacktrace not implemented.
#endif
}

Stdout:

error   |02:25:21: [BT:0] ./src/warzone2100(_ZNK5EcKey4signEPKvm+0x98) [0x16405da] [status: -2]
error   |02:25:21: [BT:0] ./src/warzone2100(_Z8recvPing8NETQUEUE+0x1ab) [0x126bc2d] [status: -2]
error   |02:25:21: [BT:0] ./src/warzone2100(_ZN27WzMultiplayerOptionsTitleUI21frontendMultiMessagesEb+0x7d9) [0x11ce219] [status: -2]
error   |02:25:21: [BT:0] ./src/warzone2100(_ZN27WzMultiplayerOptionsTitleUI3runEv+0x45) [0x11cf32b] [status: -2]
error   |02:25:21: [BT:0] ./src/warzone2100(_Z9titleLoopv+0x1aa) [0x1480b35] [status: -2]
error   |02:25:21: [BT:0] ./src/warzone2100() [0x1169cbc] [status: -2]
...
...
### BUT this works?
> addr2line -f 0x16405da -C  -e ./src/warzone2100 
EcKey::sign(void const*, unsigned long) const
/home/docker/code/lib/framework/crc.cpp:284

Compiled (C++11) under docker with:

docker@b12fbaed9c6c:~/code> g++ --version
g++ (SUSE Linux) 11.2.1 20210816 [revision 056e324ce46a7924b5cf10f61010cf9dd2ca10e9]
docker@b12fbaed9c6c:~/code> ld --version
GNU ld (GNU Binutils; openSUSE Tumbleweed) 2.37.20210803-1

Executed on host machine:

> g++ --version
g++ (SUSE Linux) 11.2.1 20220103 [revision d4a1d3c4b377f1d4acb34fe1b55b5088a3f293f6]

> ld -version
GNU ld (GNU Binutils; openSUSE Tumbleweed) 2.37.20211112-3

Solution

  • For one thing, you are not calling __cxa_demangle correctly. Documentation says:

    output_buffer
    A region of memory, allocated with malloc, of *length bytes, into which the demangled name is stored. If output_buffer is not long enough, it is expanded using realloc. output_buffer may instead be NULL; in that case, the demangled name is placed in a region of memory allocated with malloc.

    You are passing a stack buffer instead. Get rid of it (and the pointless memset), and do this instead:

        for (i = 1; i + 2 < num; ++i) 
        {
            int status = -1;
            char *readableName = abi::__cxa_demangle(btc[i], NULL, NULL, &status);
            if (status == 0)
            {
                _debug(0, part, "BT", "%s", readableName);
            }
            else
            {
                _debug(0, part, "BT", "%s [status: %i]", btc[i], status);
            }
            free(readableName);
        }
    

    Update:

    The second problem is that you appear to be expecting that __cxa_demangle() will ignore any characters that aren't part of the mangled name, and demangle the rest. (You didn't show what the actual content of btc[i] is, but from the output it appears to contain strings like ./src/warzone2100(_ZNK5EcKey4signEPKvm+0x98) [0x16405da]).

    (Re-reading the code you posted, btc[i] comes from backtrace_symbols, which is documented to return function name, a hexadecimal offset into the function, and the actual return address, so definitely some "extra" cruft __cxa_demangle() isn't expecting.)

    As the following test demonstrates, __cxa_demangle() does not ignore anything; it needs mangled name, not "stuff which contains mangled name".

    #include <cxxabi.h>
    #include <string.h>
    
    #include <array>
    #include <iostream>
    
    int main()
    {
      const std::array<const char *, 5> bt = {
        "./src/warzone2100(_ZNK5EcKey4signEPKvm+0x98) [0x16405da]",
        "./src/warzone2100(_Z8recvPing8NETQUEUE+0x1ab) [0x126bc2d]",
        "./src/warzone2100(_ZN27WzMultiplayerOptionsTitleUI21frontendMultiMessagesEb+0x7d9) [0x11ce219]",
        "./src/warzone2100(_ZN27WzMultiplayerOptionsTitleUI3runEv+0x45) [0x11cf32b]",
        "./src/warzone2100(_Z9titleLoopv+0x1aa) [0x1480b35]",
      };
      std::cout << "Wrong way:" << std::endl << std::endl;
      for (const char* p : bt) {
        int status;
        char *demangled =  abi::__cxa_demangle(p, NULL, NULL, &status);
        std::cout << p << " " << status << std::endl;
        free(demangled);
      }
    
      auto trim = [](const char *in, char *out) {
        const char *begin = strchr(in, '_');
        const char *end = strchr(begin, '+');
        memcpy(out, begin, end - begin);
        out[end - begin] = '\0';
      };
    
      std::cout << std::endl << "Right way:" << std::endl << std::endl;
      for (const char* p : bt) {
        int status;
        char buf[1024];
    
        trim(p, buf);
        char *demangled =  abi::__cxa_demangle(buf, NULL, NULL, &status);
        std::cout << buf << " -> " << demangled << " " << status << std::endl;
        free(demangled);
      }
    }
    

    Using (Debian 11.2.0-14), I get:

    g++ -g foo.cc && ./a.out
    Wrong way:
    
    ./src/warzone2100(_ZNK5EcKey4signEPKvm+0x98) [0x16405da] -2
    ./src/warzone2100(_Z8recvPing8NETQUEUE+0x1ab) [0x126bc2d] -2
    ./src/warzone2100(_ZN27WzMultiplayerOptionsTitleUI21frontendMultiMessagesEb+0x7d9) [0x11ce219] -2
    ./src/warzone2100(_ZN27WzMultiplayerOptionsTitleUI3runEv+0x45) [0x11cf32b] -2
    ./src/warzone2100(_Z9titleLoopv+0x1aa) [0x1480b35] -2
    
    Right way:
    
    _ZNK5EcKey4signEPKvm -> EcKey::sign(void const*, unsigned long) const 0
    _Z8recvPing8NETQUEUE -> recvPing(NETQUEUE) 0
    _ZN27WzMultiplayerOptionsTitleUI21frontendMultiMessagesEb -> WzMultiplayerOptionsTitleUI::frontendMultiMessages(bool) 0
    _ZN27WzMultiplayerOptionsTitleUI3runEv -> WzMultiplayerOptionsTitleUI::run() 0
    _Z9titleLoopv -> titleLoop() 0