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])
