And I just realized that I inadvertently dropped the ball by replying
only to Sandoe, and forgot to keep the list in CC.
Thanks for the quick response, Iain!
Inline below my (original, by now pretty late) replies
> > There are two reasons the script doesn't work for g++:
> > 1. g++ does not emit a `__promise` variable inside the destroy
> > function - that can be worked around by changing the script, though
>
> g++ accesses via the frame pointer - the following entries should be
> available to the debugger.
>
> frame_pointer->_Coro_promise
> frame_pointer->_Coro_resume_index
Yes, that can be easily fixed in the script - I just didn't implement it, yet.
> > 2. g++ provides no way to map the compiler-assigned suspension point
> > IDs back to line numbers - i.e., the topic of this email
>
> g++ retains the location information for user-authored code (so that setting
> breakpoints on line nunber etc. should work.)
yes, and that works great! However, that is besides my point / solving a
different problem.
In my case, I have a suspended `std::coroutine_handle` which I want to
pretty-print. From that coroutine_handle, I can get
1. a pointer to the destroy function's entry point (e.g., 0x7f1345)
2. the resume index (e.g., 15)
I now need a way to map `0x7f1345` and `idx 15` back to its location.
I.e., I need to answer "the suspension point with id 15 within the
coroutine whose destroy function starts at address 0x7f1345 is located
in foobar.cpp at line 65".
Note that I cannot simply lookup 0x7f1345 in the line table, since that
would give me the start of the coroutine function, not the position of
suspension point 15 inside the function's body.
In clang, I solved this by:
1. lookup the scope of the destroy function:
destroy_func = gdb.block_for_pc(int(self.destroy_ptr))
2. lookup the label for suspension point 15 within that function
label_name = f"__coro_resume_{suspension_point_index}"
resume_label = gdb.lookup_symbol(label_name, self.resume_func,
gdb.SYMBOL_LABEL_DOMAIN)[0]
3. look at that label's line/column
print(f"suspended at line {resume_label.line}")
For gcc-compiled code
1. step 1 also works
2. step 2 works with a small adjustment to the label name
3. step 3 does not work, since the labels produced by gcc have
neither location information nor a DW_AT_low_pc which I could look
up in the line table.
> However, for synthetic code (e.g. the ramp and the expansion of the
> co_await expressions) so far, we have intentionally generated the code
> with “unknown” locations. This (absent the kind of process you are
> mentioning) tends to impove the debug experience - because it avoids
> the apparent location jumping around.
That's great! clang unfortunately does emit debug info for this syntetic
code, and hence single stepping into / out of a clang-compiled coroutine
is a bit clunky
On Fri, Oct 10, 2025 at 8:19 AM Iain Sandoe <[email protected]> wrote:
>
>
>
> > On 10 Oct 2025, at 03:16, Adrian Vogelsgesang via Gcc <[email protected]>
> > wrote:
> >
> > Hi gcc-devs!
> >
> > TLDR: For debugging C++ coroutines, I patched clang to emit artificial
> > DW_TAG_labels, mapping suspension point ids to the corresponding
> > source location. Looking for alignment re debugging info between clang
> > and gcc.
> >
> > (Finally following up on Iain Sandoe's request to send this email)
>
> Thanks, there are others here much better able to comment on debug info
> than I am. I’ve added a couple of notes below but hope that others will
> chime in with opinions on how to proceed.
>
> > ~~~~~~~~~
> > Background
> > ~~~~~~~~~
> >
> > When using coroutines for asynchronous programming, the physical stack
> > only tells part of the truth. One also wants to see the chain of
> > "awaiting" coroutines, i.e. the coroutines which initiated the current
> > coroutine frame and are waiting for its completion.
> >
> > I published a gdb debugger script which provides a FrameFilter to
> > inject those async stack frames. With this script, the `bt` command
> > now returns
> >
> >> #0 write_output(...) at ...
> >> [async] greet() at ...
> >> [async] [noop_coroutine] at ...
> >> #1 coroutine_handle<task::promise_type>::resume() const at ...
> >> #2 task::syncStart() at ...
> >
> > However, this script currently doesn't work for gcc-compiled binaries,
> > yet, due to missing debug information.
> >
> > ~~~~~~~~~
> > Current state of gcc-generated debug info
> > ~~~~~~~~~
> >
> > There are two reasons the script doesn't work for g++:
> > 1. g++ does not emit a `__promise` variable inside the destroy
> > function - that can be worked around by changing the script, though
>
> g++ accesses via the frame pointer - the following entries should be
> available to the debugger.
>
> frame_pointer->_Coro_promise
> frame_pointer->_Coro_resume_index
>
> The resume index is updated as we pass the test for the awaiter being
> ready - so that it should be correct whether the coroutine suspends or
> continues.
>
> > 2. g++ provides no way to map the compiler-assigned suspension point
> > IDs back to line numbers - i.e., the topic of this email
>
> g++ retains the location information for user-authored code (so that setting
> breakpoints on line nunber etc. should work.)
>
> However, for synthetic code (e.g. the ramp and the expansion of the
> co_await expressions) so far, we have intentionally generated the code
> with “unknown” locations. This (absent the kind of process you are
> mentioning) tends to impove the debug experience - because it avoids
> the apparent location jumping around.
>
> > In clang, I solved this issue by emitting DW_TAG labels like
> >
> >> 0x00000f71: DW_TAG_label
> >> DW_AT_name ("__coro_resume_17")
> >> DW_AT_decl_file ("generator-example.cpp")
> >> DW_AT_decl_line (5)
> >> DW_AT_decl_column (3)
> >> DW_AT_artificial (true)
> >> DW_AT_LLVM_coro_suspend_idx (0x11)
> >> DW_AT_low_pc (0x00000000000019be)
> >
> > The debugging script can lookup the DW_TAG_label for a given
> > suspension point either by name or via DW_AT_LLVM_coro_suspend_idx and
> > retrieve the line, column and address (for setting breakpoints) from
> > that label.
> >
> > gcc emits similar information:
> >
> >> 0x0000297c: DW_TAG_label
> >> DW_AT_name ("resume.17")
> >>
> >> 0x00002981: DW_TAG_label
> >> DW_AT_name ("destroy.17")
> >
> > Unfortunately, this information is not useful because it lacks file,
> > line, column and address information. It would be great if g++ could
> > also emit file, line, column and address information for those labels.
> >
> > ~~~~~~~~~
> > Can gcc also emit useful DW_TAG debug information for coroutines?
> > ~~~~~~~~~
> >
> > What do you think about the approach of using DW_TAG_label for
> > debugging coroutines? Would you be willing to adopt the same approach
> > also for g++? (I would also be happy to adjust clang, in case we come
> > to a different alignment between both compilers).
> >
> > Adding the file, line, column and address would probably be pretty
> > fundamental for a good debugging experience. Also it would be nice
> > (although completely optional) if we could use the same naming
> > convention (`__coro_resume_x`) and you might want to set the
> > DW_AT_artificial tag. I chose `__coro_resume_x` for clang, because
> > this is a reserved name which is still easily writeable in debugger
> > commands. Using the DW_AT_artificial for those labels also seems to
> > make semantically sense (although it is strictly speaking not blessed
> > by the DWARF standard).
> >
> > ~~~~~~~~~
> > Further Reading
> > ~~~~~~~~~
> >
> > RFC for LLVM/clang:
> > https://discourse.llvm.org/t/rfc-debug-info-for-coroutine-suspension-locations-take-2/86606
> >
> > Corresponding clang commit: https://github.com/llvm/llvm-project/pull/141937
> >
> > Background on debugging of coroutines, both from the user's point of
> > view and toolchain implementation details, such as the approach for
> > devirtualizing the coroutine frame's state:
> > https://clang.llvm.org/docs/DebuggingCoroutines.html
> >
> > Best,
> > Adrian
>