Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

External backtraces for async tasks

Open
tmandry opened this issue Jun 20, 2020 · 2 comments
Open

External backtraces for async tasks #73524

tmandry opened this issue Jun 20, 2020 · 2 comments
Labels
A-async-await AsyncAwait-Triaged

Comments

@tmandry
Copy link
Contributor

tmandry commented Jun 20, 2020

It would be nice if debuggers supported printing a backtrace of await points within an async stack machine. In particular, if I have a reference to an async task, my debugger should be able to read the state of all (nested) generators compiled into that object, and turn that into something that looks like a stack trace (except, of course, there is no actual stack).

See #73522 for a more general discussion of debugger support for async/await.

Prior art

Javascript has async stack traces, but AIUI, it is really solving a different problem. In Javascript, awaiting something is the equivalent of spawning an async task in Rust and then awaiting completion of that task: if you set and hit breakpoint inside an async function, you won't have any context on what's calling that function from a "normal" stack backtrace.

This is not the case for Rust: the stack trace from inside a poll function shows you all the futures that polled you inside your task. (However, such a technique would be useful for seeing across task boundaries.)

The problem we are really trying to solve in this issue is an external backtrace: I should be able to peer into the state of an async task that isn't running and inspect the "stack" of Futures in it (both async fns and hand-rolled futures, ideally).

Trees, not stacks

A complication with the backtrace analogy is that in general, we are dealing with trees of futures (think select!()), not stacks. This means that in addition to making sure the debugger has all the information it needs, we'll need to experiment with different ways of presenting that information in the various debugging environments. I hope some prior art will be informative here.

Implementation history

@tmandry tmandry added the A-async-await label Jun 20, 2020
RalfJung added a commit to RalfJung/rust that referenced this issue Jun 20, 2020
Emit line info for generator variants

Debuggers should be able to read a generator / async fn state machine and show the line it's suspended at. Eventually, this could grow into an "async stack trace" feature of sorts. While no debugger support this for Rust today, this PR adds the debuginfo necessary for that support to exist.

[This gist](https://gist.github.com/tmandry/6d7004fa008684f76809208847459f9b) shows the resulting debuginfo for a simple example. Here's a snippet:

```
0x00000986:           DW_TAG_variant
                        DW_AT_discr_value       (0x03)

0x00000988:             DW_TAG_member
                          DW_AT_name    ("3")
                          DW_AT_type    (0x000009bc "Suspend0")
                          DW_AT_decl_file       ("/home/tmandry/code/playground/generator-simple.rs")
                          DW_AT_decl_line       (6)
                          DW_AT_alignment       (8)
                          DW_AT_data_member_location    (0x00)
```

The file and line have been added here. The line currently points to the beginning of the statement containing the yield (or await), because that's what the MIR source info points to for the yield terminator. (We may want to point to the yield or await line specifically, but that can be done independently of this change.)

Debuggers don't know how to use this kind of info yet. However, we're hoping to experiment with adding such support to Fuchsia's debugger. It would be exciting if someone were interested in adding similar to support to gdb/lldb.

r? @oli-obk
cc @eddyb @jonas-schievink

Part of rust-lang#73524.
@tmandry tmandry added the AsyncAwait-Triaged label Jun 23, 2020
Manishearth added a commit to Manishearth/rust that referenced this issue Jun 26, 2020
Emit line info for generator variants

Debuggers should be able to read a generator / async fn state machine and show the line it's suspended at. Eventually, this could grow into an "async stack trace" feature of sorts. While no debugger support this for Rust today, this PR adds the debuginfo necessary for that support to exist.

[This gist](https://gist.github.com/tmandry/6d7004fa008684f76809208847459f9b) shows the resulting debuginfo for a simple example. Here's a snippet:

```
0x00000986:           DW_TAG_variant
                        DW_AT_discr_value       (0x03)

0x00000988:             DW_TAG_member
                          DW_AT_name    ("3")
                          DW_AT_type    (0x000009bc "Suspend0")
                          DW_AT_decl_file       ("/home/tmandry/code/playground/generator-simple.rs")
                          DW_AT_decl_line       (6)
                          DW_AT_alignment       (8)
                          DW_AT_data_member_location    (0x00)
```

The file and line have been added here. The line currently points to the beginning of the statement containing the yield (or await), because that's what the MIR source info points to for the yield terminator. (We may want to point to the yield or await line specifically, but that can be done independently of this change.)

Debuggers don't know how to use this kind of info yet. However, we're hoping to experiment with adding such support to Fuchsia's debugger. It would be exciting if someone were interested in adding similar to support to gdb/lldb.

r? @oli-obk
cc @eddyb @jonas-schievink

Part of rust-lang#73524.
Manishearth added a commit to Manishearth/rust that referenced this issue Jun 26, 2020
Emit line info for generator variants

Debuggers should be able to read a generator / async fn state machine and show the line it's suspended at. Eventually, this could grow into an "async stack trace" feature of sorts. While no debugger support this for Rust today, this PR adds the debuginfo necessary for that support to exist.

[This gist](https://gist.github.com/tmandry/6d7004fa008684f76809208847459f9b) shows the resulting debuginfo for a simple example. Here's a snippet:

```
0x00000986:           DW_TAG_variant
                        DW_AT_discr_value       (0x03)

0x00000988:             DW_TAG_member
                          DW_AT_name    ("3")
                          DW_AT_type    (0x000009bc "Suspend0")
                          DW_AT_decl_file       ("/home/tmandry/code/playground/generator-simple.rs")
                          DW_AT_decl_line       (6)
                          DW_AT_alignment       (8)
                          DW_AT_data_member_location    (0x00)
```

The file and line have been added here. The line currently points to the beginning of the statement containing the yield (or await), because that's what the MIR source info points to for the yield terminator. (We may want to point to the yield or await line specifically, but that can be done independently of this change.)

Debuggers don't know how to use this kind of info yet. However, we're hoping to experiment with adding such support to Fuchsia's debugger. It would be exciting if someone were interested in adding similar to support to gdb/lldb.

r? @oli-obk
cc @eddyb @jonas-schievink

Part of rust-lang#73524.
@tmandry
Copy link
Contributor Author

tmandry commented Jan 30, 2021

Another missing piece: Debuggers need the ability to identify the local variable currently being awaited. This could be as simple as a special symbol name (something that can't be spelled in normal Rust, like $awaitee). And/or maybe there's a special designation we can make on the local in DWARF.

@yoshuawuyts
Copy link
Member

yoshuawuyts commented Feb 1, 2021

My understanding from this thread is that if using Apple's libdispatch as an executor, it's possible to encode the dispatch stack trace as part of the executor. This could be useful for macOS/iOS, albeit a bit specific. Though probably not quite as good or general-purpose a DWARF approach would be.

On Windows I've found that MSVC provides a built-in way to debug multi-threaded code (ref). If we can find out which symbols they're using to instrument their libraries, perhaps we can employ the same mechanism for our builds too. I would imagine this one to be a bit more similar to the DWARF proposal.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-async-await AsyncAwait-Triaged
Projects
None yet
Development

No branches or pull requests

2 participants