[PATCH v2] libstdc++: Improve ostream output for std::stacktrace

Jonathan Wakely jwakely@redhat.com
Thu Oct 16 11:12:53 GMT 2025


With this change locations that don't have a source file look like this:

  18# __libc_start_call_main at [0x7fd6568f6574]
  19# __libc_start_main@GLIBC_2.2.5 at [0x7fd6568f6627]
  20# _start at [0x4006a4]

Instead of:

  18# __libc_start_call_main at :0
  19# __libc_start_main@GLIBC_2.2.5 at :0
  20# _start at :0

For a non-empty stacktrace_entry, if the function name returned by
description() is an empty string we now print "<unknown>" for the
function name. For an empty (e.g. default constructed) stacktrace_entry
the entire string representation is now "<unknown>" instead of an empty
string.

Instead of printing "<unknown>" for the function name, we could set that
string in the stacktrace_entry::_Info object, so that description()
returns "<unknown>" and then operator<< wouldn't need to handle an empty
description() string. However, returning an empty string from that
function seems simpler for users to detect, rather than having to parse
"<unknown>".

We could also choose a different string for an empty stacktrace_entry,
maybe "<none>" or "<invalid>", but "<unknown>" seems OK for now.

libstdc++-v3/ChangeLog:

	* include/std/stacktrace
	(operator<<(ostream&, const stacktrace_entry&)): Improve output
	when description() or source_file() returns an empty string,
	or the stacktrace_entry is invalid.
	(operator<<(ostream&, const basic_stacktrace<A>&)): Use
	size_type of the correct specialization.
---

v2: Restore fmtflags as pointed out by Nathan. Adjust control flow in
operator<< as suggested by Tomasz.

Tested x86_64-linux.

 libstdc++-v3/include/std/stacktrace | 29 +++++++++++++++++++++++++----
 1 file changed, 25 insertions(+), 4 deletions(-)

diff --git a/libstdc++-v3/include/std/stacktrace b/libstdc++-v3/include/std/stacktrace
index b9e260e19f89..28ffda76328f 100644
--- a/libstdc++-v3/include/std/stacktrace
+++ b/libstdc++-v3/include/std/stacktrace
@@ -685,11 +685,31 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   {
     string __desc, __file;
     int __line;
-    if (__f._M_get_info(&__desc, &__file, &__line))
+    if (__f._M_get_info(&__desc, &__file, &__line)) [[likely]]
       {
-	__os.width(4);
-	__os << __desc << " at " << __file << ':' << __line;
+	if (__desc.empty()) [[unlikely]]
+	  __os << "<unknown>";
+	else
+	  __os << __desc;
+	__os << string_view(" at ");
+	if (!__file.empty()) [[likely]]
+	  return __os << __file << ':' << __line;
+	// else fall through and write native_handle() as the location.
       }
+
+    if (__f) [[likely]]
+      {
+	struct _Flag_guard
+	{
+	  ios_base& _M_ios;
+	  ios_base::fmtflags _M_f;
+	  ~_Flag_guard() { _M_ios.setf(_M_f); }
+	};
+	_Flag_guard _ = { __os, __os.setf(ios::hex, ios::basefield) };
+	__os << "[0x" << __f.native_handle() << ']';
+      }
+    else
+      __os << "<unknown>";
     return __os;
   }
 
@@ -697,7 +717,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     inline ostream&
     operator<<(ostream& __os, const basic_stacktrace<_Allocator>& __st)
     {
-      for (stacktrace::size_type __i = 0; __i < __st.size(); ++__i)
+      using size_type = typename basic_stacktrace<_Allocator>::size_type;
+      for (size_type __i = 0, __size = __st.size(); __i < __size; ++__i)
 	{
 	  __os.width(4);
 	  __os << __i << "# " << __st[__i] << '\n';
-- 
2.51.0



More information about the Libstdc++ mailing list