|
| 1 | +/** |
| 2 | +* This module provides functions to demangle C++ symbols |
| 3 | +* |
| 4 | +* For POSIX platforms the function `__cxa_demangle` present in Itanium C++ ABI does the demangling, therefore this module |
| 5 | +* depends on the C++ standard library which must be linked dynamically or available for research through the dynamic loader. |
| 6 | +* |
| 7 | +* For Windows platform the function `UnDecorateSymbolName` present in the Debug Help Library does the demangling, |
| 8 | +* which should be available for all versions of Windows supported by D. |
| 9 | +* |
| 10 | +* Copyright: Copyright © 2020, The D Language Foundation |
| 11 | +* License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). |
| 12 | +* Authors: Ernesto Castellotti |
| 13 | +* Source: $(DRUNTIMESRC core/internal/_cppdemangle.d) |
| 14 | +* See_Also: |
| 15 | +* https://itanium-cxx-abi.github.io/cxx-abi/abi.html#demangler |
| 16 | +* https://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-html-USERS-4.3/a01696.html |
| 17 | +* https://libcxxabi.llvm.org/spec.html |
| 18 | +* https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-undecoratesymbolname |
| 19 | +*/ |
| 20 | +module core.internal.cppdemangle; |
| 21 | + |
| 22 | +/** |
| 23 | +* Demangles C++ mangled names. |
| 24 | +* |
| 25 | +* The output buffer and return will be contains the demangled name of C++ symbol if the demangling is successful. |
| 26 | +* |
| 27 | +* This function will fail and the mangled name in input will be returned if: |
| 28 | +* 1. The symbol to be processed does not follow the Itanium ABI for POSIX or Visual C++ for Windows, or more simply it is not a C++ name |
| 29 | +* 2. The platform is not compatible or the library needed to perform the demangling was not found or linked with the executable |
| 30 | +* 3. The size of the output buffer is not sufficient to contain the demangled name with its prefix |
| 31 | +* |
| 32 | +* This function will fail and null will be returned if: |
| 33 | +* 1. The output buffer is null |
| 34 | +* 2. The function has failed (see list above) and output buffer is unable to contain the mangled name in input |
| 35 | +* |
| 36 | + * Params: |
| 37 | + * mangledName = The string to demangle. |
| 38 | + * outputBuffer = The destination buffer, if the size of the destination buffer is <= the size of cppdemangle output the return string would be incomplete. |
| 39 | + * prefix = Specifies the prefix to be added to the beginning of the string containing the demangled name |
| 40 | + * |
| 41 | + * Returns: |
| 42 | + * The demangled name or the original string if the name is not a mangled C++ name. |
| 43 | + */ |
| 44 | +char[] cppdemangle(const(char)[] mangledName, char[] outputBuffer, string prefix = "") @trusted |
| 45 | +{ |
| 46 | + CPPDemangle.initialize(); |
| 47 | + return CPPDemangle.instance.cppdemangle(mangledName, outputBuffer, prefix); |
| 48 | +} |
| 49 | + |
| 50 | +package struct CPPDemangle |
| 51 | +{ |
| 52 | + private __gshared CPPDemangle _instance; |
| 53 | + private __gshared bool isInitialized; |
| 54 | + |
| 55 | + version (Posix) |
| 56 | + { |
| 57 | + @nogc nothrow extern(C) |
| 58 | + { |
| 59 | + private extern(C) char* function(const char* mangledName, char* outputBuffer, size_t* length, int* status) __cxa_demangle; |
| 60 | + } |
| 61 | + |
| 62 | + version (OSX) |
| 63 | + private static immutable names = ["libc++abi.dylib", "libstdc++.dylib"]; |
| 64 | + else |
| 65 | + { |
| 66 | + private static immutable names = ["libstdc++.so", "libstdc++.so.6", "libstdc++.so.5", |
| 67 | + "libc++abi.so", "libc++abi.so.1"]; |
| 68 | + } |
| 69 | + |
| 70 | + private __gshared void* _handle; |
| 71 | + |
| 72 | + shared static ~this() { |
| 73 | + import core.sys.posix.dlfcn : dlclose; |
| 74 | + |
| 75 | + if (isInitialized) |
| 76 | + { |
| 77 | + dlclose(_handle); |
| 78 | + _handle = null; |
| 79 | + isInitialized = false; |
| 80 | + } |
| 81 | + } |
| 82 | + } |
| 83 | + |
| 84 | + version (Windows) |
| 85 | + { |
| 86 | + import core.sys.windows.dbghelp : UnDecorateSymbolNameFunc; |
| 87 | + |
| 88 | + @nogc nothrow extern(System) |
| 89 | + { |
| 90 | + private UnDecorateSymbolNameFunc UnDecorateSymbolName; |
| 91 | + } |
| 92 | + } |
| 93 | + |
| 94 | + char[] cppdemangle(const(char)[] mangledName, char[] outputBuffer, string prefix = "") @safe |
| 95 | + { |
| 96 | + auto prefixEmpty = prefix.length <= 0; |
| 97 | + auto demangleOffset = prefixEmpty ? 0 : prefix.length + 1; // Add prefix + space |
| 98 | + |
| 99 | + if (outputBuffer is null) |
| 100 | + { |
| 101 | + return null; |
| 102 | + } |
| 103 | + |
| 104 | + if (outputBuffer.length < demangleOffset) |
| 105 | + { |
| 106 | + return copyResult(mangledName, outputBuffer); |
| 107 | + } |
| 108 | + |
| 109 | + auto demangle = _cppdemangle(mangledName, outputBuffer[demangleOffset..$]); |
| 110 | + |
| 111 | + if (demangle is null) |
| 112 | + { |
| 113 | + return copyResult(mangledName, outputBuffer); |
| 114 | + } |
| 115 | + |
| 116 | + if (!prefixEmpty) |
| 117 | + { |
| 118 | + outputBuffer[0..demangleOffset - 1] = prefix; |
| 119 | + outputBuffer[demangleOffset - 1] = ' '; |
| 120 | + } |
| 121 | + |
| 122 | + return outputBuffer[0..(demangle.length + demangleOffset)]; |
| 123 | + } |
| 124 | + |
| 125 | + static CPPDemangleStatus initialize() |
| 126 | + { |
| 127 | + if (isInitialized) |
| 128 | + { |
| 129 | + return CPPDemangleStatus.INITIALIZED; |
| 130 | + } |
| 131 | + |
| 132 | + version (iOS) |
| 133 | + { |
| 134 | + // Not supported (dlopen doesn't work) |
| 135 | + // Fix me |
| 136 | + return CPPDemangleStatus.LOAD_ERROR; |
| 137 | + } |
| 138 | + else version (Posix) |
| 139 | + { |
| 140 | + import core.sys.posix.dlfcn : dlsym, dlopen, dlclose, RTLD_LAZY; |
| 141 | + |
| 142 | + auto handle = dlopen(null, RTLD_LAZY); |
| 143 | + assert(handle !is null); |
| 144 | + auto p = dlsym(handle, "__cxa_demangle"); |
| 145 | + |
| 146 | + if (p !is null) |
| 147 | + { |
| 148 | + _handle = handle; |
| 149 | + _instance.__cxa_demangle = cast(typeof(CPPDemangle.__cxa_demangle)) p; |
| 150 | + isInitialized = true; |
| 151 | + return CPPDemangleStatus.INITIALIZED; |
| 152 | + } |
| 153 | + |
| 154 | + dlclose(handle); |
| 155 | + |
| 156 | + foreach (name; names) |
| 157 | + { |
| 158 | + handle = dlopen(name.ptr, RTLD_LAZY); |
| 159 | + |
| 160 | + if (handle !is null) |
| 161 | + { |
| 162 | + break; |
| 163 | + } |
| 164 | + } |
| 165 | + |
| 166 | + if (handle is null) |
| 167 | + { |
| 168 | + return CPPDemangleStatus.LOAD_ERROR; |
| 169 | + } |
| 170 | + |
| 171 | + p = dlsym(handle, "__cxa_demangle"); |
| 172 | + |
| 173 | + if (p !is null) |
| 174 | + { |
| 175 | + _handle = handle; |
| 176 | + _instance.__cxa_demangle = cast(typeof(CPPDemangle.__cxa_demangle)) p; |
| 177 | + isInitialized = true; |
| 178 | + return CPPDemangleStatus.INITIALIZED; |
| 179 | + } |
| 180 | + else |
| 181 | + { |
| 182 | + return CPPDemangleStatus.SYMBOL_ERROR; |
| 183 | + } |
| 184 | + } |
| 185 | + else version (Windows) |
| 186 | + { |
| 187 | + import core.sys.windows.dbghelp : DbgHelp; |
| 188 | + |
| 189 | + auto dbgHelp = DbgHelp.get(); |
| 190 | + |
| 191 | + if (dbgHelp is null) |
| 192 | + { |
| 193 | + return CPPDemangleStatus.LOAD_ERROR; |
| 194 | + } |
| 195 | + |
| 196 | + auto func = dbgHelp.UnDecorateSymbolName; |
| 197 | + |
| 198 | + if (dbgHelp.UnDecorateSymbolName !is null) |
| 199 | + { |
| 200 | + _instance.UnDecorateSymbolName = dbgHelp.UnDecorateSymbolName; |
| 201 | + isInitialized = true; |
| 202 | + return CPPDemangleStatus.INITIALIZED; |
| 203 | + } |
| 204 | + else |
| 205 | + { |
| 206 | + return CPPDemangleStatus.SYMBOL_ERROR; |
| 207 | + } |
| 208 | + } |
| 209 | + else |
| 210 | + { |
| 211 | + // Platform not supported |
| 212 | + return CPPDemangleStatus.LOAD_ERROR; |
| 213 | + } |
| 214 | + } |
| 215 | + |
| 216 | + static CPPDemangle instance() @nogc nothrow |
| 217 | + { |
| 218 | + return _instance; |
| 219 | + } |
| 220 | + |
| 221 | + private char[] _cppdemangle(const(char)[] mangledName, char[] outputBuffer) @trusted |
| 222 | + { |
| 223 | + import core.memory : pureCalloc, pureFree; |
| 224 | + import core.stdc.string : strlen; |
| 225 | + |
| 226 | + if (!isInitialized) |
| 227 | + { |
| 228 | + return null; |
| 229 | + } |
| 230 | + |
| 231 | + auto mangledNamePtr = cast(char*) pureCalloc(mangledName.length + 1, char.sizeof); |
| 232 | + |
| 233 | + scope(exit) |
| 234 | + { |
| 235 | + if (mangledNamePtr !is null) |
| 236 | + { |
| 237 | + pureFree(mangledNamePtr); |
| 238 | + } |
| 239 | + } |
| 240 | + |
| 241 | + mangledNamePtr[0..mangledName.length] = mangledName[]; |
| 242 | + |
| 243 | + version (Posix) |
| 244 | + { |
| 245 | + int status; |
| 246 | + auto demangledName = _instance.__cxa_demangle(mangledNamePtr, null, null, &status); |
| 247 | + // NOTE: Due to the implementation of __cxa_demangle, the result of the function |
| 248 | + // will be a pointer to arrays of characters of unknown size before the call to |
| 249 | + // the function. |
| 250 | + // It is in no way possible to pass the output buffer of this function and perform |
| 251 | + // the demangling if its size is sufficient directly by calling __cxa_demangle, |
| 252 | + // because if the size was insufficient __cxa_demangle would try to increase |
| 253 | + // it through realloc. |
| 254 | + // Tto use this function safely, it is therefore necessary to allow the necessary |
| 255 | + // memory to be allocated (just pass null) and then copy (if the size is sufficient) |
| 256 | + // the result into the output buffer. |
| 257 | + |
| 258 | + if (status != 0 && demangledName is null) |
| 259 | + { |
| 260 | + return null; |
| 261 | + } |
| 262 | + |
| 263 | + scope(exit) |
| 264 | + { |
| 265 | + if (demangledName !is null) |
| 266 | + { |
| 267 | + pureFree(demangledName); |
| 268 | + } |
| 269 | + } |
| 270 | + |
| 271 | + return copyResult(demangledName[0..strlen(demangledName)], outputBuffer); |
| 272 | + } |
| 273 | + |
| 274 | + version (Windows) |
| 275 | + { |
| 276 | + import core.sys.windows.windef : DWORD; |
| 277 | + |
| 278 | + auto maxStringLen = (outputBuffer.length > DWORD.max) ? DWORD.max : cast(DWORD) outputBuffer.length; |
| 279 | + // NOTE: UnDecorateSymbolName expects to receive a length in DWORD (uint) instead |
| 280 | + // outputBuffer.length would be ulong. |
| 281 | + // To make a safe cast I make sure not to exceed DWORD.max |
| 282 | + |
| 283 | + auto bufferLen = _instance.UnDecorateSymbolName(mangledNamePtr, outputBuffer.ptr, maxStringLen, 0); |
| 284 | + |
| 285 | + if (bufferLen <= 0) |
| 286 | + { |
| 287 | + return null; |
| 288 | + } |
| 289 | + |
| 290 | + return outputBuffer[0..bufferLen]; |
| 291 | + } |
| 292 | + } |
| 293 | +} |
| 294 | + |
| 295 | +package enum CPPDemangleStatus |
| 296 | +{ |
| 297 | + INITIALIZED = 1, |
| 298 | + LOAD_ERROR = -1, |
| 299 | + SYMBOL_ERROR = -2 |
| 300 | +} |
| 301 | + |
| 302 | +package char[] copyResult(const(char)[] input, char[] output) @safe @nogc nothrow |
| 303 | +{ |
| 304 | + if (input.length > output.length) |
| 305 | + { |
| 306 | + return null; |
| 307 | + } |
| 308 | + |
| 309 | + output[0..input.length] = input[0..input.length]; |
| 310 | + return output[0..input.length]; |
| 311 | +} |
0 commit comments