diff --git a/changelog/cpptrace.dd b/changelog/cpptrace.dd new file mode 100644 index 00000000000..423095326aa --- /dev/null +++ b/changelog/cpptrace.dd @@ -0,0 +1,51 @@ +Added experimental `C++` symbol demanling in the stacktrace + +This feature is availableby passing the following switches to your executable (not to the compiler): +- `--DRT-cpptrace=enable:y`: Enable demangling of C++ symbols +- `--DRT-cpptrace=prefix:`: Change the prefix preceding the demangled C++ name (by default would be `[C++]`) +- `--DRT-cpptrace=noprefix:n`: Disable adding the prefix to C ++ demangle names + +To use this function in the Posix platform you will need to link your executable to the phobos shared library, compile the program by passing -defaultlib=libphobos2.so to DMD. + +Example: + +``` +module cpp_trace.d + +extern(C++) void f1() +{ + throw new Exception("exception"); +} + +void main() +{ + try + { + f1(); + } + catch (Exception e) + { + import core.stdc.stdio; + auto str = e.toString(); + printf("%.*s\n", cast(int)str.length, str.ptr); + } +} +``` + +If you run the executable with the following switches `./ --DRT-cpptrace=enable:y`, you would get this output: + +``` +object.Exception@cpp_trace.d(3): exception +---------------- +src/cpp_trace.d:5 [C++] f1() [ADDR] +src/cpp_trace.d:12 _Dmain [ADDR] +``` + +Instead, if you run the executable with the following switches `./ --DRT-cpptrace=enable:y --DRT-cpptrace=prefix:CPP`, you would get this output: + +``` +object.Exception@cpp_trace.d(5): exception +---------------- +src/cpp_trace.d:5 [CPP] f1() [ADDR] +src/cpp_trace.d:12 _Dmain [ADDR] +``` diff --git a/mak/COPY b/mak/COPY index 3611dbb1919..8c1ac30f2cd 100644 --- a/mak/COPY +++ b/mak/COPY @@ -26,6 +26,7 @@ COPY=\ $(IMPDIR)\core\internal\attributes.d \ $(IMPDIR)\core\internal\convert.d \ $(IMPDIR)\core\internal\cppdemangle.d \ + $(IMPDIR)\core\internal\cpptrace.d \ $(IMPDIR)\core\internal\dassert.d \ $(IMPDIR)\core\internal\destruction.d \ $(IMPDIR)\core\internal\entrypoint.d \ diff --git a/mak/SRCS b/mak/SRCS index b607d66001a..a49a50cb28c 100644 --- a/mak/SRCS +++ b/mak/SRCS @@ -26,6 +26,7 @@ SRCS=\ src\core\internal\attributes.d \ src\core\internal\convert.d \ src\core\internal\cppdemangle.d \ + src\core\internal\cpptrace.d \ src\core\internal\dassert.d \ src\core\internal\destruction.d \ src\core\internal\entrypoint.d \ diff --git a/mak/WINDOWS b/mak/WINDOWS index 02077d735c1..66461c0fbc8 100644 --- a/mak/WINDOWS +++ b/mak/WINDOWS @@ -135,6 +135,9 @@ $(IMPDIR)\core\internal\convert.d : src\core\internal\convert.d $(IMPDIR)\core\internal\cppdemangle.d : src\core\internal\cppdemangle.d copy $** $@ +$(IMPDIR)\core\internal\cpptrace.d : src\core\internal\cpptrace.d + copy $** $@ + $(IMPDIR)\core\internal\dassert.d : src\core\internal\dassert.d copy $** $@ diff --git a/posix.mak b/posix.mak index b6c03c3de71..ee589eef056 100644 --- a/posix.mak +++ b/posix.mak @@ -334,6 +334,9 @@ $(ROOT)/unittest/% : $(ROOT)/unittest/test_runner $(addsuffix /.run,$(filter-out test/shared,$(ADDITIONAL_TESTS))): $(DRUNTIME) test/shared/.run: $(DRUNTIMESO) +ifeq (1,$(SHARED)) + test/exceptions/.run: $(DRUNTIMESO) $(DRUNTIME) +endif test/%/.run: test/%/Makefile $(DMD) $(QUIET)$(MAKE) -C test/$* MODEL=$(MODEL) OS=$(OS) DMD=$(abspath $(DMD)) BUILD=$(BUILD) \ diff --git a/src/core/internal/cpptrace.d b/src/core/internal/cpptrace.d new file mode 100644 index 00000000000..c720f89625d --- /dev/null +++ b/src/core/internal/cpptrace.d @@ -0,0 +1,119 @@ +/** +* This module provides the demangling of C++ symbols for stacktrace +* +* Copyright: Copyright © 2020, The D Language Foundation +* License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). +* Authors: Ernesto Castellotti +* Source: $(DRUNTIMESRC core/internal/_cppdemangle.d) +*/ +module core.internal.cpptrace; + +import core.internal.cppdemangle : CPPDemangle, CPPDemangleStatus; +import core.stdc.stdio : fprintf, stderr; +import core.stdc.stdlib : exit; + +/** +* Demangles C++ mangled names passing the runtime options to cppdemangle. +* +* If it is not a C++ mangled name or cppdemangle is not supported by your platform, the original mangled C++ name will be returned. +* The optional destination buffer and return will be contains the same string if the demangle is successful. +* This function is used to demangle C++ symbols in the stacktrace. +* + * Params: + * buf = The string to demangle. + * dst = The destination buffer, if the size of the destination buffer is <= the size of cppdemangle output the return string would be incomplete. + * + * Returns: + * The demangled name or the original string if the name is not a mangled C++ name. + */ +char[] demangleCppTrace(const(char)[] buf, char[] dst) +{ + import core.internal.cppdemangle : copyResult; + + if (!CPPTrace.config.enable) + { + return copyResult(buf, dst); + } + + return CPPTrace.instance.cppdemangle(buf, dst, !CPPTrace.config.noprefix, CPPTrace.config.prefix); +} + +private struct CPPTrace +{ + __gshared CPPDemangle instance; + __gshared Config config; + + static this() + { + import core.internal.parseoptions : initConfigOptions; + initConfigOptions(config, "cpptrace"); + + version (Posix) + { + version (Shared) + { + // OK! CPPDemangling may be supported + } + else + { + if (config.enable) + { + fprintf(stderr, "C++ demangling is only supported if phobos is dynamically linked. Recompile the program by passing -defaultlib=libphobos2.so to DMD\n"); + exit(1); + assert(0); + } + } + } + + if (config.enable) + { + auto result = CPPDemangle.inizialize(); + + final switch (result) + { + case CPPDemangleStatus.INIZIALIZED: + { + instance = CPPDemangle.instance(); + return; + } + + case CPPDemangleStatus.LOAD_ERROR: + { + version (Posix) + { + fprintf(stderr, "The C++ library for the C++ demangle could not be loaded with dlopen. Please disable the option for C++ demangling.\n"); + } + else version (Windows) + { + fprintf(stderr, "The Debug Help Library could not be loaded. Please disable the option for C++ demangling.\n"); + } + + exit(1); + assert(0); + } + + case CPPDemangleStatus.SYMBOL_ERROR: + { + version (Posix) + { + fprintf(stderr, "The __cxa_demangle symbol was not found in the C++ standard library (maybe it's not compatible). Please disable the option for C++ demangling.\n"); + } + else version (Windows) + { + fprintf(stderr, "The UnDecorateSymbolName symbol was not found in the Debug Help Library (maybe it's not compatible). Please disable the option for C++ demangling.\n"); + } + + exit(1); + assert(0); + } + } + } + } +} + +private struct Config +{ + bool enable; + bool noprefix; + string prefix = "[C++]"; +} diff --git a/src/core/runtime.d b/src/core/runtime.d index 08aa95187a7..793e98eb080 100644 --- a/src/core/runtime.d +++ b/src/core/runtime.d @@ -888,6 +888,16 @@ private: auto sym = demangle(buf[symBeg .. symEnd], fixbuf[symBeg .. $]); + version (Shared) + { + import core.internal.cpptrace; + + if (sym == buf[symBeg .. symEnd]) // Retry with demangleCppTrace + { + sym = demangleCppTrace(buf[symBeg .. symEnd], fixbuf[symBeg .. $]); + } + } + if (sym.ptr !is fixbuf.ptr + symBeg) { // demangle reallocated the buffer, copy the symbol to fixbuf diff --git a/src/core/sys/windows/stacktrace.d b/src/core/sys/windows/stacktrace.d index bb9ed28eba2..794287db690 100644 --- a/src/core/sys/windows/stacktrace.d +++ b/src/core/sys/windows/stacktrace.d @@ -15,6 +15,7 @@ version (Windows): import core.demangle; import core.runtime; +import core.internal.cpptrace; import core.stdc.stdlib; import core.stdc.string; import core.sys.windows.dbghelp; @@ -295,7 +296,14 @@ private: size_t decodeIndex = 0; tempSymName = decodeDmdString(tempSymName, decodeIndex); } - res ~= demangle(tempSymName, demangleBuf); + auto demangledName = demangle(tempSymName, demangleBuf); + + if (demangledName == tempSymName) // Retry with demangleCppTrace + { + demangledName = demangleCppTrace(tempSymName, demangleBuf); + } + + res ~= demangledName; return res; } diff --git a/src/rt/backtrace/dwarf.d b/src/rt/backtrace/dwarf.d index 881453c0e32..caafa757c3b 100644 --- a/src/rt/backtrace/dwarf.d +++ b/src/rt/backtrace/dwarf.d @@ -369,7 +369,21 @@ const(char)[] getDemangledSymbol(const(char)[] btSymbol, return ref char[1024] b { import core.demangle; const mangledName = getMangledSymbolName(btSymbol); - return !mangledName.length ? buffer[0..0] : demangle(mangledName, buffer[]); + if (!mangledName.length) return buffer[0..0]; + + auto demangledName = demangle(mangledName, buffer[]); + + version (Shared) + { + import core.internal.cpptrace; + + if (demangledName == mangledName) // Retry with demangleCppTrace + { + demangledName = demangleCppTrace(mangledName, buffer[]); + } + } + + return demangledName; } T read(T)(ref const(ubyte)[] buffer) @nogc nothrow diff --git a/test/exceptions/Makefile b/test/exceptions/Makefile index 88ba7dbd9cb..2ab5aa3b9ee 100644 --- a/test/exceptions/Makefile +++ b/test/exceptions/Makefile @@ -3,9 +3,16 @@ include ../common.mak TESTS=stderr_msg unittest_assert invalid_memory_operation unknown_gc static_dtor \ future_message refcounted rt_trap_exceptions_drt catch_in_finally +ifeq ($(shell test -e $(DRUNTIMESO) && echo 0), 0) + SHARED=1 +endif + ifeq ($(OS)-$(BUILD),linux-debug) TESTS+=line_trace long_backtrace_trunc rt_trap_exceptions LINE_TRACE_DFLAGS:=-L--export-dynamic + ifeq ($(SHARED), 1) + TESTS+=cpp_trace + endif endif ifeq ($(OS),linux) TESTS+=rt_trap_exceptions_drt_gdb @@ -13,14 +20,23 @@ endif ifeq ($(OS)-$(BUILD),freebsd-debug) TESTS+=line_trace long_backtrace_trunc LINE_TRACE_DFLAGS:=-L--export-dynamic + ifeq (SHARED, 1) + TESTS+=cpp_trace + endif endif ifeq ($(OS)-$(BUILD),dragonflybsd-debug) TESTS+=line_trace long_backtrace_trunc LINE_TRACE_DFLAGS:=-L--export-dynamic + ifeq (SHARED, 1) + TESTS+=cpp_trace + endif endif ifeq ($(OS)-$(BUILD),osx-debug) TESTS+=line_trace long_backtrace_trunc LINE_TRACE_DFLAGS:= + ifeq (SHARED, 1) + TESTS+=cpp_trace + endif endif ifeq ($(BUILD),debug) @@ -56,6 +72,14 @@ $(ROOT)/chain.done: $(ROOT)/chain @rm -f $(ROOT)/chain.output @touch $@ +$(ROOT)/cpp_trace.done: $(ROOT)/cpp_trace + @echo Testing cpp_trace + $(QUIET)$(TIMELIMIT)$(ROOT)/cpp_trace $(RUN_ARGS) > $(ROOT)/cpp_trace.output + # Use sed to canonicalize cpp_trace.output and compare against expected output in cpp_trace.exp + $(QUIET)$(SED) "s/\[0x[0-9a-f]*\]/\[ADDR\]/g; s/scope //g; s/Nl//g" $(ROOT)/cpp_trace.output | $(DIFF) cpp_trace.exp - + @rm -f $(ROOT)/cpp_trace.output + @touch $@ + $(ROOT)/stderr_msg.done: STDERR_EXP="stderr_msg msg" $(ROOT)/unittest_assert.done: STDERR_EXP="unittest_assert msg" $(ROOT)/invalid_memory_operation.done: STDERR_EXP="InvalidMemoryOperationError" @@ -99,6 +123,8 @@ $(ROOT)/line_trace: DFLAGS+=$(LINE_TRACE_DFLAGS) $(ROOT)/rt_trap_exceptions_drt: DFLAGS+=-g $(ROOT)/assert_fail: DFLAGS+=-checkaction=context $(ROOT)/refcounted: DFLAGS+=-dip1008 +$(ROOT)/cpp_trace.done: RUN_ARGS="--DRT-cpptrace=enable:y" +$(ROOT)/cpp_trace: DFLAGS+=-L$(DRUNTIMESO) $(LINE_TRACE_DFLAGS) $(ROOT)/%: $(SRC)/%.d $(DMD) $(DRUNTIME) $(QUIET)$(DMD) $(DFLAGS) -of$@ $< diff --git a/test/exceptions/cpp_trace.exp b/test/exceptions/cpp_trace.exp new file mode 100644 index 00000000000..8bd9dc4693a --- /dev/null +++ b/test/exceptions/cpp_trace.exp @@ -0,0 +1,12 @@ +object.Exception@src/cpp_trace.d(40): exception +---------------- +src/cpp_trace.d:40 [C++] f1() [ADDR] +src/cpp_trace.d:5 _Dmain [ADDR] +object.Exception@src/cpp_trace.d(47): exception +---------------- +src/cpp_trace.d:47 [C++] S1::f1() [ADDR] +src/cpp_trace.d:16 _Dmain [ADDR] +object.Exception@src/cpp_trace.d(57): exception +---------------- +src/cpp_trace.d:57 [C++] C1::f1(unsigned char, unsigned char*, unsigned char&) [ADDR] +src/cpp_trace.d:28 _Dmain [ADDR] diff --git a/test/exceptions/src/cpp_trace.d b/test/exceptions/src/cpp_trace.d new file mode 100644 index 00000000000..7d6eb50441c --- /dev/null +++ b/test/exceptions/src/cpp_trace.d @@ -0,0 +1,60 @@ +void main() +{ + try + { + f1(); + } + catch (Exception e) + { + import core.stdc.stdio; + auto str = e.toString(); + printf("%.*s\n", cast(int)str.length, str.ptr); + } + + try + { + S1.f1(); + } + catch (Exception e) + { + import core.stdc.stdio; + auto str = e.toString(); + printf("%.*s\n", cast(int)str.length, str.ptr); + } + + try + { + ubyte nothing; + C1!ubyte.f1(nothing, ¬hing, nothing); + } + catch (Exception e) + { + import core.stdc.stdio; + auto str = e.toString(); + printf("%.*s\n", cast(int)str.length, str.ptr); + } +} + +extern(C++) void f1() +{ + throw new Exception("exception"); +} + +extern(C++) struct S1 +{ + static void f1() + { + throw new Exception("exception"); + } +} + +extern(C++) +{ + class C1(T) + { + static T f1(T arg1, T* arg2, ref T arg3) + { + throw new Exception("exception"); + } + } +}