0

I have a struct with an async method which I'd like to debug. I used gdb to set a breakpoint with a debug build. Here is how the code looks like when stopping at the async method Strct::async_method:

0x5555557f4a6a <bin::Strct::async_method+26>            mov    QWORD PTR [rsp+0x10],rsi
0x5555557f4a6f <bin::Strct::async_method+31>            mov    QWORD PTR [rsp+0x18],rdx
0x5555557f4a74 <bin::Strct::async_method+36>            mov    BYTE PTR [rsp+0x112],0x0
0x5555557f4a7c <bin::Strct::async_method+44>            lea    rsi,[rsp+0x10]
0x5555557f4a81 <bin::Strct::async_method+49>            mov    QWORD PTR [rsp+0x8],rax
0x5555557f4a86 <bin::Strct::async_method+54>            call   0x5555557e6970 <core::future::from_generator>

The code calls core::future::from_generator which is not what I'd like to debug. What is the proper way to suspend the execution of the async method body?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Some Name
  • 8,555
  • 5
  • 27
  • 77

1 Answers1

5

Let's use this as our MRE

use futures::executor; // 0.3.5

pub fn exercise() {
    executor::block_on(example());
}

#[inline(never)]
async fn example() {
    canary()
}

#[inline(never)]
fn canary() {}

If you view the assembly for this, you'll see how the compiler implements async functions. The async function returns a type implementing impl Future, which is powered under the hood by a generator:

playground::example:
    subq    $24, %rsp
    movb    $0, 16(%rsp)
    movzbl  16(%rsp), %edi
    callq   *core::future::from_generator@GOTPCREL(%rip)
    movb    %al, 23(%rsp)
    movb    23(%rsp), %al
    movb    %al, 8(%rsp)
    movb    8(%rsp), %al
    addq    $24, %rsp
    retq

The actual body of the async function is moved into the generator, which happens to use the name {{closure}}:

playground::example::{{closure}}:
;; Lots of instructions removed
    movq    playground::canary@GOTPCREL(%rip), %rcx
    callq   *%rcx
    jmp .LBB20_2
;; Even more removed

Thus, you can set a breakpoint on that generated function:

(lldb) br set -r '.*example.*closure.*'

(lldb) r
Process 28101 launched: '/tmp/f/target/debug/f' (x86_64)
Process 28101 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x0000000100000ab0 f`f::example::_$u7b$$u7b$closure$u7d$$u7d$::h2b30ad777e7395f3((null)=(pointer = 0x00007ffeefbff328), (null)=ResumeTy @ 0x00007ffeefbff070) at main.rs:8:20
   5    }
   6
   7    #[inline(never)]
-> 8    async fn example() {
   9        canary()
   10   }
   11
Target 0: (f) stopped.

You could also set a breakpoint on the desired line:

(lldb) breakpoint set --file /private/tmp/f/src/main.rs --line 9

(lldb) r
Process 28113 launched: '/tmp/f/target/debug/f' (x86_64)
Process 28113 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x0000000100000ad8 f`f::example::_$u7b$$u7b$closure$u7d$$u7d$::h2b30ad777e7395f3((null)=(pointer = 0x00007ffeefbff328), (null)=ResumeTy @ 0x00007ffeefbff070) at main.rs:9:5
   6
   7    #[inline(never)]
   8    async fn example() {
-> 9        canary()
   10   }
   11
   12   #[inline(never)]
Target 0: (f) stopped.

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366