Skip to content
This repository was archived by the owner on Oct 12, 2022. It is now read-only.

Commit bbe2da0

Browse files
author
Ernesto Castellotti
committed
Add cppdemangle in druntime
Having a C++ symbol demangler is very useful, with this it will be possible to implement the C++ symbol demangle in the stacktrace. The demangling is done in Posix platform by function __cxa_demangle presents in Itanium C++ ABI (https://itanium-cxx-abi.github.io/cxx-abi/abi.html#demangler). Itanium C++ ABI is used practically in all modern C++ compilers on Posix, however old compilers (like GCC < 3.0) used a different name mangling, this is not a problem because DMD in fact only supports Itanium C++ ABI on Posix. For Windows instead the implementation it is provided by the UnDecorateSymbolName function present in the Debug Help Library. (https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-undecoratesymbolname) Signed-off-by: Ernesto Castellotti <[email protected]>
1 parent 89486de commit bbe2da0

File tree

4 files changed

+316
-0
lines changed

4 files changed

+316
-0
lines changed

mak/COPY

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ COPY=\
2525
$(IMPDIR)\core\internal\atomic.d \
2626
$(IMPDIR)\core\internal\attributes.d \
2727
$(IMPDIR)\core\internal\convert.d \
28+
$(IMPDIR)\core\internal\cppdemangle.d \
2829
$(IMPDIR)\core\internal\dassert.d \
2930
$(IMPDIR)\core\internal\destruction.d \
3031
$(IMPDIR)\core\internal\entrypoint.d \

mak/SRCS

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ SRCS=\
2525
src\core\internal\atomic.d \
2626
src\core\internal\attributes.d \
2727
src\core\internal\convert.d \
28+
src\core\internal\cppdemangle.d \
2829
src\core\internal\dassert.d \
2930
src\core\internal\destruction.d \
3031
src\core\internal\entrypoint.d \

mak/WINDOWS

+3
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,9 @@ $(IMPDIR)\core\internal\attributes.d : src\core\internal\attributes.d
132132
$(IMPDIR)\core\internal\convert.d : src\core\internal\convert.d
133133
copy $** $@
134134

135+
$(IMPDIR)\core\internal\cppdemangle.d : src\core\internal\cppdemangle.d
136+
copy $** $@
137+
135138
$(IMPDIR)\core\internal\dassert.d : src\core\internal\dassert.d
136139
copy $** $@
137140

src/core/internal/cppdemangle.d

+311
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
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 @nogc nothrow
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 @nogc nothrow
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

Comments
 (0)