This is the mail archive of the libstdc++@gcc.gnu.org mailing list for the libstdc++ project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: libbacktrace integration for _GLIBCXX_DEBUG mode


Here is what I come up with.


Regarding allocation in print_function I would also prefer to avoid it. But this patch also aim at creating a backtrace_state object in case of UB so the alloc is perhaps not so important. I can't use string_view as I need to modify it to display only a part of it through fsprintf. I could try to use "%.*s" however. I haven't also consider your remark about template parameters containing '<' yet.


+#if defined(_GLIBCXX_DEBUG_BACKTRACE)
+# if !defined(BACKTRACE_SUPPORTED)
+#  if defined(__has_include) && !__has_include(<backtrace-supported.h>)
+#   error No libbacktrace backtrace-supported.h file found.
+#  endif
+#  include <backtrace-supported.h>
+# endif
+# if !BACKTRACE_SUPPORTED
+#  error libbacktrace not supported.
+# endif
+# include <backtrace.h>
+#else
+# include <stdint.h> // For uintptr_t.

Please use <cstdint> and std::uintptr_t.

I did so but then realized that to do so I had to be in C++11 mode. I used tr1/cstdint in pre-C++11 mode.


+// Extracted from libbacktrace.
+typedef void (*backtrace_error_callback) (void*, const char*, int);
+
+typedef int (*backtrace_full_callback) (void*, uintptr_t, const char*, int,
+                    const char*);

These typedefs should use __reserved_names.

+struct backtrace_state;

Although this one can't use a reserved name, unless we're going to
create opaque wrappers around the libbacktrace type. Introducing t his
non-reserved name means that defining _GLIBCXX_DEBUG makes the library
non-conforming.

It would be possible to avoid declaring this struct, by making
_M_backtrace_state a void* and creating a wrapper function for
backtrace_create_state, and a weak symbol in the library. I'll have to
think about this more

My main problem was to be able to respect the ODR even when !BACKTRACE_SUPPORTED. To do so I eventually realized that I had to limit the feature to system where uintptr_t is available which I detect thanks to the _GLIBCXX_USE_C99_STDINT_TR1 macro which is used both in <cstdint> and <tr1/cstdint>.

If you think it is fine I'll document it.

François

diff --git a/libstdc++-v3/doc/xml/manual/debug_mode.xml b/libstdc++-v3/doc/xml/manual/debug_mode.xml
index 23a5df975a2..680b9d5999d 100644
--- a/libstdc++-v3/doc/xml/manual/debug_mode.xml
+++ b/libstdc++-v3/doc/xml/manual/debug_mode.xml
@@ -162,6 +162,13 @@ which always works correctly.
   <code>GLIBCXX_DEBUG_MESSAGE_LENGTH</code> can be used to request a
   different length.</para>
 
+<para>Starting with GCC 10 libstdc++ is able to use
+  <link xmlns:xlink="http://www.w3.org/1999/xlink";
+  xlink:href="https://github.com/ianlancetaylor/libbacktrace";>libbacktrace</link>
+  to produce backtraces on error. Use <code>-D_GLIBCXX_DEBUG_BACKTRACE</code> to
+  activate it. Note that if not properly installed or if libbacktrace is not
+  supported, compilation will fail. You'll also have to use
+  <code>-lbacktrace</code> to build your application.</para>
 </section>
 
 <section xml:id="debug_mode.using.specific" xreflabel="Using Specific"><info><title>Using a Specific Debug Container</title></info>
diff --git a/libstdc++-v3/doc/xml/manual/using.xml b/libstdc++-v3/doc/xml/manual/using.xml
index d7fbfe9584d..5769722192c 100644
--- a/libstdc++-v3/doc/xml/manual/using.xml
+++ b/libstdc++-v3/doc/xml/manual/using.xml
@@ -1128,6 +1128,16 @@ g++ -Winvalid-pch -I. -include stdc++.h -H -g -O2 hello.cc -o test.exe
 	extensions and libstdc++-specific behavior into errors.
       </para>
     </listitem></varlistentry>
+    <varlistentry><term><code>_GLIBCXX_DEBUG_BACKTRACE</code></term>
+    <listitem>
+      <para>
+	Undefined by default. Considered only if <code>_GLIBCXX_DEBUG</code>
+	is defined. When defined, checks for <link xmlns:xlink="http://www.w3.org/1999/xlink";
+	xlink:href="https://github.com/ianlancetaylor/libbacktrace";>libbacktrace</link>
+	support and use it to display backtraces on
+	<link linkend="manual.ext.debug_mode">debug mode</link> assertions.
+      </para>
+    </listitem></varlistentry>
     <varlistentry><term><code>_GLIBCXX_PARALLEL</code></term>
     <listitem>
       <para>Undefined by default. When defined, compiles user code
@@ -1634,6 +1644,17 @@ A quick read of the relevant part of the GCC
       header will remain compatible between different GCC releases.
     </para>
     </section>
+
+    <section xml:id="manual.intro.using.linkage.externals" xreflabel="External Libraries"><info><title>External Libraries</title></info>
+
+    <para>
+      GCC 10 <link linkend="manual.ext.debug_mode">debug mode</link> is able
+      produce backtraces thanks to <link xmlns:xlink="http://www.w3.org/1999/xlink";
+      xlink:href="https://github.com/ianlancetaylor/libbacktrace";>libbacktrace</link>.
+      To use the library you should define <code>_GLIBCXX_DEBUG_BACKTRACE</code>
+      and link with <option>-lbacktrace</option>.
+    </para>
+    </section>
   </section>
 
   <section xml:id="manual.intro.using.concurrency" xreflabel="Concurrency"><info><title>Concurrency</title></info>
diff --git a/libstdc++-v3/include/debug/formatter.h b/libstdc++-v3/include/debug/formatter.h
index 220379994c0..9e5962a4744 100644
--- a/libstdc++-v3/include/debug/formatter.h
+++ b/libstdc++-v3/include/debug/formatter.h
@@ -31,6 +31,38 @@
 
 #include <bits/c++config.h>
 
+#if defined(_GLIBCXX_DEBUG_BACKTRACE)
+# if !defined(BACKTRACE_SUPPORTED)
+#  include <backtrace-supported.h>
+# endif
+#endif
+
+#if BACKTRACE_SUPPORTED
+# define _GLIBCXX_DEBUG_USE_BACKTRACE 1
+# include <backtrace.h>
+typedef backtrace_error_callback __backtrace_error_cb;
+typedef backtrace_full_callback __backtrace_full_cb;
+typedef backtrace_state __backtrace_state;
+#elif defined (_GLIBCXX_USE_C99_STDINT_TR1)
+# define _GLIBCXX_DEBUG_USE_BACKTRACE 1
+
+# if __cplusplus >= 201103L
+#  include <cstdint> // For std::uintptr_t.
+typedef int (*__backtrace_full_cb) (void*, std::uintptr_t, const char*,
+				    int, const char*);
+# else
+#  include <tr1/cstdint>
+typedef int (*__backtrace_full_cb) (void*, std::tr1::uintptr_t, const char*,
+				    int, const char*);
+# endif
+
+typedef void (*__backtrace_error_cb) (void*, const char*, int);
+
+struct __backtrace_state;
+#else
+# define _GLIBCXX_DEBUG_USE_BACKTRACE 0
+#endif
+
 #if __cpp_rtti
 # include <typeinfo>
 # define _GLIBCXX_TYPEID(_Type) &typeid(_Type)
@@ -156,6 +188,12 @@ namespace __gnu_debug
 
   class _Error_formatter
   {
+#if _GLIBCXX_DEBUG_USE_BACKTRACE
+    typedef int (*_Bt_full_cb) (__backtrace_state*, int,
+				__backtrace_full_cb,
+				__backtrace_error_cb, void*);
+#endif
+
     // Tags denoting the type of parameter for construction
     struct _Is_iterator { };
     struct _Is_iterator_value_type { };
@@ -558,11 +596,25 @@ namespace __gnu_debug
     _M_print_string(const char* __string) const _GLIBCXX_DEPRECATED;
 #endif
 
+#if _GLIBCXX_DEBUG_USE_BACKTRACE
+    void
+    _M_print_backtrace(__backtrace_full_cb __cb, void* __data) const
+    { _M_backtrace_full_func(_M_backtrace_state, 1, __cb, 0, __data); }
+#endif
+
   private:
     _Error_formatter(const char* __file, unsigned int __line,
 		     const char* __function)
     : _M_file(__file), _M_line(__line), _M_num_parameters(0), _M_text(0)
     , _M_function(__function)
+#if _GLIBCXX_DEBUG_USE_BACKTRACE
+# if defined(_GLIBCXX_DEBUG_BACKTRACE) && BACKTRACE_SUPPORTED
+    , _M_backtrace_state(backtrace_create_state(NULL, 0, NULL, NULL))
+    , _M_backtrace_full_func(&backtrace_full)
+# else
+    , _M_backtrace_state()
+# endif
+#endif
     { }
 
 #if !_GLIBCXX_INLINE_VERSION
@@ -578,6 +630,10 @@ namespace __gnu_debug
     unsigned int	_M_num_parameters;
     const char*		_M_text;
     const char*		_M_function;
+#if _GLIBCXX_DEBUG_USE_BACKTRACE
+    __backtrace_state*	_M_backtrace_state;
+    _Bt_full_cb		_M_backtrace_full_func;
+#endif
 
   public:
     static _Error_formatter&
diff --git a/libstdc++-v3/src/c++11/debug.cc b/libstdc++-v3/src/c++11/debug.cc
index f5a49992efa..2e4de272ad8 100644
--- a/libstdc++-v3/src/c++11/debug.cc
+++ b/libstdc++-v3/src/c++11/debug.cc
@@ -34,16 +34,13 @@
 
 #include <cassert>
 #include <cstdio>
-#include <cctype> // for std::isspace
+#include <cctype>	// for std::isspace.
+#include <cstring>	// for std::strstr.
 
-#include <algorithm> // for std::min
+#include <algorithm>	// for std::min.
+#include <string>
 
-#include <cxxabi.h> // for __cxa_demangle
-
-// libstdc++/85768
-#if 0 // defined _GLIBCXX_HAVE_EXECINFO_H
-# include <execinfo.h> // for backtrace
-#endif
+#include <cxxabi.h>	// for __cxa_demangle.
 
 #include "mutex_pool.h"
 
@@ -571,6 +568,7 @@ namespace
   {
     PrintContext()
     : _M_max_length(78), _M_column(1), _M_first_line(true), _M_wordwrap(false)
+    , _M_demangle_name(false)
     { get_max_length(_M_max_length); }
 
     std::size_t	_M_max_length;
@@ -578,16 +576,18 @@ namespace
     std::size_t	_M_column;
     bool	_M_first_line;
     bool	_M_wordwrap;
+    bool	_M_demangle_name;
   };
 
+  typedef void (*_Print_func_t) (PrintContext&, const char*);
+
   template<size_t Length>
     void
     print_literal(PrintContext& ctx, const char(&word)[Length])
     { print_word(ctx, word, Length - 1); }
 
   void
-  print_word(PrintContext& ctx, const char* word,
-	     std::ptrdiff_t count = -1)
+  print_word(PrintContext& ctx, const char* word, std::ptrdiff_t count)
   {
     size_t length = count >= 0 ? count : __builtin_strlen(word);
     if (length == 0)
@@ -619,7 +619,7 @@ namespace
 	    for (int i = 0; i < ctx._M_indent; ++i)
 	      spacing[i] = ' ';
 	    spacing[ctx._M_indent] = '\0';
-	    fprintf(stderr, "%s", spacing);
+	    fprintf(stderr, spacing);
 	    ctx._M_column += ctx._M_indent;
 	  }
 
@@ -640,6 +640,143 @@ namespace
       }
   }
 
+  void
+  print_word(PrintContext& ctx, const char* word)
+  { print_word(ctx, word, -1); }
+
+  void
+  print_raw(PrintContext&, const char* word)
+  { fprintf(stderr, "%s", word); }
+
+  void
+  print_func_impl(PrintContext& ctx, const char* function,
+		  _Print_func_t print_func)
+  {
+    const char cxx1998[] = "__cxx1998::";
+    const char allocator[] = ", std::allocator<";
+    const char safe_iterator[] = "__gnu_debug::_Safe_iterator<";
+    string func(function);
+    string::size_type index = 0;
+    for (;;)
+      {
+	auto idx1 = func.find(cxx1998, index);
+	auto idx2 = func.find(allocator, index);
+	auto idx3 = ctx._M_demangle_name
+	  ? func.find(safe_iterator, index) : string::npos;
+	if (idx1 != string::npos || idx2 != string::npos ||
+	    idx3 != string::npos)
+	  {
+	    if (idx1 != string::npos
+		&& (idx2 == string::npos || idx1 < idx2)
+		&& (idx3 == string::npos || idx1 < idx3))
+	      {
+		func[idx1] = '\0';
+		print_func(ctx, func.c_str() + index);
+		index = idx1 + sizeof(cxx1998) - 1;
+	      }
+	    else if (idx2 != string::npos
+		     && (idx3 == string::npos || idx2 < idx3))
+	      {
+		func[idx2] = '\0';
+		print_func(ctx, func.c_str() + index);
+
+		// We need to look for the closing '>'
+		idx2 += sizeof(allocator) - 1;
+		int open_bracket = 0;
+		for (; idx2 != func.size(); ++idx2)
+		  {
+		    if (func[idx2] == '>')
+		      {
+			if (open_bracket)
+			  --open_bracket;
+			else
+			  break;
+		      }
+		    else if (func[idx2] == '<')
+		      ++open_bracket;
+		  }
+
+		index = idx2 + 1;
+		idx2 = func.find(" const&", index);
+		if (idx2 == index)
+		  // Allocator parameter qualification, skipped too.
+		  index += sizeof(" const&") - 1;
+	      }
+	    else if (idx3 != string::npos)
+	      {
+		// Just keep the 1st template parameter.
+		// Look for 1st comma outside any additional brackets.
+		idx3 += sizeof(safe_iterator) - 1;
+		char c = func[idx3];
+		func[idx3] = '\0';
+		print_func(ctx, func.c_str() + index);
+		func[idx3] = c;
+		index = idx3;
+		int nb_open_bracket = 0;
+		for (; nb_open_bracket != -1;)
+		  {
+		    auto n = func.find_first_of("<,>", idx3);
+		    switch (func[n])
+		      {
+		      case '<':
+			++nb_open_bracket;
+			break;
+		      case '>':
+			--nb_open_bracket;
+			break;
+		      case ',':
+			if (nb_open_bracket == 0)
+			  {
+			    func[n] = '\0';
+			    print_func_impl(ctx, func.c_str() + index, print_func);
+			    nb_open_bracket = -1;
+			  }
+
+			break;
+		      }
+
+		    idx3 = n + 1;
+		  }
+
+		// Now look for the closing '>'.
+		nb_open_bracket = 0;
+		for (; idx3 != func.size(); ++idx3)
+		  {
+		    if (func[idx3] == '>')
+		      {
+			if (nb_open_bracket)
+			  --nb_open_bracket;
+			else
+			  break;
+		      }
+		    else if (func[idx3] == '<')
+		      ++nb_open_bracket;
+		  }
+
+		index = idx3;
+	      }
+
+	    while (isspace(func[index]))
+	      ++index;
+	  }
+	else
+	  {
+	    print_func(ctx, func.c_str() + index);
+	    break;
+	  }
+      }
+  }
+
+  void
+  print_function(PrintContext& ctx, const char* function,
+		 _Print_func_t print_func)
+  {
+    __try
+      { print_func_impl(ctx, function, print_func); }
+    __catch (...)
+      { print_func(ctx, function); }
+  }
+
   template<size_t Length>
     void
     print_type(PrintContext& ctx,
@@ -653,7 +790,13 @@ namespace
 	  int status;
 	  char* demangled_name =
 	    __cxxabiv1::__cxa_demangle(info->name(), NULL, NULL, &status);
-	  print_word(ctx, status == 0 ? demangled_name : info->name());
+	  if (status == 0)
+	    {
+	      ctx._M_demangle_name = true;
+	      print_function(ctx, demangled_name, &print_word);
+	    }
+	  else
+	    print_word(ctx, info->name());
 	  free(demangled_name);
 	}
     }
@@ -921,10 +1064,10 @@ namespace
   }
 
   void
-  print_string(PrintContext& ctx, const char* string,
+  print_string(PrintContext& ctx, const char* str,
 	       const _Parameter* parameters, std::size_t num_parameters)
   {
-    const char* start = string;
+    const char* start = str;
     const int bufsize = 128;
     char buf[bufsize];
     int bufindex = 0;
@@ -1013,6 +1156,65 @@ namespace
 	print_word(ctx, buf, bufindex);
       }
   }
+
+  void
+  print_string(PrintContext& ctx, const char* str)
+  { print_string(ctx, str, nullptr, 0); }
+
+#if _GLIBCXX_DEBUG_USE_BACKTRACE
+  int
+  print_backtrace(void* data, std::uintptr_t pc, const char* filename,
+		  int lineno, const char* function)
+  {
+    const int bufsize = 64;
+    char buf[bufsize];
+
+    PrintContext& ctx = *static_cast<PrintContext*>(data);
+
+    int written = __builtin_sprintf(buf, "%p ", (void*)pc);
+    print_word(ctx, buf, written);
+
+    int ret = 0;
+    if (function)
+      {
+	int status;
+	char* demangled_name =
+	  __cxxabiv1::__cxa_demangle(function, NULL, NULL, &status);
+	if (status == 0)
+	  {
+	    ctx._M_demangle_name = true;
+	    print_function(ctx, demangled_name, &print_raw);
+	  }
+	else
+	  print_word(ctx, function);
+
+	free(demangled_name);
+	ret = strstr(function, "main") ? 1 : 0;
+      }
+
+    print_literal(ctx, "\n");
+
+    if (filename)
+      {
+	bool wordwrap = false;
+	swap(wordwrap, ctx._M_wordwrap);
+	print_word(ctx, filename);
+
+	if (lineno)
+	  {
+	    written = __builtin_sprintf(buf, ":%u\n", lineno);
+	    print_word(ctx, buf, written);
+	  }
+	else
+	  print_literal(ctx, "\n");
+	swap(wordwrap, ctx._M_wordwrap);
+      }
+    else
+      print_literal(ctx, "???:0\n");
+
+    return ret;
+  }
+#endif
 }
 
 namespace __gnu_debug
@@ -1054,34 +1256,20 @@ namespace __gnu_debug
     if (_M_function)
       {
 	print_literal(ctx, "In function:\n");
-	print_string(ctx, _M_function, nullptr, 0);
+	print_function(ctx, _M_function, &print_string);
 	print_literal(ctx, "\n");
 	ctx._M_first_line = true;
 	print_literal(ctx, "\n");
       }
 
-// libstdc++/85768
-#if 0 //defined _GLIBCXX_HAVE_EXECINFO_H
-    {
-      void* stack[32];
-      int nb = backtrace(stack, 32);
-
-      // Note that we skip current method symbol.
-      if (nb > 1)
+#if _GLIBCXX_DEBUG_USE_BACKTRACE
+    if (_M_backtrace_state)
       {
 	print_literal(ctx, "Backtrace:\n");
-	  auto symbols = backtrace_symbols(stack, nb);
-	  for (int i = 1; i < nb; ++i)
-	    {
-	      print_word(ctx, symbols[i]);
-	      print_literal(ctx, "\n");
-	    }
-
-	  free(symbols);
+	_M_print_backtrace(print_backtrace, &ctx);
 	ctx._M_first_line = true;
 	print_literal(ctx, "\n");
       }
-    }
 #endif
 
     print_literal(ctx, "Error: ");

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]