Message ID | 20241211201739.1380222-4-peterx@redhat.com (mailing list archive) |
---|---|
State | New |
Headers | show |
Series | scripts/qemu-gdb: Make coroutine dumps to work with coredumps | expand |
On Wed, Dec 11, 2024 at 03:17:39PM -0500, Peter Xu wrote: > Dumping coroutines don't yet work with coredumps. Let's make it work. > > We still kept most of the old code because they can be either more > flexible, or prettier. Only add the fallbacks when they stop working. > > Currently the raw unwind is pretty ugly, but it works, like this: > > (gdb) qemu bt > Coroutine at 0x7fc474728748: It didn't get commited.. I forgot to indent. It looks like this: (gdb) qemu bt #0 process_incoming_migration_co (opaque=0x0) at ../migration/migration.c:788 #1 0x000055cf3894d4d9 in coroutine_trampoline (i0=1565638480, i1=21967) at ../util/coroutine-ucontext.c:175 #2 0x00007fc481f72f40 in ??? () at /lib64/libc.so.6 #3 0x00007ffc0c74a520 in ??? () #4 0x0000000000000000 in ??? () Coroutine at 0x7fc474728748: #0 qemu_coroutine_switch + 120 #1 qemu_aio_coroutine_enter + 357 #2 qemu_coroutine_enter + 35 #3 migration_incoming_process + 44 #4 migration_ioc_process_incoming + 491 #5 migration_channel_process_incoming + 146 #6 socket_accept_incoming_migration + 119 #7 qio_net_listener_channel_func + 132 #8 qio_channel_fd_source_dispatch + 79 #9 g_main_context_dispatch_unlocked.lto_priv + 316 #10 g_main_context_dispatch + 37 #11 glib_pollfds_poll + 91 #12 os_host_main_loop_wait + 129 #13 main_loop_wait + 204 #14 qemu_main_loop + 42 #15 qemu_default_main + 20 #16 main + 41 #17 __libc_start_call_main + 120 #18 __libc_start_main_impl + 139 > > Signed-off-by: Peter Xu <peterx@redhat.com> > --- > scripts/qemugdb/coroutine.py | 50 +++++++++++++++++++++++++++++++----- > 1 file changed, 43 insertions(+), 7 deletions(-) > > diff --git a/scripts/qemugdb/coroutine.py b/scripts/qemugdb/coroutine.py > index 20f76ed37b..b29ee16205 100644 > --- a/scripts/qemugdb/coroutine.py > +++ b/scripts/qemugdb/coroutine.py > @@ -46,9 +46,30 @@ def get_jmpbuf_regs(jmpbuf): > 'r15': jmpbuf[JB_R15], > 'rip': glibc_ptr_demangle(jmpbuf[JB_PC], pointer_guard) } > > -def bt_jmpbuf(jmpbuf): > - '''Backtrace a jmpbuf''' > - regs = get_jmpbuf_regs(jmpbuf) > +def symbol_lookup(addr): > + # Example: "__clone3 + 44 in section .text of /lib64/libc.so.6" > + result = gdb.execute(f"info symbol {hex(addr)}", to_string=True).strip() > + return result.split(" in ")[0] > + > +def dump_backtrace(regs): > + ''' > + Backtrace dump with raw registers, mimic GDB command 'bt'. > + ''' > + # Here only rbp and rip that matter.. > + rbp = regs['rbp'] > + rip = regs['rip'] > + i = 0 > + > + while rbp: > + print(f"#{i}\t{symbol_lookup(rip)}") > + rip = gdb.parse_and_eval(f"*(uint64_t *)(uint64_t)({hex(rbp)} + 8)") > + rbp = gdb.parse_and_eval(f"*(uint64_t *)(uint64_t)({hex(rbp)})") > + i += 1 > + > +def dump_backtrace_live(regs): > + ''' > + Backtrace dump with gdb's 'bt' command, only usable in a live session. > + ''' > old = dict() > > # remember current stack frame and select the topmost > @@ -69,6 +90,17 @@ def bt_jmpbuf(jmpbuf): > > selected_frame.select() > > +def bt_jmpbuf(jmpbuf): > + '''Backtrace a jmpbuf''' > + regs = get_jmpbuf_regs(jmpbuf) > + try: > + # This reuses gdb's "bt" command, which can be slightly prettier > + # but only works with live sessions. > + dump_backtrace_live(regs) > + except: > + # If above doesn't work, fallback to poor man's unwind > + dump_backtrace(regs) > + > def co_cast(co): > return co.cast(gdb.lookup_type('CoroutineUContext').pointer()) > > @@ -101,10 +133,14 @@ def invoke(self, arg, from_tty): > > gdb.execute("bt") > > - if gdb.parse_and_eval("qemu_in_coroutine()") == False: > - return > - > - co_ptr = gdb.parse_and_eval("qemu_coroutine_self()") > + try: > + # This only works with a live session > + co_ptr = gdb.parse_and_eval("qemu_coroutine_self()") > + if co_ptr == False: > + return > + except: > + # Fallback to use hard-coded ucontext vars if it's coredump > + co_ptr = gdb.parse_and_eval("co_tls_current") > > while True: > co = co_cast(co_ptr) > -- > 2.47.0 >
diff --git a/scripts/qemugdb/coroutine.py b/scripts/qemugdb/coroutine.py index 20f76ed37b..b29ee16205 100644 --- a/scripts/qemugdb/coroutine.py +++ b/scripts/qemugdb/coroutine.py @@ -46,9 +46,30 @@ def get_jmpbuf_regs(jmpbuf): 'r15': jmpbuf[JB_R15], 'rip': glibc_ptr_demangle(jmpbuf[JB_PC], pointer_guard) } -def bt_jmpbuf(jmpbuf): - '''Backtrace a jmpbuf''' - regs = get_jmpbuf_regs(jmpbuf) +def symbol_lookup(addr): + # Example: "__clone3 + 44 in section .text of /lib64/libc.so.6" + result = gdb.execute(f"info symbol {hex(addr)}", to_string=True).strip() + return result.split(" in ")[0] + +def dump_backtrace(regs): + ''' + Backtrace dump with raw registers, mimic GDB command 'bt'. + ''' + # Here only rbp and rip that matter.. + rbp = regs['rbp'] + rip = regs['rip'] + i = 0 + + while rbp: + print(f"#{i}\t{symbol_lookup(rip)}") + rip = gdb.parse_and_eval(f"*(uint64_t *)(uint64_t)({hex(rbp)} + 8)") + rbp = gdb.parse_and_eval(f"*(uint64_t *)(uint64_t)({hex(rbp)})") + i += 1 + +def dump_backtrace_live(regs): + ''' + Backtrace dump with gdb's 'bt' command, only usable in a live session. + ''' old = dict() # remember current stack frame and select the topmost @@ -69,6 +90,17 @@ def bt_jmpbuf(jmpbuf): selected_frame.select() +def bt_jmpbuf(jmpbuf): + '''Backtrace a jmpbuf''' + regs = get_jmpbuf_regs(jmpbuf) + try: + # This reuses gdb's "bt" command, which can be slightly prettier + # but only works with live sessions. + dump_backtrace_live(regs) + except: + # If above doesn't work, fallback to poor man's unwind + dump_backtrace(regs) + def co_cast(co): return co.cast(gdb.lookup_type('CoroutineUContext').pointer()) @@ -101,10 +133,14 @@ def invoke(self, arg, from_tty): gdb.execute("bt") - if gdb.parse_and_eval("qemu_in_coroutine()") == False: - return - - co_ptr = gdb.parse_and_eval("qemu_coroutine_self()") + try: + # This only works with a live session + co_ptr = gdb.parse_and_eval("qemu_coroutine_self()") + if co_ptr == False: + return + except: + # Fallback to use hard-coded ucontext vars if it's coredump + co_ptr = gdb.parse_and_eval("co_tls_current") while True: co = co_cast(co_ptr)
Dumping coroutines don't yet work with coredumps. Let's make it work. We still kept most of the old code because they can be either more flexible, or prettier. Only add the fallbacks when they stop working. Currently the raw unwind is pretty ugly, but it works, like this: (gdb) qemu bt Coroutine at 0x7fc474728748: Signed-off-by: Peter Xu <peterx@redhat.com> --- scripts/qemugdb/coroutine.py | 50 +++++++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 7 deletions(-)