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

Async Rust support #37

Open
tyt2y3 opened this issue Jan 24, 2024 · 2 comments
Open

Async Rust support #37

tyt2y3 opened this issue Jan 24, 2024 · 2 comments

Comments

@tyt2y3
Copy link
Member

tyt2y3 commented Jan 24, 2024

How can we support debugging async Rust? As it is basically impossible to use a step debugger.

First off, the background. Async Rust has a runtime. The runtime manages a number of async tasks, called Future. Each Future is a state machine (the complex part is it is hierarchical), and it can only progress by having someone polling it. From an instruction point of view, each async function is a block of code ("closure") that can be reentered an unknown number of times. There is a context associated with this future, and our goal is to track the lifecycle of each Future, from create to drop, and every time it makes progress. Every await point is a yield point, meaning the closure returns, and the state machine may or may not have a state transition.

Static analysis already tells us whether a function is async. (it is actually quite tricky to derive this from looking at the assembly).

I think we can model the event stream in FireDBG just like regular functions, but with an additional async context pointer. At any given point in time, the pointer should uniquely identifies a Future. But we also need to hook into the Future lifecycle and record the "async context create" and "async context destroy" events. The Pin semantic ensures that once a Future is being polled, it stays in place in memory. Then we should have enough information to reconstruct an async timeline. Hierarchical async functions shares the same async context, so they can be uniquely identified by (async context, function address).

Constructing a call tree requires parent-child relation. We need to identify the "true" parent of an async function on runtime.

async fn func_a() {
    future::all([func_b, func_c]).await;
}

We have to be aware of b, c both being children of a, instead of a -> b -> c if we naively look at the sequence of events. At least in the above case, we can reconstruct the async call stack by looking at the real stack trace:

#0 func_b/c
#1 poll*
#2 tokio
#3 tokio
#4 func_a
#5 poll*
...
#9 main

It should be doable to capture the parameters of an async function on first call, but I couldn't think of a way to capture the return value (yet).

Reference: https://fitzgeraldnick.com/2019/08/27/async-stacks-in-rust.html

@tyt2y3
Copy link
Member Author

tyt2y3 commented Apr 20, 2024

Screenshot 2024-04-20 at 4 58 12 PM

@Milo123459
Copy link

Woo, awesome work! Does this mean we are getting closer to seeing this in a release version of FireDBG?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants