Skip to content

Conversation

vogelsgesang
Copy link
Member

@vogelsgesang vogelsgesang commented Oct 6, 2025

In "Debugging C++ Coroutines", we provide a gdb script to aid with debugging C++ coroutines in gdb. This commit updates said script to make it easier to use and more robust.

The commit contains the following user-facing changes:

  • show-coro-frame was replaced by a pretty-printer for std::coroutine_handle. This is much easier to use than a custom command since it works out-of-the-box with p and in my IDE's variable view (tested using VS-Code)
  • the new get_coro_{frame,promise} functions can be called from expressions to access nested members. Example: p get_coro_promise(fib.coro_hdl)->current_state
  • async-bt was replaced by a frame filter. This way, the builtin bt command directly shows all the async coroutine frames.

Under the covers, the script became more robust:

  • For devirtualization, we now look up the __coro_frame variable in the resume function instead of relying on the .coro_frame_ty naming convention. Thereby, devirtualization works slightly better also on gcc-compiled binaries (however, there is still more work to be done).
  • We use the LLVM-generated __coro_resume_<N> labels to get the exact line at which a coroutine was suspended.
  • The continuation handle is now looked up by name instead of via dereferencing a calculated pointer. Thereby, the script should be simpler to adjust for various coroutine libraries without requiring pointer arithmetic hacks.

Other sections of the documentation were adjusted accordingly to reflect the newly added features of the gdb script.

In "Debugging C++ Coroutines", we provide a gdb script to aid with
debugging C++ coroutines in gdb.

This commit contains the following user-facing changes:
* `show-coro-frame` was replaced by a pretty-printer for
  `std::coroutine_handle`. This is much easier to use than a custom
  command since it works out-of-the-box with `p` and in my IDE's
  variable view (tested using VS-Code)
* the new `get_coro_{frame,promise}` functions can be called from
  expressions to access nested members.
  Example: `p get_coro_promise(fib.coro_hdl)->current_state`
* `async-bt` was replaced by a frame filter. This way, the builtin
  `bt` command directly shows all the async coroutine frames.

Under the covers, the script became more robust:
* For devirtualization, we now look up the `__coro_frame` variable
  in the resume function instead of relying on the `.coro_frame_ty`
  naming convention. Thereby, devirtualization works slightly better
  also on gcc-compiled binaries (however, there is still more work to be
  done).
* We use the LLVM-generated `__coro_resume_<N>` labels to get the exact
  line at which a coroutine was suspended.
* The continuation handle is now looked up by name instead of via
  dereferencing a calculated pointer. Thereby, the script should be
  simpler to adjust for various coroutine libraries without requiring
  pointer arithmetic hacks.

Other sections of the documentation were adjusted accordingly to reflect
the newly added features of the gdb script.
@vogelsgesang vogelsgesang added the coroutines C++20 coroutines label Oct 6, 2025
@vogelsgesang vogelsgesang changed the title [docs][coroutines] Update gdb script [docs][coroutines] Update gdb debugger script Oct 6, 2025
@llvmbot llvmbot added the clang Clang issues not falling into any other category label Oct 6, 2025
@llvmbot
Copy link
Member

llvmbot commented Oct 6, 2025

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-coroutines

Author: Adrian Vogelsgesang (vogelsgesang)

Changes

In "Debugging C++ Coroutines", we provide a gdb script to aid with debugging C++ coroutines in gdb.

This commit contains the following user-facing changes:

  • show-coro-frame was replaced by a pretty-printer for std::coroutine_handle. This is much easier to use than a custom command since it works out-of-the-box with p and in my IDE's variable view (tested using VS-Code)
  • the new get_coro_{frame,promise} functions can be called from expressions to access nested members. Example: p get_coro_promise(fib.coro_hdl)-&gt;current_state
  • async-bt was replaced by a frame filter. This way, the builtin bt command directly shows all the async coroutine frames.

Under the covers, the script became more robust:

  • For devirtualization, we now look up the __coro_frame variable in the resume function instead of relying on the .coro_frame_ty naming convention. Thereby, devirtualization works slightly better also on gcc-compiled binaries (however, there is still more work to be done).
  • We use the LLVM-generated __coro_resume_&lt;N&gt; labels to get the exact line at which a coroutine was suspended.
  • The continuation handle is now looked up by name instead of via dereferencing a calculated pointer. Thereby, the script should be simpler to adjust for various coroutine libraries without requiring pointer arithmetic hacks.

Other sections of the documentation were adjusted accordingly to reflect the newly added features of the gdb script.


Patch is 21.11 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/162145.diff

1 Files Affected:

  • (modified) clang/docs/DebuggingCoroutines.rst (+274-158)
diff --git a/clang/docs/DebuggingCoroutines.rst b/clang/docs/DebuggingCoroutines.rst
index 9eaf8d4028adf..4304270458d09 100644
--- a/clang/docs/DebuggingCoroutines.rst
+++ b/clang/docs/DebuggingCoroutines.rst
@@ -179,8 +179,8 @@ generator and its internal state.
 
 To do so, we can simply look into the ``gen.hdl`` variable. LLDB comes with a
 pretty printer for ``std::coroutine_handle`` which will show us the internal
-state of the coroutine. For GDB, you will have to use the ``show-coro-frame``
-command provided by the :ref:`gdb-script`.
+state of the coroutine. For GDB, the pretty printer is provided by a script,
+see :ref:`gdb-script` for setup instructions.
 
 .. image:: ./coro-generator-suspended.png
 
@@ -206,23 +206,16 @@ Tracking the exact suspension point
 
 Among the compiler-generated members, the ``__coro_index`` is particularly
 important. This member identifies the suspension point at which the coroutine
-is currently suspended.
+is currently suspended. However, it is non-trivial to map this number backto
+a source code location.
 
-However, it is non-trivial to map this number back to a source code location.
-The compiler emits debug info labels for the suspension points. This allows us
-to map the suspension point index back to a source code location. In gdb, we
-can use the ``info line`` command to get the source code location of the
-suspension point.
-
-::
-
-  (gdb) info line -function coro_task -label __coro_resume_2
-  Line 45 of "llvm-example.cpp" starts at address 0x1b1b <_ZL9coro_taski.resume+555> and ends at 0x1b46 <_ZL9coro_taski.resume+598>.
-  Line 45 of "llvm-example.cpp" starts at address 0x201b <_ZL9coro_taski.destroy+555> and ends at 0x2046 <_ZL9coro_taski.destroy+598>.
-  Line 45 of "llvm-example.cpp" starts at address 0x253b <_ZL9coro_taski.cleanup+555> and ends at 0x2566 <_ZL9coro_taski.cleanup+598>.
+For GDB, the provided :ref:`gdb-script` already takes care of this and provides
+the exact line number of the suspension point as part of the couroutine handle's
+summary string. Unfortunately, LLDB's pretty-printer does not support this, yet.
+Furthermore, those labels are only emitted starting with clang 21.0.
 
-LLDB does not support looking up labels. Furthermore, those labels are only emitted
-starting with clang 21.0.
+When debugging with LLDB or when using older clang versions, we will have to use
+a different approach.
 
 For simple cases, you might still be able to guess the suspension point correctly.
 Alternatively, you might also want to modify your coroutine library to store
@@ -655,33 +648,17 @@ There are two possible approaches to do so:
    We can lookup their types and thereby get the types of promise
    and coroutine frame.
 
-In gdb, one can use the following approach to devirtualize a coroutine type,
-assuming we have a ``std::coroutine_handle`` is at address 0x418eb0:
-
-::
+In general, the second approach is preferred, as it is more portable.
 
-  (gdb) # Get the address of coroutine frame
-  (gdb) print/x *0x418eb0
-  $1 = 0x4019e0
-  (gdb) # Get the linkage name for the coroutine
-  (gdb) x 0x4019e0
-  0x4019e0 <_ZL9coro_taski>:  0xe5894855
-  (gdb) # Turn off the demangler temporarily to avoid the debugger misunderstanding the name.
-  (gdb) set demangle-style none
-  (gdb) # The coroutine frame type is 'linkage_name.coro_frame_ty'
-  (gdb) print  ('_ZL9coro_taski.coro_frame_ty')*(0x418eb0)
-  $2 = {__resume_fn = 0x4019e0 <coro_task(int)>, __destroy_fn = 0x402000 <coro_task(int)>, __promise = {...}, ...}
-
-In practice, one would use the ``show-coro-frame`` command provided by the
-:ref:`gdb-script`.
+To do so, we look up the types in the destroy function and not the resume function
+because the resume function pointer will be set to a ``nullptr`` as soon as a
+coroutine reaches its final suspension point. If we used the resume function,
+devirtualization would hence fail for all coroutines that have reached their final
+suspension point.
 
 LLDB comes with devirtualization support out of the box, as part of the
-pretty-printer for ``std::coroutine_handle``. Internally, this pretty-printer
-uses the second approach. We look up the types in the destroy function and not
-the resume function because the resume function pointer will be set to a
-``nullptr`` as soon as a coroutine reaches its final suspension point. If we used
-the resume function, devirtualization would hence fail for all coroutines that
-have reached their final suspension point.
+pretty-printer for ``std::coroutine_handle``. For GDB, a similar pretty-printer
+is provided by the :ref:`gdb-script`.
 
 Interpreting the coroutine frame in optimized builds
 ----------------------------------------------------
@@ -756,6 +733,26 @@ should not be thought of as directly representing the variables in the C++
 source.
 
 
+Mapping suspension point indices to source code locations
+---------------------------------------------------------
+
+To aid in mapping a ``__coro_index`` back to a source code location, clang 21.0
+and newer emit special, compiler-generated labels for the suspension points.
+
+In gdb, we can use the ``info line`` command to get the source code location of
+the suspension point.
+
+::
+
+  (gdb) info line -function coro_task -label __coro_resume_2
+  Line 45 of "llvm-example.cpp" starts at address 0x1b1b <_ZL9coro_taski.resume+555> and ends at 0x1b46 <_ZL9coro_taski.resume+598>.
+  Line 45 of "llvm-example.cpp" starts at address 0x201b <_ZL9coro_taski.destroy+555> and ends at 0x2046 <_ZL9coro_taski.destroy+598>.
+  Line 45 of "llvm-example.cpp" starts at address 0x253b <_ZL9coro_taski.cleanup+555> and ends at 0x2566 <_ZL9coro_taski.cleanup+598>.
+
+LLDB does not support looking up labels, yet. That's also why LLDB's pretty-printer
+does not show the exact line number of the suspension point.
+
+
 Resources
 =========
 
@@ -1019,154 +1016,273 @@ GDB Debugger Script
 
 For GDB, the following script provides a couple of useful commands:
 
-* ``async-bt`` to print the stack trace of a coroutine
-* ``show-coro-frame`` to print the coroutine frame, similar to
-  LLDB's builtin pretty-printer for coroutine frames
+The following script provides
+
+* a pretty-printer for coroutine handles
+* a frame filter to add coroutine frames to the built-in ``bt`` command
+* the commands ``get_coro_frame`` and ``get_coro_promise`` to be used in
+  expressions, e.g. ``p get_coro_promise(fib.coro_hdl)->current_state``
+
+It can be loaded into GDB using ``source gdb_coro_debugging.py``.
+To load this by default, add this command to your ``~/.gdbinit`` file.
 
 .. code-block:: python
 
-  # debugging-helper.py
+  # gdb_coro_debugging.py
   import gdb
   from gdb.FrameDecorator import FrameDecorator
 
-  class SymValueWrapper():
-      def __init__(self, symbol, value):
-          self.sym = symbol
-          self.val = value
+  import typing
+  import re
+
+  def _load_pointer_at(addr: int):
+      return gdb.Value(addr).reinterpret_cast(gdb.lookup_type('void').pointer().pointer()).dereference()
+
+  """
+  Devirtualized coroutine frame.
+
+  Devirtualizes the promise and frame pointer types by inspecting
+  the destroy function.
+
+  Implements `to_string` and `children` to be used by `gdb.printing.PrettyPrinter`.
+  Base class for `CoroutineHandlePrinter`.
+  """
+  class DevirtualizedCoroFrame:
+      def __init__(self, frame_ptr_raw: int, val: gdb.Value | None = None):
+          self.val = val
+          self.frame_ptr_raw = frame_ptr_raw
+
+          # Get the resume and destroy pointers.
+          if frame_ptr_raw == 0:
+              self.resume_ptr = None
+              self.destroy_ptr = None
+              self.promise_ptr = None
+              self.frame_ptr = gdb.Value(frame_ptr_raw).reinterpret_cast(gdb.lookup_type("void").pointer())
+              return
+
+          # Get the resume and destroy pointers.
+          self.resume_ptr = _load_pointer_at(frame_ptr_raw)
+          self.destroy_ptr = _load_pointer_at(frame_ptr_raw + 8)
+
+          # Devirtualize the promise and frame pointer types.
+          frame_type = gdb.lookup_type("void")
+          promise_type = gdb.lookup_type("void")
+          self.destroy_func = gdb.block_for_pc(int(self.destroy_ptr))
+          if self.destroy_func is not None:
+              frame_var = gdb.lookup_symbol("__coro_frame", self.destroy_func, gdb.SYMBOL_VAR_DOMAIN)[0]
+              if frame_var is not None:
+                  frame_type = frame_var.type
+              promise_var = gdb.lookup_symbol("__promise", self.destroy_func, gdb.SYMBOL_VAR_DOMAIN)[0]
+              if promise_var is not None:
+                  promise_type = promise_var.type.strip_typedefs()
+
+          # If the type has a template argument, prefer it over the devirtualized type.
+          if self.val is not None:
+              promise_type_template_arg = self.val.type.template_argument(0)
+              if promise_type_template_arg is not None and promise_type_template_arg.code != gdb.TYPE_CODE_VOID:
+                  promise_type = promise_type_template_arg
+
+          self.promise_ptr = gdb.Value(frame_ptr_raw + 16).reinterpret_cast(promise_type.pointer())
+          self.frame_ptr = gdb.Value(frame_ptr_raw).reinterpret_cast(frame_type.pointer())
+
+          # Try to get the suspension point index and look up the exact line entry.
+          self.suspension_point_index = int(self.frame_ptr.dereference()["__coro_index"]) if frame_type.code == gdb.TYPE_CODE_STRUCT else None
+          self.resume_func = gdb.block_for_pc(int(self.resume_ptr))
+          self.resume_label = None
+          if self.resume_func is not None and self.suspension_point_index is not None:
+              label_name = f"__coro_resume_{self.suspension_point_index}"
+              self.resume_label = gdb.lookup_symbol(label_name, self.resume_func, gdb.SYMBOL_LABEL_DOMAIN)[0]
+
+      def get_function_name(self):
+          if self.destroy_func is None:
+              return None
+          name = self.destroy_func.function.name
+          # Strip the "clone" suffix if it exists.
+          if " [clone " in name:
+              name = name[:name.index(" [clone ")]
+          return name
+
+      def to_string(self):
+          result = "coro(" + str(self.frame_ptr_raw) + ")"
+          if self.destroy_func is not None:
+              result += ": " + self.get_function_name()
+          if self.resume_label is not None:
+              result += ", line " + str(self.resume_label.line)
+          if self.suspension_point_index is not None:
+              result += ", suspension point " + str(self.suspension_point_index)
+          return result
+
+      def children(self):
+          if self.resume_ptr is None:
+              return [
+                  ("coro_frame", self.frame_ptr),
+              ]
+          else:
+              return [
+                  ("resume", self.resume_ptr),
+                  ("destroy", self.destroy_ptr),
+                  ("promise", self.promise_ptr),
+                  ("coro_frame", self.frame_ptr)
+              ]
+
+
+  # Works for both libc++ and libstdc++.
+  libcxx_corohdl_regex = re.compile('^std::__[A-Za-z0-9]+::coroutine_handle<.+>$|^std::coroutine_handle<.+>(( )?&)?$')
+
+  def _extract_coro_frame_ptr_from_handle(val: gdb.Value):
+      if libcxx_corohdl_regex.match(val.type.strip_typedefs().name) is None:
+          raise ValueError("Expected a std::coroutine_handle, got %s" % val.type.strip_typedefs().name)
+
+      # We expect the coroutine handle to have a single field, which is the frame pointer.
+      # This heuristic works for both libc++ and libstdc++.
+      fields = val.type.fields()
+      if len(fields) != 1:
+          raise ValueError("Expected 1 field, got %d" % len(fields))
+      return int(val[fields[0]])
 
-      def __str__(self):
-          return str(self.sym) + " = " + str(self.val)
 
-  def get_long_pointer_size():
-      return gdb.lookup_type('long').pointer().sizeof
+  """
+  Pretty printer for `std::coroutine_handle<T>`
 
-  def cast_addr2long_pointer(addr):
-      return gdb.Value(addr).cast(gdb.lookup_type('long').pointer())
+  Works for both libc++ and libstdc++.
 
-  def dereference(addr):
-      return long(cast_addr2long_pointer(addr).dereference())
+  It prints the coroutine handle as a struct with the following fields:
+  - resume: the resume function pointer
+  - destroy: the destroy function pointer
+  - promise: the promise pointer
+  - coro_frame: the coroutine frame pointer
 
-  class CoroutineFrame(object):
-      def __init__(self, task_addr):
-          self.frame_addr = task_addr
-          self.resume_addr = task_addr
-          self.destroy_addr = task_addr + get_long_pointer_size()
-          self.promise_addr = task_addr + get_long_pointer_size() * 2
-          # In the example, the continuation is the first field member of the promise_type.
-          # So they have the same addresses.
-          # If we want to generalize the scripts to other coroutine types, we need to be sure
-          # the continuation field is the first member of promise_type.
-          self.continuation_addr = self.promise_addr
+  Most of the functionality is implemented in `DevirtualizedCoroFrame`.
+  """
+  class CoroutineHandlePrinter(DevirtualizedCoroFrame):
+      def __init__(self, val : gdb.Value):
+          frame_ptr_raw = _extract_coro_frame_ptr_from_handle(val)
+          super(CoroutineHandlePrinter, self).__init__(frame_ptr_raw, val)
 
-      def next_task_addr(self):
-          return dereference(self.continuation_addr)
 
+  def build_pretty_printer():
+      pp = gdb.printing.RegexpCollectionPrettyPrinter("coroutine")
+      pp.add_printer('std::coroutine_handle', libcxx_corohdl_regex, CoroutineHandlePrinter)
+      return pp
+
+  gdb.printing.register_pretty_printer(
+      gdb.current_objfile(),
+      build_pretty_printer())
+
+
+  """
+  Get the coroutine frame pointer from a coroutine handle.
+
+  Usage:
+  ```
+  p *get_coro_frame(coroutine_hdl)
+  ```
+  """
+  class GetCoroFrame(gdb.Function):
+      def __init__(self):
+          super(GetCoroFrame, self).__init__("get_coro_frame")
+
+      def invoke(self, coroutine_hdl_raw):
+          return CoroutineHandlePrinter(coroutine_hdl_raw).frame_ptr
+
+  GetCoroFrame()
+
+
+  """
+  Get the coroutine frame pointer from a coroutine handle.
+
+  Usage:
+  ```
+  p *get_coro_promise(coroutine_hdl)
+  ```
+  """
+  class GetCoroFrame(gdb.Function):
+      def __init__(self):
+          super(GetCoroFrame, self).__init__("get_coro_promise")
+
+      def invoke(self, coroutine_hdl_raw):
+          return CoroutineHandlePrinter(coroutine_hdl_raw).promise_ptr
+
+  GetCoroFrame()
+
+
+  """
+  Decorator for coroutine frames.
+
+  Used by `CoroutineFrameFilter` to add the coroutine frames to the built-in `bt` command.
+  """
   class CoroutineFrameDecorator(FrameDecorator):
-      def __init__(self, coro_frame):
-          super(CoroutineFrameDecorator, self).__init__(None)
+      def __init__(self, coro_frame: DevirtualizedCoroFrame, inferior_frame: gdb.Frame):
+          super(CoroutineFrameDecorator, self).__init__(inferior_frame)
           self.coro_frame = coro_frame
-          self.resume_func = dereference(self.coro_frame.resume_addr)
-          self.resume_func_block = gdb.block_for_pc(self.resume_func)
-          if self.resume_func_block is None:
-              raise Exception('Not stackless coroutine.')
-          self.line_info = gdb.find_pc_line(self.resume_func)
+
+      def function(self):
+          func_name = self.coro_frame.destroy_func.function.name
+          if func_name is not None:
+              return func_name
+          return "coroutine (coro_frame=" + str(self.coro_frame.frame_ptr_raw) + ")"
 
       def address(self):
-          return self.resume_func
+          return None
 
       def filename(self):
-          return self.line_info.symtab.filename
-
-      def frame_args(self):
-          return [SymValueWrapper("frame_addr", cast_addr2long_pointer(self.coro_frame.frame_addr)),
-                  SymValueWrapper("promise_addr", cast_addr2long_pointer(self.coro_frame.promise_addr)),
-                  SymValueWrapper("continuation_addr", cast_addr2long_pointer(self.coro_frame.continuation_addr))
-                  ]
-
-      def function(self):
-          return self.resume_func_block.function.print_name
+          if self.coro_frame.destroy_func is not None:
+              return self.coro_frame.destroy_func.function.symtab.filename
+          return None
 
       def line(self):
-          return self.line_info.line
-
-  class StripDecorator(FrameDecorator):
-      def __init__(self, frame):
-          super(StripDecorator, self).__init__(frame)
-          self.frame = frame
-          f = frame.function()
-          self.function_name = f
-
-      def __str__(self, shift = 2):
-          addr = "" if self.address() is None else '%#x' % self.address() + " in "
-          location = "" if self.filename() is None else " at " + self.filename() + ":" + str(self.line())
-          return addr + self.function() + " " + str([str(args) for args in self.frame_args()]) + location
-
-  class CoroutineFilter:
-      def create_coroutine_frames(self, task_addr):
-          frames = []
-          while task_addr != 0:
-              coro_frame = CoroutineFrame(task_addr)
-              frames.append(CoroutineFrameDecorator(coro_frame))
-              task_addr = coro_frame.next_task_addr()
-          return frames
-
-  class AsyncStack(gdb.Command):
-      def __init__(self):
-          super(AsyncStack, self).__init__("async-bt", gdb.COMMAND_USER)
+          if self.coro_frame.resume_label is not None:
+              return self.coro_frame.resume_label.line
+          return None
 
-      def invoke(self, arg, from_tty):
-          coroutine_filter = CoroutineFilter()
-          argv = gdb.string_to_argv(arg)
-          if len(argv) == 0:
-              try:
-                  task = gdb.parse_and_eval('__coro_frame')
-                  task = int(str(task.address), 16)
-              except Exception:
-                  print ("Can't find __coro_frame in current context.\n" +
-                        "Please use `async-bt` in stackless coroutine context.")
-                  return
-          elif len(argv) != 1:
-              print("usage: async-bt <pointer to task>")
-              return
-          else:
-              task = int(argv[0], 16)
+      def frame_args(self):
+          return []
 
-          frames = coroutine_filter.create_coroutine_frames(task)
-          i = 0
-          for f in frames:
-              print '#'+ str(i), str(StripDecorator(f))
-              i += 1
-          return
+      def frame_locals(self):
+          return []
 
-  AsyncStack()
 
-  class ShowCoroFrame(gdb.Command):
-      def __init__(self):
-          super(ShowCoroFrame, self).__init__("show-coro-frame", gdb.COMMAND_USER)
+  def _get_continuation(promise: gdb.Value) -> DevirtualizedCoroFrame | None:
+      try:
+          # TODO: adjust this according for your coroutine framework
+          return DevirtualizedCoroFrame(_extract_coro_frame_ptr_from_handle(promise["continuation"]))
+      except Exception as e:
+          return None
 
-      def invoke(self, arg, from_tty):
-          argv = gdb.string_to_argv(arg)
-          if len(argv) != 1:
-              print("usage: show-coro-frame <address of coroutine frame>")
-              return
 
-          addr = int(argv[0], 16)
-          block = gdb.block_for_pc(long(cast_addr2long_pointer(addr).dereference()))
-          if block is None:
-              print "block " + str(addr) + " is None."
-              return
+  def _create_coroutine_frames(coro_frame: DevirtualizedCoroFrame, inferior_frame: gdb.Frame):
+      while coro_frame is not None:
+          yield CoroutineFrameDecorator(coro_frame, inferior_frame)
+          coro_frame = _get_continuation(coro_frame.promise_ptr)
 
-          # Disable demangling since gdb will treat names starting with `_Z`(The marker for Itanium ABI) specially.
-          gdb.execute("set demangle-style none")
 
-          coro_frame_type = gdb.lookup_type(block.function.linkage_name + ".coro_frame_ty")
-          coro_frame_ptr_type = coro_frame_type.pointer()
-          coro_frame = gdb.Value(addr).cast(coro_frame_ptr_type).dereference()
+  """
+  Frame filter to add coroutine frames to the built-in `bt` command.
+  """
+  class CppCo...
[truncated]

Copy link
Contributor

@kazutakahirata kazutakahirata left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not familiar with the gdb script, so let me just proofread the documentation changes.

@vogelsgesang
Copy link
Member Author

so let me just proofread the documentation changes.

Thank you! Fixed

I'm not familiar with the gdb script

Let's see if @ChuanqiXu9 (afaik, the author of the previous gdb script) has any feedback on the script itself 🙂

Copy link
Member

@ChuanqiXu9 ChuanqiXu9 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH, I don't have time to understand the script line by line right now (I am not so familar with the gdb script, I just wrote it by reading the document) but I think it is worthy to trust @vogelsgesang

@vogelsgesang vogelsgesang merged commit 6e784af into llvm:main Oct 10, 2025
10 checks passed
DharuniRAcharya pushed a commit to DharuniRAcharya/llvm-project that referenced this pull request Oct 13, 2025
In "Debugging C++ Coroutines", we provide a gdb script to aid with
debugging C++ coroutines in gdb. This commit updates said script to make
it easier to use and more robust.

The commit contains the following user-facing changes:
* `show-coro-frame` was replaced by a pretty-printer for
  `std::coroutine_handle`. This is much easier to use than a custom
  command since it works out-of-the-box with `p` and in my IDE's variable
  view (tested using VS-Code)
* the new `get_coro_{frame,promise}` functions can be called from
  expressions to access nested members. Example: `p
  get_coro_promise(fib.coro_hdl)->current_state`
* `async-bt` was replaced by a frame filter. This way, the builtin `bt`
  command directly shows all the async coroutine frames.

Under the covers, the script became more robust:
* For devirtualization, we now look up the `__coro_frame` variable in
  the resume function instead of relying on the `.coro_frame_ty` naming
  convention. Thereby, devirtualization works slightly better also on
  gcc-compiled binaries (however, there is still more work to be done).
* We use the LLVM-generated `__coro_resume_<N>` labels to get the exact
  line at which a coroutine was suspended.
* The continuation handle is now looked up by name instead of via
  dereferencing a calculated pointer. Thereby, the script should be
  simpler to adjust for various coroutine libraries without requiring
  pointer arithmetic hacks.

Other sections of the documentation were adjusted accordingly to reflect
the newly added features of the gdb script.
akadutta pushed a commit to akadutta/llvm-project that referenced this pull request Oct 14, 2025
In "Debugging C++ Coroutines", we provide a gdb script to aid with
debugging C++ coroutines in gdb. This commit updates said script to make
it easier to use and more robust.

The commit contains the following user-facing changes:
* `show-coro-frame` was replaced by a pretty-printer for
  `std::coroutine_handle`. This is much easier to use than a custom
  command since it works out-of-the-box with `p` and in my IDE's variable
  view (tested using VS-Code)
* the new `get_coro_{frame,promise}` functions can be called from
  expressions to access nested members. Example: `p
  get_coro_promise(fib.coro_hdl)->current_state`
* `async-bt` was replaced by a frame filter. This way, the builtin `bt`
  command directly shows all the async coroutine frames.

Under the covers, the script became more robust:
* For devirtualization, we now look up the `__coro_frame` variable in
  the resume function instead of relying on the `.coro_frame_ty` naming
  convention. Thereby, devirtualization works slightly better also on
  gcc-compiled binaries (however, there is still more work to be done).
* We use the LLVM-generated `__coro_resume_<N>` labels to get the exact
  line at which a coroutine was suspended.
* The continuation handle is now looked up by name instead of via
  dereferencing a calculated pointer. Thereby, the script should be
  simpler to adjust for various coroutine libraries without requiring
  pointer arithmetic hacks.

Other sections of the documentation were adjusted accordingly to reflect
the newly added features of the gdb script.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang Clang issues not falling into any other category coroutines C++20 coroutines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants