11934b3aeSChuanqi Xu======================== 21934b3aeSChuanqi XuDebugging C++ Coroutines 31934b3aeSChuanqi Xu======================== 41934b3aeSChuanqi Xu 51934b3aeSChuanqi Xu.. contents:: 61934b3aeSChuanqi Xu :local: 71934b3aeSChuanqi Xu 81934b3aeSChuanqi XuIntroduction 91934b3aeSChuanqi Xu============ 101934b3aeSChuanqi Xu 111934b3aeSChuanqi XuFor performance and other architectural reasons, the C++ Coroutines feature in 121934b3aeSChuanqi Xuthe Clang compiler is implemented in two parts of the compiler. Semantic 131934b3aeSChuanqi Xuanalysis is performed in Clang, and Coroutine construction and optimization 141934b3aeSChuanqi Xutakes place in the LLVM middle-end. 151934b3aeSChuanqi Xu 161934b3aeSChuanqi XuHowever, this design forces us to generate insufficient debugging information. 171934b3aeSChuanqi XuTypically, the compiler generates debug information in the Clang frontend, as 181934b3aeSChuanqi Xudebug information is highly language specific. However, this is not possible 191934b3aeSChuanqi Xufor Coroutine frames because the frames are constructed in the LLVM middle-end. 201934b3aeSChuanqi Xu 211934b3aeSChuanqi XuTo mitigate this problem, the LLVM middle end attempts to generate some debug 221934b3aeSChuanqi Xuinformation, which is unfortunately incomplete, since much of the language 231934b3aeSChuanqi Xuspecific information is missing in the middle end. 241934b3aeSChuanqi Xu 251934b3aeSChuanqi XuThis document describes how to use this debug information to better debug 261934b3aeSChuanqi Xucoroutines. 271934b3aeSChuanqi Xu 281934b3aeSChuanqi XuTerminology 291934b3aeSChuanqi Xu=========== 301934b3aeSChuanqi Xu 311934b3aeSChuanqi XuDue to the recent nature of C++20 Coroutines, the terminology used to describe 321934b3aeSChuanqi Xuthe concepts of Coroutines is not settled. This section defines a common, 331934b3aeSChuanqi Xuunderstandable terminology to be used consistently throughout this document. 341934b3aeSChuanqi Xu 351934b3aeSChuanqi Xucoroutine type 361934b3aeSChuanqi Xu-------------- 371934b3aeSChuanqi Xu 381934b3aeSChuanqi XuA `coroutine function` is any function that contains any of the Coroutine 391934b3aeSChuanqi XuKeywords `co_await`, `co_yield`, or `co_return`. A `coroutine type` is a 401934b3aeSChuanqi Xupossible return type of one of these `coroutine functions`. `Task` and 411934b3aeSChuanqi Xu`Generator` are commonly referred to coroutine types. 421934b3aeSChuanqi Xu 431934b3aeSChuanqi Xucoroutine 441934b3aeSChuanqi Xu--------- 451934b3aeSChuanqi Xu 461934b3aeSChuanqi XuBy technical definition, a `coroutine` is a suspendable function. However, 471934b3aeSChuanqi Xuprogrammers typically use `coroutine` to refer to an individual instance. 481934b3aeSChuanqi XuFor example: 491934b3aeSChuanqi Xu 501934b3aeSChuanqi Xu.. code-block:: c++ 511934b3aeSChuanqi Xu 521934b3aeSChuanqi Xu std::vector<Task> Coros; // Task is a coroutine type. 531934b3aeSChuanqi Xu for (int i = 0; i < 3; i++) 541934b3aeSChuanqi Xu Coros.push_back(CoroTask()); // CoroTask is a coroutine function, which 551934b3aeSChuanqi Xu // would return a coroutine type 'Task'. 561934b3aeSChuanqi Xu 571934b3aeSChuanqi XuIn practice, we typically say "`Coros` contains 3 coroutines" in the above 581934b3aeSChuanqi Xuexample, though this is not strictly correct. More technically, this should 591934b3aeSChuanqi Xusay "`Coros` contains 3 coroutine instances" or "Coros contains 3 coroutine 601934b3aeSChuanqi Xuobjects." 611934b3aeSChuanqi Xu 621934b3aeSChuanqi XuIn this document, we follow the common practice of using `coroutine` to refer 631934b3aeSChuanqi Xuto an individual `coroutine instance`, since the terms `coroutine instance` and 641934b3aeSChuanqi Xu`coroutine object` aren't sufficiently defined in this case. 651934b3aeSChuanqi Xu 661934b3aeSChuanqi Xucoroutine frame 671934b3aeSChuanqi Xu--------------- 681934b3aeSChuanqi Xu 691934b3aeSChuanqi XuThe C++ Standard uses `coroutine state` to describe the allocated storage. In 701934b3aeSChuanqi Xuthe compiler, we use `coroutine frame` to describe the generated data structure 711934b3aeSChuanqi Xuthat contains the necessary information. 721934b3aeSChuanqi Xu 731934b3aeSChuanqi XuThe structure of coroutine frames 741934b3aeSChuanqi Xu================================= 751934b3aeSChuanqi Xu 761934b3aeSChuanqi XuThe structure of coroutine frames is defined as: 771934b3aeSChuanqi Xu 781934b3aeSChuanqi Xu.. code-block:: c++ 791934b3aeSChuanqi Xu 801934b3aeSChuanqi Xu struct { 811934b3aeSChuanqi Xu void (*__r)(); // function pointer to the `resume` function 821934b3aeSChuanqi Xu void (*__d)(); // function pointer to the `destroy` function 831934b3aeSChuanqi Xu promise_type; // the corresponding `promise_type` 841934b3aeSChuanqi Xu ... // Any other needed information 851934b3aeSChuanqi Xu } 861934b3aeSChuanqi Xu 871934b3aeSChuanqi XuIn the debugger, the function's name is obtainable from the address of the 881934b3aeSChuanqi Xufunction. And the name of `resume` function is equal to the name of the 891934b3aeSChuanqi Xucoroutine function. So the name of the coroutine is obtainable once the 901934b3aeSChuanqi Xuaddress of the coroutine is known. 911934b3aeSChuanqi Xu 921934b3aeSChuanqi XuPrint promise_type 931934b3aeSChuanqi Xu================== 941934b3aeSChuanqi Xu 951934b3aeSChuanqi XuEvery coroutine has a `promise_type`, which defines the behavior 961934b3aeSChuanqi Xufor the corresponding coroutine. In other words, if two coroutines have the 971934b3aeSChuanqi Xusame `promise_type`, they should behave in the same way. 981934b3aeSChuanqi XuTo print a `promise_type` in a debugger when stopped at a breakpoint inside a 991934b3aeSChuanqi Xucoroutine, printing the `promise_type` can be done by: 1001934b3aeSChuanqi Xu 1011934b3aeSChuanqi Xu.. parsed-literal:: 1021934b3aeSChuanqi Xu 1031934b3aeSChuanqi Xu print __promise 1041934b3aeSChuanqi Xu 1051934b3aeSChuanqi XuIt is also possible to print the `promise_type` of a coroutine from the address 1061934b3aeSChuanqi Xuof the coroutine frame. For example, if the address of a coroutine frame is 1071934b3aeSChuanqi Xu0x416eb0, and the type of the `promise_type` is `task::promise_type`, printing 1081934b3aeSChuanqi Xuthe `promise_type` can be done by: 1091934b3aeSChuanqi Xu 1101934b3aeSChuanqi Xu.. parsed-literal:: 1111934b3aeSChuanqi Xu 1121934b3aeSChuanqi Xu print (task::promise_type)*(0x416eb0+0x10) 1131934b3aeSChuanqi Xu 1141934b3aeSChuanqi XuThis is possible because the `promise_type` is guaranteed by the ABI to be at a 1151934b3aeSChuanqi Xu16 bit offset from the coroutine frame. 1161934b3aeSChuanqi Xu 1171934b3aeSChuanqi XuNote that there is also an ABI independent method: 1181934b3aeSChuanqi Xu 1191934b3aeSChuanqi Xu.. parsed-literal:: 1201934b3aeSChuanqi Xu 1211934b3aeSChuanqi Xu print std::coroutine_handle<task::promise_type>::from_address((void*)0x416eb0).promise() 1221934b3aeSChuanqi Xu 1231934b3aeSChuanqi XuThe functions `from_address(void*)` and `promise()` are often small enough to 1241934b3aeSChuanqi Xube removed during optimization, so this method may not be possible. 1251934b3aeSChuanqi Xu 1261934b3aeSChuanqi XuPrint coroutine frames 1271934b3aeSChuanqi Xu====================== 1281934b3aeSChuanqi Xu 1291934b3aeSChuanqi XuLLVM generates the debug information for the coroutine frame in the LLVM middle 1301934b3aeSChuanqi Xuend, which permits printing of the coroutine frame in the debugger. Much like 1311934b3aeSChuanqi Xuthe `promise_type`, when stopped at a breakpoint inside a coroutine we can 1321934b3aeSChuanqi Xuprint the coroutine frame by: 1331934b3aeSChuanqi Xu 1341934b3aeSChuanqi Xu.. parsed-literal:: 1351934b3aeSChuanqi Xu 1361934b3aeSChuanqi Xu print __coro_frame 1371934b3aeSChuanqi Xu 1381934b3aeSChuanqi Xu 1391934b3aeSChuanqi XuJust as printing the `promise_type` is possible from the coroutine address, 1401934b3aeSChuanqi Xuprinting the details of the coroutine frame from an address is also possible: 1411934b3aeSChuanqi Xu 14235f48572SAaron Ballman:: 1431934b3aeSChuanqi Xu 1441934b3aeSChuanqi Xu (gdb) # Get the address of coroutine frame 1451934b3aeSChuanqi Xu (gdb) print/x *0x418eb0 1461934b3aeSChuanqi Xu $1 = 0x4019e0 1471934b3aeSChuanqi Xu (gdb) # Get the linkage name for the coroutine 1481934b3aeSChuanqi Xu (gdb) x 0x4019e0 1491934b3aeSChuanqi Xu 0x4019e0 <_ZL9coro_taski>: 0xe5894855 150210a4197SChuanqi Xu (gdb) # Turn off the demangler temporarily to avoid the debugger misunderstanding the name. 151210a4197SChuanqi Xu (gdb) set demangle-style none 1521934b3aeSChuanqi Xu (gdb) # The coroutine frame type is 'linkage_name.coro_frame_ty' 153210a4197SChuanqi Xu (gdb) print ('_ZL9coro_taski.coro_frame_ty')*(0x418eb0) 1541934b3aeSChuanqi Xu $2 = {__resume_fn = 0x4019e0 <coro_task(int)>, __destroy_fn = 0x402000 <coro_task(int)>, __promise = {...}, ...} 1551934b3aeSChuanqi Xu 1561934b3aeSChuanqi XuThe above is possible because: 1571934b3aeSChuanqi Xu 1581934b3aeSChuanqi Xu(1) The name of the debug type of the coroutine frame is the `linkage_name`, 1591934b3aeSChuanqi Xuplus the `.coro_frame_ty` suffix because each coroutine function shares the 1601934b3aeSChuanqi Xusame coroutine type. 1611934b3aeSChuanqi Xu 1621934b3aeSChuanqi Xu(2) The coroutine function name is accessible from the address of the coroutine 1631934b3aeSChuanqi Xuframe. 1641934b3aeSChuanqi Xu 1651934b3aeSChuanqi XuThe above commands can be simplified by placing them in debug scripts. 1661934b3aeSChuanqi Xu 1671934b3aeSChuanqi XuExamples to print coroutine frames 1681934b3aeSChuanqi Xu---------------------------------- 1691934b3aeSChuanqi Xu 1701934b3aeSChuanqi XuThe print examples below use the following definition: 1711934b3aeSChuanqi Xu 1721934b3aeSChuanqi Xu.. code-block:: c++ 1731934b3aeSChuanqi Xu 1741934b3aeSChuanqi Xu #include <coroutine> 1751934b3aeSChuanqi Xu #include <iostream> 1761934b3aeSChuanqi Xu 1771934b3aeSChuanqi Xu struct task{ 1781934b3aeSChuanqi Xu struct promise_type { 1791934b3aeSChuanqi Xu task get_return_object() { return std::coroutine_handle<promise_type>::from_promise(*this); } 1801934b3aeSChuanqi Xu std::suspend_always initial_suspend() { return {}; } 1811934b3aeSChuanqi Xu std::suspend_always final_suspend() noexcept { return {}; } 1821934b3aeSChuanqi Xu void return_void() noexcept {} 1831934b3aeSChuanqi Xu void unhandled_exception() noexcept {} 1841934b3aeSChuanqi Xu 1851934b3aeSChuanqi Xu int count = 0; 1861934b3aeSChuanqi Xu }; 1871934b3aeSChuanqi Xu 1881934b3aeSChuanqi Xu void resume() noexcept { 1891934b3aeSChuanqi Xu handle.resume(); 1901934b3aeSChuanqi Xu } 1911934b3aeSChuanqi Xu 1921934b3aeSChuanqi Xu task(std::coroutine_handle<promise_type> hdl) : handle(hdl) {} 1931934b3aeSChuanqi Xu ~task() { 1941934b3aeSChuanqi Xu if (handle) 1951934b3aeSChuanqi Xu handle.destroy(); 1961934b3aeSChuanqi Xu } 1971934b3aeSChuanqi Xu 1981934b3aeSChuanqi Xu std::coroutine_handle<> handle; 1991934b3aeSChuanqi Xu }; 2001934b3aeSChuanqi Xu 2011934b3aeSChuanqi Xu class await_counter : public std::suspend_always { 2021934b3aeSChuanqi Xu public: 2031934b3aeSChuanqi Xu template<class PromiseType> 2041934b3aeSChuanqi Xu void await_suspend(std::coroutine_handle<PromiseType> handle) noexcept { 2051934b3aeSChuanqi Xu handle.promise().count++; 2061934b3aeSChuanqi Xu } 2071934b3aeSChuanqi Xu }; 2081934b3aeSChuanqi Xu 2091934b3aeSChuanqi Xu static task coro_task(int v) { 2101934b3aeSChuanqi Xu int a = v; 2111934b3aeSChuanqi Xu co_await await_counter{}; 2121934b3aeSChuanqi Xu a++; 2131934b3aeSChuanqi Xu std::cout << a << "\n"; 2141934b3aeSChuanqi Xu a++; 2151934b3aeSChuanqi Xu std::cout << a << "\n"; 2161934b3aeSChuanqi Xu a++; 2171934b3aeSChuanqi Xu std::cout << a << "\n"; 2181934b3aeSChuanqi Xu co_await await_counter{}; 2191934b3aeSChuanqi Xu a++; 2201934b3aeSChuanqi Xu std::cout << a << "\n"; 2211934b3aeSChuanqi Xu a++; 2221934b3aeSChuanqi Xu std::cout << a << "\n"; 2231934b3aeSChuanqi Xu } 2241934b3aeSChuanqi Xu 2251934b3aeSChuanqi Xu int main() { 2261934b3aeSChuanqi Xu task t = coro_task(43); 2271934b3aeSChuanqi Xu t.resume(); 2281934b3aeSChuanqi Xu t.resume(); 2291934b3aeSChuanqi Xu t.resume(); 2301934b3aeSChuanqi Xu return 0; 2311934b3aeSChuanqi Xu } 2321934b3aeSChuanqi Xu 2331934b3aeSChuanqi XuIn debug mode (`O0` + `g`), the printing result would be: 2341934b3aeSChuanqi Xu 2351934b3aeSChuanqi Xu.. parsed-literal:: 2361934b3aeSChuanqi Xu 2371934b3aeSChuanqi Xu {__resume_fn = 0x4019e0 <coro_task(int)>, __destroy_fn = 0x402000 <coro_task(int)>, __promise = {count = 1}, v = 43, a = 45, __coro_index = 1 '\001', struct_std__suspend_always_0 = {__int_8 = 0 '\000'}, 2381934b3aeSChuanqi Xu class_await_counter_1 = {__int_8 = 0 '\000'}, class_await_counter_2 = {__int_8 = 0 '\000'}, struct_std__suspend_always_3 = {__int_8 = 0 '\000'}} 2391934b3aeSChuanqi Xu 2401934b3aeSChuanqi XuIn the above, the values of `v` and `a` are clearly expressed, as are the 2411934b3aeSChuanqi Xutemporary values for `await_counter` (`class_await_counter_1` and 2421934b3aeSChuanqi Xu`class_await_counter_2`) and `std::suspend_always` ( 2431934b3aeSChuanqi Xu`struct_std__suspend_always_0` and `struct_std__suspend_always_3`). The index 2441934b3aeSChuanqi Xuof the current suspension point of the coroutine is emitted as `__coro_index`. 2451934b3aeSChuanqi XuIn the above example, the `__coro_index` value of `1` means the coroutine 2461934b3aeSChuanqi Xustopped at the second suspend point (Note that `__coro_index` is zero indexed) 2471934b3aeSChuanqi Xuwhich is the first `co_await await_counter{};` in `coro_task`. Note that the 2481934b3aeSChuanqi Xufirst initial suspend point is the compiler generated 2491934b3aeSChuanqi Xu`co_await promise_type::initial_suspend()`. 2501934b3aeSChuanqi Xu 2511934b3aeSChuanqi XuHowever, when optimizations are enabled, the printed result changes drastically: 2521934b3aeSChuanqi Xu 2531934b3aeSChuanqi Xu.. parsed-literal:: 2541934b3aeSChuanqi Xu 2551934b3aeSChuanqi Xu {__resume_fn = 0x401280 <coro_task(int)>, __destroy_fn = 0x401390 <coro_task(int)>, __promise = {count = 1}, __int_32_0 = 43, __coro_index = 1 '\001'} 2561934b3aeSChuanqi Xu 2571934b3aeSChuanqi XuUnused values are optimized out, as well as the name of the local variable `a`. 2581934b3aeSChuanqi XuThe only information remained is the value of a 32 bit integer. In this simple 2591934b3aeSChuanqi Xucase, it seems to be pretty clear that `__int_32_0` represents `a`. However, it 2601934b3aeSChuanqi Xuis not true. 2611934b3aeSChuanqi Xu 2621934b3aeSChuanqi XuAn important note with optimization is that the value of a variable may not 2631934b3aeSChuanqi Xuproperly express the intended value in the source code. For example: 2641934b3aeSChuanqi Xu 2651934b3aeSChuanqi Xu.. code-block:: c++ 2661934b3aeSChuanqi Xu 2671934b3aeSChuanqi Xu static task coro_task(int v) { 2681934b3aeSChuanqi Xu int a = v; 2691934b3aeSChuanqi Xu co_await await_counter{}; 2701934b3aeSChuanqi Xu a++; // __int_32_0 is 43 here 2711934b3aeSChuanqi Xu std::cout << a << "\n"; 2721934b3aeSChuanqi Xu a++; // __int_32_0 is still 43 here 2731934b3aeSChuanqi Xu std::cout << a << "\n"; 2741934b3aeSChuanqi Xu a++; // __int_32_0 is still 43 here! 2751934b3aeSChuanqi Xu std::cout << a << "\n"; 2761934b3aeSChuanqi Xu co_await await_counter{}; 2771934b3aeSChuanqi Xu a++; // __int_32_0 is still 43 here!! 2781934b3aeSChuanqi Xu std::cout << a << "\n"; 2791934b3aeSChuanqi Xu a++; // Why is __int_32_0 still 43 here? 2801934b3aeSChuanqi Xu std::cout << a << "\n"; 2811934b3aeSChuanqi Xu } 2821934b3aeSChuanqi Xu 2831934b3aeSChuanqi XuWhen debugging step-by-step, the value of `__int_32_0` seemingly does not 2841934b3aeSChuanqi Xuchange, despite being frequently incremented, and instead is always `43`. 2851934b3aeSChuanqi XuWhile this might be surprising, this is a result of the optimizer recognizing 2861934b3aeSChuanqi Xuthat it can eliminate most of the load/store operations. The above code gets 2871934b3aeSChuanqi Xuoptimized to the equivalent of: 2881934b3aeSChuanqi Xu 2891934b3aeSChuanqi Xu.. code-block:: c++ 2901934b3aeSChuanqi Xu 2911934b3aeSChuanqi Xu static task coro_task(int v) { 2921934b3aeSChuanqi Xu store v to __int_32_0 in the frame 2931934b3aeSChuanqi Xu co_await await_counter{}; 2941934b3aeSChuanqi Xu a = load __int_32_0 2951934b3aeSChuanqi Xu std::cout << a+1 << "\n"; 2961934b3aeSChuanqi Xu std::cout << a+2 << "\n"; 2971934b3aeSChuanqi Xu std::cout << a+3 << "\n"; 2981934b3aeSChuanqi Xu co_await await_counter{}; 2991934b3aeSChuanqi Xu a = load __int_32_0 3001934b3aeSChuanqi Xu std::cout << a+4 << "\n"; 3011934b3aeSChuanqi Xu std::cout << a+5 << "\n"; 3021934b3aeSChuanqi Xu } 3031934b3aeSChuanqi Xu 3041934b3aeSChuanqi XuIt should now be obvious why the value of `__int_32_0` remains unchanged 3051934b3aeSChuanqi Xuthroughout the function. It is important to recognize that `__int_32_0` 3061934b3aeSChuanqi Xudoes not directly correspond to `a`, but is instead a variable generated 3071934b3aeSChuanqi Xuto assist the compiler in code generation. The variables in an optimized 3081934b3aeSChuanqi Xucoroutine frame should not be thought of as directly representing the 3091934b3aeSChuanqi Xuvariables in the C++ source. 3101934b3aeSChuanqi Xu 3111934b3aeSChuanqi XuGet the suspended points 3121934b3aeSChuanqi Xu======================== 3131934b3aeSChuanqi Xu 3141934b3aeSChuanqi XuAn important requirement for debugging coroutines is to understand suspended 3151934b3aeSChuanqi Xupoints, which are where the coroutine is currently suspended and awaiting. 3161934b3aeSChuanqi Xu 3171934b3aeSChuanqi XuFor simple cases like the above, inspecting the value of the `__coro_index` 3181934b3aeSChuanqi Xuvariable in the coroutine frame works well. 3191934b3aeSChuanqi Xu 3201934b3aeSChuanqi XuHowever, it is not quite so simple in really complex situations. In these 3211934b3aeSChuanqi Xucases, it is necessary to use the coroutine libraries to insert the 3221934b3aeSChuanqi Xuline-number. 3231934b3aeSChuanqi Xu 3241934b3aeSChuanqi XuFor example: 3251934b3aeSChuanqi Xu 3261934b3aeSChuanqi Xu.. code-block:: c++ 3271934b3aeSChuanqi Xu 3281934b3aeSChuanqi Xu // For all the promise_type we want: 3291934b3aeSChuanqi Xu class promise_type { 3301934b3aeSChuanqi Xu ... 3311934b3aeSChuanqi Xu + unsigned line_number = 0xffffffff; 3321934b3aeSChuanqi Xu }; 3331934b3aeSChuanqi Xu 3341934b3aeSChuanqi Xu #include <source_location> 3351934b3aeSChuanqi Xu 3361934b3aeSChuanqi Xu // For all the awaiter types we need: 3371934b3aeSChuanqi Xu class awaiter { 3381934b3aeSChuanqi Xu ... 3391934b3aeSChuanqi Xu template <typename Promise> 3401934b3aeSChuanqi Xu void await_suspend(std::coroutine_handle<Promise> handle, 3411934b3aeSChuanqi Xu std::source_location sl = std::source_location::current()) { 3421934b3aeSChuanqi Xu ... 3431934b3aeSChuanqi Xu handle.promise().line_number = sl.line(); 3441934b3aeSChuanqi Xu } 3451934b3aeSChuanqi Xu }; 3461934b3aeSChuanqi Xu 3471934b3aeSChuanqi XuIn this case, we use `std::source_location` to store the line number of the 3481934b3aeSChuanqi Xuawait inside the `promise_type`. Since we can locate the coroutine function 3491934b3aeSChuanqi Xufrom the address of the coroutine, we can identify suspended points this way 3501934b3aeSChuanqi Xuas well. 3511934b3aeSChuanqi Xu 3521934b3aeSChuanqi XuThe downside here is that this comes at the price of additional runtime cost. 3531934b3aeSChuanqi XuThis is consistent with the C++ philosophy of "Pay for what you use". 3541934b3aeSChuanqi Xu 3551934b3aeSChuanqi XuGet the asynchronous stack 3561934b3aeSChuanqi Xu========================== 3571934b3aeSChuanqi Xu 3581934b3aeSChuanqi XuAnother important requirement to debug a coroutine is to print the asynchronous 3591934b3aeSChuanqi Xustack to identify the asynchronous caller of the coroutine. As many 3601934b3aeSChuanqi Xuimplementations of coroutine types store `std::coroutine_handle<> continuation` 3611934b3aeSChuanqi Xuin the promise type, identifying the caller should be trivial. The 3621934b3aeSChuanqi Xu`continuation` is typically the awaiting coroutine for the current coroutine. 3631934b3aeSChuanqi XuThat is, the asynchronous parent. 3641934b3aeSChuanqi Xu 3651934b3aeSChuanqi XuSince the `promise_type` is obtainable from the address of a coroutine and 3661934b3aeSChuanqi Xucontains the corresponding continuation (which itself is a coroutine with a 3671934b3aeSChuanqi Xu`promise_type`), it should be trivial to print the entire asynchronous stack. 3681934b3aeSChuanqi Xu 3691934b3aeSChuanqi XuThis logic should be quite easily captured in a debugger script. 3701934b3aeSChuanqi Xu 3714332b049SChuanqi XuExamples to print asynchronous stack 3724332b049SChuanqi Xu------------------------------------ 3734332b049SChuanqi Xu 3744332b049SChuanqi XuHere is an example to print the asynchronous stack for the normal task implementation. 3754332b049SChuanqi Xu 3764332b049SChuanqi Xu.. code-block:: c++ 3774332b049SChuanqi Xu 3784332b049SChuanqi Xu // debugging-example.cpp 3794332b049SChuanqi Xu #include <coroutine> 3804332b049SChuanqi Xu #include <iostream> 3814332b049SChuanqi Xu #include <utility> 3824332b049SChuanqi Xu 3834332b049SChuanqi Xu struct task { 3844332b049SChuanqi Xu struct promise_type { 3854332b049SChuanqi Xu task get_return_object(); 3864332b049SChuanqi Xu std::suspend_always initial_suspend() { return {}; } 3874332b049SChuanqi Xu 3884332b049SChuanqi Xu void unhandled_exception() noexcept {} 3894332b049SChuanqi Xu 3904332b049SChuanqi Xu struct FinalSuspend { 3914332b049SChuanqi Xu std::coroutine_handle<> continuation; 3924332b049SChuanqi Xu auto await_ready() noexcept { return false; } 3934332b049SChuanqi Xu auto await_suspend(std::coroutine_handle<> handle) noexcept { 3944332b049SChuanqi Xu return continuation; 3954332b049SChuanqi Xu } 3964332b049SChuanqi Xu void await_resume() noexcept {} 3974332b049SChuanqi Xu }; 3984332b049SChuanqi Xu FinalSuspend final_suspend() noexcept { return {continuation}; } 3994332b049SChuanqi Xu 4004332b049SChuanqi Xu void return_value(int res) { result = res; } 4014332b049SChuanqi Xu 4024332b049SChuanqi Xu std::coroutine_handle<> continuation = std::noop_coroutine(); 4034332b049SChuanqi Xu int result = 0; 4044332b049SChuanqi Xu }; 4054332b049SChuanqi Xu 4064332b049SChuanqi Xu task(std::coroutine_handle<promise_type> handle) : handle(handle) {} 4074332b049SChuanqi Xu ~task() { 4084332b049SChuanqi Xu if (handle) 4094332b049SChuanqi Xu handle.destroy(); 4104332b049SChuanqi Xu } 4114332b049SChuanqi Xu 4124332b049SChuanqi Xu auto operator co_await() { 4134332b049SChuanqi Xu struct Awaiter { 4144332b049SChuanqi Xu std::coroutine_handle<promise_type> handle; 4154332b049SChuanqi Xu auto await_ready() { return false; } 4164332b049SChuanqi Xu auto await_suspend(std::coroutine_handle<> continuation) { 4174332b049SChuanqi Xu handle.promise().continuation = continuation; 4184332b049SChuanqi Xu return handle; 4194332b049SChuanqi Xu } 4204332b049SChuanqi Xu int await_resume() { 4214332b049SChuanqi Xu int ret = handle.promise().result; 4224332b049SChuanqi Xu handle.destroy(); 4234332b049SChuanqi Xu return ret; 4244332b049SChuanqi Xu } 4254332b049SChuanqi Xu }; 4264332b049SChuanqi Xu return Awaiter{std::exchange(handle, nullptr)}; 4274332b049SChuanqi Xu } 4284332b049SChuanqi Xu 4294332b049SChuanqi Xu int syncStart() { 4304332b049SChuanqi Xu handle.resume(); 4314332b049SChuanqi Xu return handle.promise().result; 4324332b049SChuanqi Xu } 4334332b049SChuanqi Xu 4344332b049SChuanqi Xu private: 4354332b049SChuanqi Xu std::coroutine_handle<promise_type> handle; 4364332b049SChuanqi Xu }; 4374332b049SChuanqi Xu 4384332b049SChuanqi Xu task task::promise_type::get_return_object() { 4394332b049SChuanqi Xu return std::coroutine_handle<promise_type>::from_promise(*this); 4404332b049SChuanqi Xu } 4414332b049SChuanqi Xu 4424332b049SChuanqi Xu namespace detail { 4434332b049SChuanqi Xu template <int N> 4444332b049SChuanqi Xu task chain_fn() { 4454332b049SChuanqi Xu co_return N + co_await chain_fn<N - 1>(); 4464332b049SChuanqi Xu } 4474332b049SChuanqi Xu 4484332b049SChuanqi Xu template <> 4494332b049SChuanqi Xu task chain_fn<0>() { 4504332b049SChuanqi Xu // This is the default breakpoint. 4514332b049SChuanqi Xu __builtin_debugtrap(); 4524332b049SChuanqi Xu co_return 0; 4534332b049SChuanqi Xu } 4544332b049SChuanqi Xu } // namespace detail 4554332b049SChuanqi Xu 4564332b049SChuanqi Xu task chain() { 4574332b049SChuanqi Xu co_return co_await detail::chain_fn<30>(); 4584332b049SChuanqi Xu } 4594332b049SChuanqi Xu 4604332b049SChuanqi Xu int main() { 4614332b049SChuanqi Xu std::cout << chain().syncStart() << "\n"; 4624332b049SChuanqi Xu return 0; 4634332b049SChuanqi Xu } 4644332b049SChuanqi Xu 4654332b049SChuanqi XuIn the example, the ``task`` coroutine holds a ``continuation`` field, 4664332b049SChuanqi Xuwhich would be resumed once the ``task`` completes. 4674332b049SChuanqi XuIn another word, the ``continuation`` is the asynchronous caller for the ``task``. 4684332b049SChuanqi XuJust like the normal function returns to its caller when the function completes. 4694332b049SChuanqi Xu 4704332b049SChuanqi XuSo we can use the ``continuation`` field to construct the asynchronous stack: 4714332b049SChuanqi Xu 4724332b049SChuanqi Xu.. code-block:: python 4734332b049SChuanqi Xu 4744332b049SChuanqi Xu # debugging-helper.py 4754332b049SChuanqi Xu import gdb 4764332b049SChuanqi Xu from gdb.FrameDecorator import FrameDecorator 4774332b049SChuanqi Xu 4784332b049SChuanqi Xu class SymValueWrapper(): 4794332b049SChuanqi Xu def __init__(self, symbol, value): 4804332b049SChuanqi Xu self.sym = symbol 4814332b049SChuanqi Xu self.val = value 4824332b049SChuanqi Xu 4834332b049SChuanqi Xu def __str__(self): 4844332b049SChuanqi Xu return str(self.sym) + " = " + str(self.val) 4854332b049SChuanqi Xu 4864332b049SChuanqi Xu def get_long_pointer_size(): 4874332b049SChuanqi Xu return gdb.lookup_type('long').pointer().sizeof 4884332b049SChuanqi Xu 4894332b049SChuanqi Xu def cast_addr2long_pointer(addr): 4904332b049SChuanqi Xu return gdb.Value(addr).cast(gdb.lookup_type('long').pointer()) 4914332b049SChuanqi Xu 4924332b049SChuanqi Xu def dereference(addr): 4934332b049SChuanqi Xu return long(cast_addr2long_pointer(addr).dereference()) 4944332b049SChuanqi Xu 4954332b049SChuanqi Xu class CoroutineFrame(object): 4964332b049SChuanqi Xu def __init__(self, task_addr): 4974332b049SChuanqi Xu self.frame_addr = task_addr 4984332b049SChuanqi Xu self.resume_addr = task_addr 4994332b049SChuanqi Xu self.destroy_addr = task_addr + get_long_pointer_size() 5004332b049SChuanqi Xu self.promise_addr = task_addr + get_long_pointer_size() * 2 5014332b049SChuanqi Xu # In the example, the continuation is the first field member of the promise_type. 5024332b049SChuanqi Xu # So they have the same addresses. 5034332b049SChuanqi Xu # If we want to generalize the scripts to other coroutine types, we need to be sure 50411e29758SKazu Hirata # the continuation field is the first member of promise_type. 5054332b049SChuanqi Xu self.continuation_addr = self.promise_addr 5064332b049SChuanqi Xu 5074332b049SChuanqi Xu def next_task_addr(self): 5084332b049SChuanqi Xu return dereference(self.continuation_addr) 5094332b049SChuanqi Xu 5104332b049SChuanqi Xu class CoroutineFrameDecorator(FrameDecorator): 5114332b049SChuanqi Xu def __init__(self, coro_frame): 5124332b049SChuanqi Xu super(CoroutineFrameDecorator, self).__init__(None) 5134332b049SChuanqi Xu self.coro_frame = coro_frame 5144332b049SChuanqi Xu self.resume_func = dereference(self.coro_frame.resume_addr) 5154332b049SChuanqi Xu self.resume_func_block = gdb.block_for_pc(self.resume_func) 516*e8182029SEisuke Kawashima if self.resume_func_block is None: 5174332b049SChuanqi Xu raise Exception('Not stackless coroutine.') 5184332b049SChuanqi Xu self.line_info = gdb.find_pc_line(self.resume_func) 5194332b049SChuanqi Xu 5204332b049SChuanqi Xu def address(self): 5214332b049SChuanqi Xu return self.resume_func 5224332b049SChuanqi Xu 5234332b049SChuanqi Xu def filename(self): 5244332b049SChuanqi Xu return self.line_info.symtab.filename 5254332b049SChuanqi Xu 5264332b049SChuanqi Xu def frame_args(self): 5274332b049SChuanqi Xu return [SymValueWrapper("frame_addr", cast_addr2long_pointer(self.coro_frame.frame_addr)), 5284332b049SChuanqi Xu SymValueWrapper("promise_addr", cast_addr2long_pointer(self.coro_frame.promise_addr)), 5294332b049SChuanqi Xu SymValueWrapper("continuation_addr", cast_addr2long_pointer(self.coro_frame.continuation_addr)) 5304332b049SChuanqi Xu ] 5314332b049SChuanqi Xu 5324332b049SChuanqi Xu def function(self): 5334332b049SChuanqi Xu return self.resume_func_block.function.print_name 5344332b049SChuanqi Xu 5354332b049SChuanqi Xu def line(self): 5364332b049SChuanqi Xu return self.line_info.line 5374332b049SChuanqi Xu 5384332b049SChuanqi Xu class StripDecorator(FrameDecorator): 5394332b049SChuanqi Xu def __init__(self, frame): 5404332b049SChuanqi Xu super(StripDecorator, self).__init__(frame) 5414332b049SChuanqi Xu self.frame = frame 5424332b049SChuanqi Xu f = frame.function() 5434332b049SChuanqi Xu self.function_name = f 5444332b049SChuanqi Xu 5454332b049SChuanqi Xu def __str__(self, shift = 2): 546*e8182029SEisuke Kawashima addr = "" if self.address() is None else '%#x' % self.address() + " in " 547*e8182029SEisuke Kawashima location = "" if self.filename() is None else " at " + self.filename() + ":" + str(self.line()) 5484332b049SChuanqi Xu return addr + self.function() + " " + str([str(args) for args in self.frame_args()]) + location 5494332b049SChuanqi Xu 5504332b049SChuanqi Xu class CoroutineFilter: 5514332b049SChuanqi Xu def create_coroutine_frames(self, task_addr): 5524332b049SChuanqi Xu frames = [] 5534332b049SChuanqi Xu while task_addr != 0: 5544332b049SChuanqi Xu coro_frame = CoroutineFrame(task_addr) 5554332b049SChuanqi Xu frames.append(CoroutineFrameDecorator(coro_frame)) 5564332b049SChuanqi Xu task_addr = coro_frame.next_task_addr() 5574332b049SChuanqi Xu return frames 5584332b049SChuanqi Xu 5594332b049SChuanqi Xu class AsyncStack(gdb.Command): 5604332b049SChuanqi Xu def __init__(self): 5614332b049SChuanqi Xu super(AsyncStack, self).__init__("async-bt", gdb.COMMAND_USER) 5624332b049SChuanqi Xu 5634332b049SChuanqi Xu def invoke(self, arg, from_tty): 5644332b049SChuanqi Xu coroutine_filter = CoroutineFilter() 5654332b049SChuanqi Xu argv = gdb.string_to_argv(arg) 5664332b049SChuanqi Xu if len(argv) == 0: 5674332b049SChuanqi Xu try: 5684332b049SChuanqi Xu task = gdb.parse_and_eval('__coro_frame') 5694332b049SChuanqi Xu task = int(str(task.address), 16) 5704332b049SChuanqi Xu except Exception: 5714332b049SChuanqi Xu print ("Can't find __coro_frame in current context.\n" + 5724332b049SChuanqi Xu "Please use `async-bt` in stackless coroutine context.") 5734332b049SChuanqi Xu return 5744332b049SChuanqi Xu elif len(argv) != 1: 5754332b049SChuanqi Xu print("usage: async-bt <pointer to task>") 5764332b049SChuanqi Xu return 5774332b049SChuanqi Xu else: 5784332b049SChuanqi Xu task = int(argv[0], 16) 5794332b049SChuanqi Xu 5804332b049SChuanqi Xu frames = coroutine_filter.create_coroutine_frames(task) 5814332b049SChuanqi Xu i = 0 5824332b049SChuanqi Xu for f in frames: 5834332b049SChuanqi Xu print '#'+ str(i), str(StripDecorator(f)) 5844332b049SChuanqi Xu i += 1 5854332b049SChuanqi Xu return 5864332b049SChuanqi Xu 5874332b049SChuanqi Xu AsyncStack() 5884332b049SChuanqi Xu 5894332b049SChuanqi Xu class ShowCoroFrame(gdb.Command): 5904332b049SChuanqi Xu def __init__(self): 5914332b049SChuanqi Xu super(ShowCoroFrame, self).__init__("show-coro-frame", gdb.COMMAND_USER) 5924332b049SChuanqi Xu 5934332b049SChuanqi Xu def invoke(self, arg, from_tty): 5944332b049SChuanqi Xu argv = gdb.string_to_argv(arg) 5954332b049SChuanqi Xu if len(argv) != 1: 5964332b049SChuanqi Xu print("usage: show-coro-frame <address of coroutine frame>") 5974332b049SChuanqi Xu return 5984332b049SChuanqi Xu 5994332b049SChuanqi Xu addr = int(argv[0], 16) 6004332b049SChuanqi Xu block = gdb.block_for_pc(long(cast_addr2long_pointer(addr).dereference())) 601*e8182029SEisuke Kawashima if block is None: 6024332b049SChuanqi Xu print "block " + str(addr) + " is none." 6034332b049SChuanqi Xu return 6044332b049SChuanqi Xu 6054332b049SChuanqi Xu # Disable demangling since gdb will treat names starting with `_Z`(The marker for Itanium ABI) specially. 6064332b049SChuanqi Xu gdb.execute("set demangle-style none") 6074332b049SChuanqi Xu 6084332b049SChuanqi Xu coro_frame_type = gdb.lookup_type(block.function.linkage_name + ".coro_frame_ty") 6094332b049SChuanqi Xu coro_frame_ptr_type = coro_frame_type.pointer() 6104332b049SChuanqi Xu coro_frame = gdb.Value(addr).cast(coro_frame_ptr_type).dereference() 6114332b049SChuanqi Xu 6124332b049SChuanqi Xu gdb.execute("set demangle-style auto") 6134332b049SChuanqi Xu gdb.write(coro_frame.format_string(pretty_structs = True)) 6144332b049SChuanqi Xu 6154332b049SChuanqi Xu ShowCoroFrame() 6164332b049SChuanqi Xu 6174332b049SChuanqi XuThen let's run: 6184332b049SChuanqi Xu 6194332b049SChuanqi Xu.. code-block:: text 6204332b049SChuanqi Xu 6214332b049SChuanqi Xu $ clang++ -std=c++20 -g debugging-example.cpp -o debugging-example 6224332b049SChuanqi Xu $ gdb ./debugging-example 623cc8237d9SAyushi Shukla (gdb) # We've already set the breakpoint. 6244332b049SChuanqi Xu (gdb) r 6254332b049SChuanqi Xu Program received signal SIGTRAP, Trace/breakpoint trap. 6264332b049SChuanqi Xu detail::chain_fn<0> () at debugging-example2.cpp:73 6274332b049SChuanqi Xu 73 co_return 0; 6284332b049SChuanqi Xu (gdb) # Executes the debugging scripts 6294332b049SChuanqi Xu (gdb) source debugging-helper.py 6304332b049SChuanqi Xu (gdb) # Print the asynchronous stack 6314332b049SChuanqi Xu (gdb) async-bt 6324332b049SChuanqi Xu #0 0x401c40 in detail::chain_fn<0>() ['frame_addr = 0x441860', 'promise_addr = 0x441870', 'continuation_addr = 0x441870'] at debugging-example.cpp:71 6334332b049SChuanqi Xu #1 0x4022d0 in detail::chain_fn<1>() ['frame_addr = 0x441810', 'promise_addr = 0x441820', 'continuation_addr = 0x441820'] at debugging-example.cpp:66 6344332b049SChuanqi Xu #2 0x403060 in detail::chain_fn<2>() ['frame_addr = 0x4417c0', 'promise_addr = 0x4417d0', 'continuation_addr = 0x4417d0'] at debugging-example.cpp:66 6354332b049SChuanqi Xu #3 0x403df0 in detail::chain_fn<3>() ['frame_addr = 0x441770', 'promise_addr = 0x441780', 'continuation_addr = 0x441780'] at debugging-example.cpp:66 6364332b049SChuanqi Xu #4 0x404b80 in detail::chain_fn<4>() ['frame_addr = 0x441720', 'promise_addr = 0x441730', 'continuation_addr = 0x441730'] at debugging-example.cpp:66 6374332b049SChuanqi Xu #5 0x405910 in detail::chain_fn<5>() ['frame_addr = 0x4416d0', 'promise_addr = 0x4416e0', 'continuation_addr = 0x4416e0'] at debugging-example.cpp:66 6384332b049SChuanqi Xu #6 0x4066a0 in detail::chain_fn<6>() ['frame_addr = 0x441680', 'promise_addr = 0x441690', 'continuation_addr = 0x441690'] at debugging-example.cpp:66 6394332b049SChuanqi Xu #7 0x407430 in detail::chain_fn<7>() ['frame_addr = 0x441630', 'promise_addr = 0x441640', 'continuation_addr = 0x441640'] at debugging-example.cpp:66 6404332b049SChuanqi Xu #8 0x4081c0 in detail::chain_fn<8>() ['frame_addr = 0x4415e0', 'promise_addr = 0x4415f0', 'continuation_addr = 0x4415f0'] at debugging-example.cpp:66 6414332b049SChuanqi Xu #9 0x408f50 in detail::chain_fn<9>() ['frame_addr = 0x441590', 'promise_addr = 0x4415a0', 'continuation_addr = 0x4415a0'] at debugging-example.cpp:66 6424332b049SChuanqi Xu #10 0x409ce0 in detail::chain_fn<10>() ['frame_addr = 0x441540', 'promise_addr = 0x441550', 'continuation_addr = 0x441550'] at debugging-example.cpp:66 6434332b049SChuanqi Xu #11 0x40aa70 in detail::chain_fn<11>() ['frame_addr = 0x4414f0', 'promise_addr = 0x441500', 'continuation_addr = 0x441500'] at debugging-example.cpp:66 6444332b049SChuanqi Xu #12 0x40b800 in detail::chain_fn<12>() ['frame_addr = 0x4414a0', 'promise_addr = 0x4414b0', 'continuation_addr = 0x4414b0'] at debugging-example.cpp:66 6454332b049SChuanqi Xu #13 0x40c590 in detail::chain_fn<13>() ['frame_addr = 0x441450', 'promise_addr = 0x441460', 'continuation_addr = 0x441460'] at debugging-example.cpp:66 6464332b049SChuanqi Xu #14 0x40d320 in detail::chain_fn<14>() ['frame_addr = 0x441400', 'promise_addr = 0x441410', 'continuation_addr = 0x441410'] at debugging-example.cpp:66 6474332b049SChuanqi Xu #15 0x40e0b0 in detail::chain_fn<15>() ['frame_addr = 0x4413b0', 'promise_addr = 0x4413c0', 'continuation_addr = 0x4413c0'] at debugging-example.cpp:66 6484332b049SChuanqi Xu #16 0x40ee40 in detail::chain_fn<16>() ['frame_addr = 0x441360', 'promise_addr = 0x441370', 'continuation_addr = 0x441370'] at debugging-example.cpp:66 6494332b049SChuanqi Xu #17 0x40fbd0 in detail::chain_fn<17>() ['frame_addr = 0x441310', 'promise_addr = 0x441320', 'continuation_addr = 0x441320'] at debugging-example.cpp:66 6504332b049SChuanqi Xu #18 0x410960 in detail::chain_fn<18>() ['frame_addr = 0x4412c0', 'promise_addr = 0x4412d0', 'continuation_addr = 0x4412d0'] at debugging-example.cpp:66 6514332b049SChuanqi Xu #19 0x4116f0 in detail::chain_fn<19>() ['frame_addr = 0x441270', 'promise_addr = 0x441280', 'continuation_addr = 0x441280'] at debugging-example.cpp:66 6524332b049SChuanqi Xu #20 0x412480 in detail::chain_fn<20>() ['frame_addr = 0x441220', 'promise_addr = 0x441230', 'continuation_addr = 0x441230'] at debugging-example.cpp:66 6534332b049SChuanqi Xu #21 0x413210 in detail::chain_fn<21>() ['frame_addr = 0x4411d0', 'promise_addr = 0x4411e0', 'continuation_addr = 0x4411e0'] at debugging-example.cpp:66 6544332b049SChuanqi Xu #22 0x413fa0 in detail::chain_fn<22>() ['frame_addr = 0x441180', 'promise_addr = 0x441190', 'continuation_addr = 0x441190'] at debugging-example.cpp:66 6554332b049SChuanqi Xu #23 0x414d30 in detail::chain_fn<23>() ['frame_addr = 0x441130', 'promise_addr = 0x441140', 'continuation_addr = 0x441140'] at debugging-example.cpp:66 6564332b049SChuanqi Xu #24 0x415ac0 in detail::chain_fn<24>() ['frame_addr = 0x4410e0', 'promise_addr = 0x4410f0', 'continuation_addr = 0x4410f0'] at debugging-example.cpp:66 6574332b049SChuanqi Xu #25 0x416850 in detail::chain_fn<25>() ['frame_addr = 0x441090', 'promise_addr = 0x4410a0', 'continuation_addr = 0x4410a0'] at debugging-example.cpp:66 6584332b049SChuanqi Xu #26 0x4175e0 in detail::chain_fn<26>() ['frame_addr = 0x441040', 'promise_addr = 0x441050', 'continuation_addr = 0x441050'] at debugging-example.cpp:66 6594332b049SChuanqi Xu #27 0x418370 in detail::chain_fn<27>() ['frame_addr = 0x440ff0', 'promise_addr = 0x441000', 'continuation_addr = 0x441000'] at debugging-example.cpp:66 6604332b049SChuanqi Xu #28 0x419100 in detail::chain_fn<28>() ['frame_addr = 0x440fa0', 'promise_addr = 0x440fb0', 'continuation_addr = 0x440fb0'] at debugging-example.cpp:66 6614332b049SChuanqi Xu #29 0x419e90 in detail::chain_fn<29>() ['frame_addr = 0x440f50', 'promise_addr = 0x440f60', 'continuation_addr = 0x440f60'] at debugging-example.cpp:66 6624332b049SChuanqi Xu #30 0x41ac20 in detail::chain_fn<30>() ['frame_addr = 0x440f00', 'promise_addr = 0x440f10', 'continuation_addr = 0x440f10'] at debugging-example.cpp:66 6634332b049SChuanqi Xu #31 0x41b9b0 in chain() ['frame_addr = 0x440eb0', 'promise_addr = 0x440ec0', 'continuation_addr = 0x440ec0'] at debugging-example.cpp:77 6644332b049SChuanqi Xu 6654332b049SChuanqi XuNow we get the complete asynchronous stack! 6664332b049SChuanqi XuIt is also possible to print other asynchronous stack which doesn't live in the top of the stack. 6674332b049SChuanqi XuWe can make it by passing the address of the corresponding coroutine frame to ``async-bt`` command. 6684332b049SChuanqi Xu 6694332b049SChuanqi XuBy the debugging scripts, we can print any coroutine frame too as long as we know the address. 6704332b049SChuanqi XuFor example, we can print the coroutine frame for ``detail::chain_fn<18>()`` in the above example. 6714332b049SChuanqi XuFrom the log record, we know the address of the coroutine frame is ``0x4412c0`` in the run. Then we can: 6724332b049SChuanqi Xu 6734332b049SChuanqi Xu.. code-block:: text 6744332b049SChuanqi Xu 6754332b049SChuanqi Xu (gdb) show-coro-frame 0x4412c0 6764332b049SChuanqi Xu { 6774332b049SChuanqi Xu __resume_fn = 0x410960 <detail::chain_fn<18>()>, 6784332b049SChuanqi Xu __destroy_fn = 0x410d60 <detail::chain_fn<18>()>, 6794332b049SChuanqi Xu __promise = { 6804332b049SChuanqi Xu continuation = { 6814332b049SChuanqi Xu _M_fr_ptr = 0x441270 6824332b049SChuanqi Xu }, 6834332b049SChuanqi Xu result = 0 6844332b049SChuanqi Xu }, 6854332b049SChuanqi Xu struct_Awaiter_0 = { 6864332b049SChuanqi Xu struct_std____n4861__coroutine_handle_0 = { 6874332b049SChuanqi Xu struct_std____n4861__coroutine_handle = { 6884332b049SChuanqi Xu PointerType = 0x441310 6894332b049SChuanqi Xu } 6904332b049SChuanqi Xu } 6914332b049SChuanqi Xu }, 6924332b049SChuanqi Xu struct_task_1 = { 6934332b049SChuanqi Xu struct_std____n4861__coroutine_handle_0 = { 6944332b049SChuanqi Xu struct_std____n4861__coroutine_handle = { 6954332b049SChuanqi Xu PointerType = 0x0 6964332b049SChuanqi Xu } 6974332b049SChuanqi Xu } 6984332b049SChuanqi Xu }, 6994332b049SChuanqi Xu struct_task__promise_type__FinalSuspend_2 = { 7004332b049SChuanqi Xu struct_std____n4861__coroutine_handle = { 7014332b049SChuanqi Xu PointerType = 0x0 7024332b049SChuanqi Xu } 7034332b049SChuanqi Xu }, 7044332b049SChuanqi Xu __coro_index = 1 '\001', 7054332b049SChuanqi Xu struct_std____n4861__suspend_always_3 = { 7064332b049SChuanqi Xu __int_8 = 0 '\000' 7074332b049SChuanqi Xu } 7084332b049SChuanqi Xu 7094332b049SChuanqi Xu 7101934b3aeSChuanqi XuGet the living coroutines 7111934b3aeSChuanqi Xu========================= 7121934b3aeSChuanqi Xu 7131934b3aeSChuanqi XuAnother useful task when debugging coroutines is to enumerate the list of 7141934b3aeSChuanqi Xuliving coroutines, which is often done with threads. While technically 7151934b3aeSChuanqi Xupossible, this task is not recommended in production code as it is costly at 7161934b3aeSChuanqi Xuruntime. One such solution is to store the list of currently running coroutines 7171934b3aeSChuanqi Xuin a collection: 7181934b3aeSChuanqi Xu 7191934b3aeSChuanqi Xu.. code-block:: c++ 7201934b3aeSChuanqi Xu 7211934b3aeSChuanqi Xu inline std::unordered_set<void*> lived_coroutines; 7221934b3aeSChuanqi Xu // For all promise_type we want to record 7231934b3aeSChuanqi Xu class promise_type { 7241934b3aeSChuanqi Xu public: 7251934b3aeSChuanqi Xu promise_type() { 7261934b3aeSChuanqi Xu // Note to avoid data races 7271934b3aeSChuanqi Xu lived_coroutines.insert(std::coroutine_handle<promise_type>::from_promise(*this).address()); 7281934b3aeSChuanqi Xu } 7291934b3aeSChuanqi Xu ~promise_type() { 7301934b3aeSChuanqi Xu // Note to avoid data races 7311934b3aeSChuanqi Xu lived_coroutines.erase(std::coroutine_handle<promise_type>::from_promise(*this).address()); 7321934b3aeSChuanqi Xu } 7331934b3aeSChuanqi Xu }; 7341934b3aeSChuanqi Xu 7351934b3aeSChuanqi XuIn the above code snippet, we save the address of every lived coroutine in the 7361934b3aeSChuanqi Xu`lived_coroutines` `unordered_set`. As before, once we know the address of the 7371934b3aeSChuanqi Xucoroutine we can derive the function, `promise_type`, and other members of the 7381934b3aeSChuanqi Xuframe. Thus, we could print the list of lived coroutines from that collection. 7391934b3aeSChuanqi Xu 7401934b3aeSChuanqi XuPlease note that the above is expensive from a storage perspective, and requires 7411934b3aeSChuanqi Xusome level of locking (not pictured) on the collection to prevent data races. 742