[clang] 4332b04 - [docs] Add examples for printing asynchronous stack for coroutines

Chuanqi Xu via cfe-commits cfe-commits at lists.llvm.org
Tue Aug 23 02:37:51 PDT 2022


Author: Chuanqi Xu
Date: 2022-08-23T17:37:12+08:00
New Revision: 4332b049edf6ccf98c9e31dcc983760a89f01d40

URL: https://github.com/llvm/llvm-project/commit/4332b049edf6ccf98c9e31dcc983760a89f01d40
DIFF: https://github.com/llvm/llvm-project/commit/4332b049edf6ccf98c9e31dcc983760a89f01d40.diff

LOG: [docs] Add examples for printing asynchronous stack for coroutines

Previously when I wrote this document, I felt the completed scripts was
lengthy, redundant and not easy to read. So I didn't add complete
examples in the previous commit.

However, in the recent discussion with @avogelsgesang, I found people
may not know how to use debugging scripts to improve their debugging
efficiency. So now, I feel like it is helpful to put the examples even
if they are a little bit long.

Test Plan: make docs-clang-html

Reviewed By: avogelsgesang

Differential Revision: https://reviews.llvm.org/D132451

Added: 
    

Modified: 
    clang/docs/DebuggingCoroutines.rst

Removed: 
    


################################################################################
diff  --git a/clang/docs/DebuggingCoroutines.rst b/clang/docs/DebuggingCoroutines.rst
index 828822f52bd61..ae5359117775b 100644
--- a/clang/docs/DebuggingCoroutines.rst
+++ b/clang/docs/DebuggingCoroutines.rst
@@ -368,6 +368,345 @@ contains the corresponding continuation (which itself is a coroutine with a
 
 This logic should be quite easily captured in a debugger script.
 
+Examples to print asynchronous stack
+------------------------------------
+
+Here is an example to print the asynchronous stack for the normal task implementation.
+
+.. code-block:: c++
+
+  // debugging-example.cpp
+  #include <coroutine>
+  #include <iostream>
+  #include <utility>
+
+  struct task {
+    struct promise_type {
+      task get_return_object();
+      std::suspend_always initial_suspend() { return {}; }
+      
+      void unhandled_exception() noexcept {}
+
+      struct FinalSuspend {
+        std::coroutine_handle<> continuation;
+        auto await_ready() noexcept { return false; }
+        auto await_suspend(std::coroutine_handle<> handle) noexcept {
+          return continuation;
+        }
+        void await_resume() noexcept {}
+      };
+      FinalSuspend final_suspend() noexcept { return {continuation}; }
+
+      void return_value(int res) { result = res; }
+
+      std::coroutine_handle<> continuation = std::noop_coroutine();
+      int result = 0;
+    };
+
+    task(std::coroutine_handle<promise_type> handle) : handle(handle) {}
+    ~task() {
+      if (handle)
+        handle.destroy();
+    }
+
+    auto operator co_await() {
+      struct Awaiter {
+        std::coroutine_handle<promise_type> handle;
+        auto await_ready() { return false; }
+        auto await_suspend(std::coroutine_handle<> continuation) {
+          handle.promise().continuation = continuation;
+          return handle;
+        }
+        int await_resume() {
+          int ret = handle.promise().result;
+          handle.destroy();
+          return ret;
+        }
+      };
+      return Awaiter{std::exchange(handle, nullptr)};
+    }
+
+    int syncStart() {
+      handle.resume();
+      return handle.promise().result;
+    }
+
+  private:
+    std::coroutine_handle<promise_type> handle;
+  };
+
+  task task::promise_type::get_return_object() {
+    return std::coroutine_handle<promise_type>::from_promise(*this);
+  }
+
+  namespace detail {
+  template <int N>
+  task chain_fn() {
+    co_return N + co_await chain_fn<N - 1>();
+  }
+
+  template <>
+  task chain_fn<0>() {
+    // This is the default breakpoint.
+    __builtin_debugtrap();
+    co_return 0;
+  }
+  }  // namespace detail
+
+  task chain() {
+    co_return co_await detail::chain_fn<30>();
+  }
+
+  int main() {
+    std::cout << chain().syncStart() << "\n";
+    return 0;
+  }
+
+In the example, the ``task`` coroutine holds a ``continuation`` field,
+which would be resumed once the ``task`` completes.
+In another word, the ``continuation`` is the asynchronous caller for the ``task``.
+Just like the normal function returns to its caller when the function completes.
+
+So we can use the ``continuation`` field to construct the asynchronous stack:
+
+.. code-block:: python
+
+  # debugging-helper.py
+  import gdb
+  from gdb.FrameDecorator import FrameDecorator
+
+  class SymValueWrapper():
+      def __init__(self, symbol, value):
+          self.sym = symbol
+          self.val = value
+
+      def __str__(self):
+          return str(self.sym) + " = " + str(self.val)
+
+  def get_long_pointer_size():
+      return gdb.lookup_type('long').pointer().sizeof
+
+  def cast_addr2long_pointer(addr):
+      return gdb.Value(addr).cast(gdb.lookup_type('long').pointer())
+
+  def dereference(addr):
+      return long(cast_addr2long_pointer(addr).dereference())
+
+  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 memeber of promise_type.
+          self.continuation_addr = self.promise_addr
+
+      def next_task_addr(self):
+          return dereference(self.continuation_addr)
+
+  class CoroutineFrameDecorator(FrameDecorator):
+      def __init__(self, coro_frame):
+          super(CoroutineFrameDecorator, self).__init__(None)
+          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 == None:
+              raise Exception('Not stackless coroutine.')
+          self.line_info = gdb.find_pc_line(self.resume_func)
+
+      def address(self):
+          return self.resume_func
+
+      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
+
+      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() == None else '%#x' % self.address() + " in "
+          location = "" if self.filename() == 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)
+
+      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)
+
+          frames = coroutine_filter.create_coroutine_frames(task)
+          i = 0
+          for f in frames:
+              print '#'+ str(i), str(StripDecorator(f))
+              i += 1
+          return
+
+  AsyncStack()
+
+  class ShowCoroFrame(gdb.Command):
+      def __init__(self):
+          super(ShowCoroFrame, self).__init__("show-coro-frame", gdb.COMMAND_USER)
+
+      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 == None:
+              print "block " + str(addr) + "  is none."
+              return
+
+          # 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()
+
+          gdb.execute("set demangle-style auto")
+          gdb.write(coro_frame.format_string(pretty_structs = True))
+
+  ShowCoroFrame()
+
+Then let's run:
+
+.. code-block:: text
+
+  $ clang++ -std=c++20 -g debugging-example.cpp -o debugging-example
+  $ gdb ./debugging-example
+  (gdb) # We've alreay set the breakpoint.
+  (gdb) r
+  Program received signal SIGTRAP, Trace/breakpoint trap.
+  detail::chain_fn<0> () at debugging-example2.cpp:73
+  73	  co_return 0;
+  (gdb) # Executes the debugging scripts
+  (gdb) source debugging-helper.py
+  (gdb) # Print the asynchronous stack
+  (gdb) async-bt
+  #0 0x401c40 in detail::chain_fn<0>() ['frame_addr = 0x441860', 'promise_addr = 0x441870', 'continuation_addr = 0x441870'] at debugging-example.cpp:71
+  #1 0x4022d0 in detail::chain_fn<1>() ['frame_addr = 0x441810', 'promise_addr = 0x441820', 'continuation_addr = 0x441820'] at debugging-example.cpp:66
+  #2 0x403060 in detail::chain_fn<2>() ['frame_addr = 0x4417c0', 'promise_addr = 0x4417d0', 'continuation_addr = 0x4417d0'] at debugging-example.cpp:66
+  #3 0x403df0 in detail::chain_fn<3>() ['frame_addr = 0x441770', 'promise_addr = 0x441780', 'continuation_addr = 0x441780'] at debugging-example.cpp:66
+  #4 0x404b80 in detail::chain_fn<4>() ['frame_addr = 0x441720', 'promise_addr = 0x441730', 'continuation_addr = 0x441730'] at debugging-example.cpp:66
+  #5 0x405910 in detail::chain_fn<5>() ['frame_addr = 0x4416d0', 'promise_addr = 0x4416e0', 'continuation_addr = 0x4416e0'] at debugging-example.cpp:66
+  #6 0x4066a0 in detail::chain_fn<6>() ['frame_addr = 0x441680', 'promise_addr = 0x441690', 'continuation_addr = 0x441690'] at debugging-example.cpp:66
+  #7 0x407430 in detail::chain_fn<7>() ['frame_addr = 0x441630', 'promise_addr = 0x441640', 'continuation_addr = 0x441640'] at debugging-example.cpp:66
+  #8 0x4081c0 in detail::chain_fn<8>() ['frame_addr = 0x4415e0', 'promise_addr = 0x4415f0', 'continuation_addr = 0x4415f0'] at debugging-example.cpp:66
+  #9 0x408f50 in detail::chain_fn<9>() ['frame_addr = 0x441590', 'promise_addr = 0x4415a0', 'continuation_addr = 0x4415a0'] at debugging-example.cpp:66
+  #10 0x409ce0 in detail::chain_fn<10>() ['frame_addr = 0x441540', 'promise_addr = 0x441550', 'continuation_addr = 0x441550'] at debugging-example.cpp:66
+  #11 0x40aa70 in detail::chain_fn<11>() ['frame_addr = 0x4414f0', 'promise_addr = 0x441500', 'continuation_addr = 0x441500'] at debugging-example.cpp:66
+  #12 0x40b800 in detail::chain_fn<12>() ['frame_addr = 0x4414a0', 'promise_addr = 0x4414b0', 'continuation_addr = 0x4414b0'] at debugging-example.cpp:66
+  #13 0x40c590 in detail::chain_fn<13>() ['frame_addr = 0x441450', 'promise_addr = 0x441460', 'continuation_addr = 0x441460'] at debugging-example.cpp:66
+  #14 0x40d320 in detail::chain_fn<14>() ['frame_addr = 0x441400', 'promise_addr = 0x441410', 'continuation_addr = 0x441410'] at debugging-example.cpp:66
+  #15 0x40e0b0 in detail::chain_fn<15>() ['frame_addr = 0x4413b0', 'promise_addr = 0x4413c0', 'continuation_addr = 0x4413c0'] at debugging-example.cpp:66
+  #16 0x40ee40 in detail::chain_fn<16>() ['frame_addr = 0x441360', 'promise_addr = 0x441370', 'continuation_addr = 0x441370'] at debugging-example.cpp:66
+  #17 0x40fbd0 in detail::chain_fn<17>() ['frame_addr = 0x441310', 'promise_addr = 0x441320', 'continuation_addr = 0x441320'] at debugging-example.cpp:66
+  #18 0x410960 in detail::chain_fn<18>() ['frame_addr = 0x4412c0', 'promise_addr = 0x4412d0', 'continuation_addr = 0x4412d0'] at debugging-example.cpp:66
+  #19 0x4116f0 in detail::chain_fn<19>() ['frame_addr = 0x441270', 'promise_addr = 0x441280', 'continuation_addr = 0x441280'] at debugging-example.cpp:66
+  #20 0x412480 in detail::chain_fn<20>() ['frame_addr = 0x441220', 'promise_addr = 0x441230', 'continuation_addr = 0x441230'] at debugging-example.cpp:66
+  #21 0x413210 in detail::chain_fn<21>() ['frame_addr = 0x4411d0', 'promise_addr = 0x4411e0', 'continuation_addr = 0x4411e0'] at debugging-example.cpp:66
+  #22 0x413fa0 in detail::chain_fn<22>() ['frame_addr = 0x441180', 'promise_addr = 0x441190', 'continuation_addr = 0x441190'] at debugging-example.cpp:66
+  #23 0x414d30 in detail::chain_fn<23>() ['frame_addr = 0x441130', 'promise_addr = 0x441140', 'continuation_addr = 0x441140'] at debugging-example.cpp:66
+  #24 0x415ac0 in detail::chain_fn<24>() ['frame_addr = 0x4410e0', 'promise_addr = 0x4410f0', 'continuation_addr = 0x4410f0'] at debugging-example.cpp:66
+  #25 0x416850 in detail::chain_fn<25>() ['frame_addr = 0x441090', 'promise_addr = 0x4410a0', 'continuation_addr = 0x4410a0'] at debugging-example.cpp:66
+  #26 0x4175e0 in detail::chain_fn<26>() ['frame_addr = 0x441040', 'promise_addr = 0x441050', 'continuation_addr = 0x441050'] at debugging-example.cpp:66
+  #27 0x418370 in detail::chain_fn<27>() ['frame_addr = 0x440ff0', 'promise_addr = 0x441000', 'continuation_addr = 0x441000'] at debugging-example.cpp:66
+  #28 0x419100 in detail::chain_fn<28>() ['frame_addr = 0x440fa0', 'promise_addr = 0x440fb0', 'continuation_addr = 0x440fb0'] at debugging-example.cpp:66
+  #29 0x419e90 in detail::chain_fn<29>() ['frame_addr = 0x440f50', 'promise_addr = 0x440f60', 'continuation_addr = 0x440f60'] at debugging-example.cpp:66
+  #30 0x41ac20 in detail::chain_fn<30>() ['frame_addr = 0x440f00', 'promise_addr = 0x440f10', 'continuation_addr = 0x440f10'] at debugging-example.cpp:66
+  #31 0x41b9b0 in chain() ['frame_addr = 0x440eb0', 'promise_addr = 0x440ec0', 'continuation_addr = 0x440ec0'] at debugging-example.cpp:77
+
+Now we get the complete asynchronous stack!
+It is also possible to print other asynchronous stack which doesn't live in the top of the stack.
+We can make it by passing the address of the corresponding coroutine frame to ``async-bt`` command.
+
+By the debugging scripts, we can print any coroutine frame too as long as we know the address.
+For example, we can print the coroutine frame for ``detail::chain_fn<18>()`` in the above example.
+From the log record, we know the address of the coroutine frame is ``0x4412c0`` in the run. Then we can:
+
+.. code-block:: text
+
+  (gdb) show-coro-frame 0x4412c0
+  {
+    __resume_fn = 0x410960 <detail::chain_fn<18>()>,
+    __destroy_fn = 0x410d60 <detail::chain_fn<18>()>,
+    __promise = {
+      continuation = {
+        _M_fr_ptr = 0x441270
+      },
+      result = 0
+    },
+    struct_Awaiter_0 = {
+      struct_std____n4861__coroutine_handle_0 = {
+        struct_std____n4861__coroutine_handle = {
+          PointerType = 0x441310
+        }
+      }
+    },
+    struct_task_1 = {
+      struct_std____n4861__coroutine_handle_0 = {
+        struct_std____n4861__coroutine_handle = {
+          PointerType = 0x0
+        }
+      }
+    },
+    struct_task__promise_type__FinalSuspend_2 = {
+      struct_std____n4861__coroutine_handle = {
+        PointerType = 0x0
+      }
+    },
+    __coro_index = 1 '\001',
+    struct_std____n4861__suspend_always_3 = {
+      __int_8 = 0 '\000'
+    }
+
+
 Get the living coroutines
 =========================
 


        


More information about the cfe-commits mailing list