Jason Merrill via Gcc <[email protected]> writes:

> On Mon, Nov 3, 2025 at 2:26 PM Adrian Vogelsgesang via Gcc <[email protected]>
> wrote:
>
>> 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.
>>
>
> It looks like those labels internally already have the location
> information, but the DWARF writer decides not to represent it because the
> labels are marked DECL_ARTIFICIAL.  We might just remove that flag from
> create_named_label_with_ctx (even though they are indeed artificial)?

This is not identical but similar to the problem that gcov doesn't
handle coroutine bodies.  See PR 110827.

>> > > ~~~~~~~~~
>> > > 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
>> >
>>
>>
>

-- 
Michael Welsh Duggan
([email protected])

Reply via email to