| 1 | # |
| 2 | # GDB debugging support |
| 3 | # |
| 4 | # Copyright 2012 Red Hat, Inc. and/or its affiliates |
| 5 | # |
| 6 | # Authors: |
| 7 | # Avi Kivity <avi@redhat.com> |
| 8 | # |
| 9 | # This work is licensed under the terms of the GNU GPL, version 2 |
| 10 | # or later. See the COPYING file in the top-level directory. |
| 11 | |
| 12 | import atexit |
| 13 | import gdb |
| 14 | import os |
| 15 | import pty |
| 16 | import re |
| 17 | import struct |
| 18 | import textwrap |
| 19 | |
| 20 | from collections import OrderedDict |
| 21 | from copy import deepcopy |
| 22 | |
| 23 | VOID_PTR = gdb.lookup_type('void').pointer() |
| 24 | |
| 25 | # Registers in the same order they're present in ELF coredump file. |
| 26 | # See asm/ptrace.h |
| 27 | PT_REGS = ['r15', 'r14', 'r13', 'r12', 'rbp', 'rbx', 'r11', 'r10', 'r9', |
| 28 | 'r8', 'rax', 'rcx', 'rdx', 'rsi', 'rdi', 'orig_rax', 'rip', 'cs', |
| 29 | 'eflags', 'rsp', 'ss'] |
| 30 | |
| 31 | coredump = None |
| 32 | |
| 33 | |
| 34 | class Coredump: |
| 35 | _ptregs_suff = '.ptregs' |
| 36 | |
| 37 | def __init__(self, coredump, executable): |
| 38 | atexit.register(self._cleanup) |
| 39 | |
| 40 | self.coredump = coredump |
| 41 | self.executable = executable |
| 42 | self._ptregs_blob = coredump + self._ptregs_suff |
| 43 | self._dirty = False |
| 44 | |
| 45 | with open(coredump, 'rb') as f: |
| 46 | while f.read(4) != b'CORE': |
| 47 | pass |
| 48 | gdb.write(f'core file {coredump}: found "CORE" at 0x{f.tell():x}\n') |
| 49 | |
| 50 | # Looking for struct elf_prstatus and pr_reg field in it (an array |
| 51 | # of general purpose registers). See sys/procfs.h. |
| 52 | |
| 53 | # lseek(f.fileno(), 4, SEEK_CUR): go to elf_prstatus |
| 54 | f.seek(4, 1) |
| 55 | |
| 56 | # lseek(f.fileno(), 112, SEEK_CUR): |
| 57 | # offsetof(struct elf_prstatus, pr_reg) |
| 58 | f.seek(112, 1) |
| 59 | |
| 60 | self._ptregs_offset = f.tell() |
| 61 | |
| 62 | # If binary blob with the name /path/to/coredump + '.ptregs' |
| 63 | # exists, that means proper cleanup didn't happen during previous |
| 64 | # GDB session with the same coredump, and registers in the dump |
| 65 | # itself might've remained patched. Thus we restore original |
| 66 | # registers values from this blob |
| 67 | if os.path.exists(self._ptregs_blob): |
| 68 | with open(self._ptregs_blob, 'rb') as b: |
| 69 | orig_ptregs_bytes = b.read() |
| 70 | self._dirty = True |
| 71 | else: |
| 72 | orig_ptregs_bytes = f.read(len(PT_REGS) * 8) |
| 73 | |
| 74 | values = struct.unpack(f"={len(PT_REGS)}q", orig_ptregs_bytes) |
| 75 | self._orig_ptregs = OrderedDict(zip(PT_REGS, values)) |
| 76 | |
| 77 | if not os.path.exists(self._ptregs_blob): |
| 78 | gdb.write(f'saving original pt_regs in {self._ptregs_blob}\n') |
| 79 | with open(self._ptregs_blob, 'wb') as b: |
| 80 | b.write(orig_ptregs_bytes) |
| 81 | |
| 82 | gdb.write('\n') |
| 83 | |
| 84 | def patch_regs(self, regs): |
| 85 | # Set dirty flag early on to make sure regs are restored upon cleanup |
| 86 | self._dirty = True |
| 87 | |
| 88 | gdb.write(f'patching core file {self.coredump}\n') |
| 89 | patched_ptregs = deepcopy(self._orig_ptregs) |
| 90 | int_regs = {k: int(v) for k, v in regs.items()} |
| 91 | patched_ptregs.update(int_regs) |
| 92 | |
| 93 | with open(self.coredump, 'ab') as f: |
| 94 | gdb.write(f'assume pt_regs at 0x{self._ptregs_offset:x}\n') |
| 95 | f.seek(self._ptregs_offset, 0) |
| 96 | gdb.write('writing regs:\n') |
| 97 | for reg in self._orig_ptregs.keys(): |
| 98 | if reg in int_regs: |
| 99 | gdb.write(f" {reg}: {int_regs[reg]:#16x}\n") |
| 100 | f.write(struct.pack(f"={len(PT_REGS)}q", *patched_ptregs.values())) |
| 101 | |
| 102 | gdb.write('\n') |
| 103 | |
| 104 | def restore_regs(self): |
| 105 | if not self._dirty: |
| 106 | return |
| 107 | |
| 108 | gdb.write(f'\nrestoring original regs in core file {self.coredump}\n') |
| 109 | with open(self.coredump, 'ab') as f: |
| 110 | gdb.write(f'assume pt_regs at 0x{self._ptregs_offset:x}\n') |
| 111 | f.seek(self._ptregs_offset, 0) |
| 112 | f.write(struct.pack(f"={len(PT_REGS)}q", |
| 113 | *self._orig_ptregs.values())) |
| 114 | |
| 115 | self._dirty = False |
| 116 | gdb.write('\n') |
| 117 | |
| 118 | def _cleanup(self): |
| 119 | if os.path.exists(self._ptregs_blob): |
| 120 | self.restore_regs() |
| 121 | gdb.write(f'\nremoving saved pt_regs file {self._ptregs_blob}\n') |
| 122 | os.unlink(self._ptregs_blob) |
| 123 | |
| 124 | |
| 125 | def pthread_self(): |
| 126 | '''Fetch the base address of TLS.''' |
| 127 | return gdb.parse_and_eval("$fs_base") |
| 128 | |
| 129 | def get_glibc_pointer_guard(): |
| 130 | '''Fetch glibc pointer guard value''' |
| 131 | fs_base = pthread_self() |
| 132 | return gdb.parse_and_eval('*(uint64_t*)((uint64_t)%s + 0x30)' % fs_base) |
| 133 | |
| 134 | def glibc_ptr_demangle(val, pointer_guard): |
| 135 | '''Undo effect of glibc's PTR_MANGLE()''' |
| 136 | return gdb.parse_and_eval('(((uint64_t)%s >> 0x11) | ((uint64_t)%s << (64 - 0x11))) ^ (uint64_t)%s' % (val, val, pointer_guard)) |
| 137 | |
| 138 | def get_jmpbuf_regs(jmpbuf): |
| 139 | JB_RBX = 0 |
| 140 | JB_RBP = 1 |
| 141 | JB_R12 = 2 |
| 142 | JB_R13 = 3 |
| 143 | JB_R14 = 4 |
| 144 | JB_R15 = 5 |
| 145 | JB_RSP = 6 |
| 146 | JB_PC = 7 |
| 147 | |
| 148 | pointer_guard = get_glibc_pointer_guard() |
| 149 | return {'rbx': jmpbuf[JB_RBX], |
| 150 | 'rbp': glibc_ptr_demangle(jmpbuf[JB_RBP], pointer_guard), |
| 151 | 'rsp': glibc_ptr_demangle(jmpbuf[JB_RSP], pointer_guard), |
| 152 | 'r12': jmpbuf[JB_R12], |
| 153 | 'r13': jmpbuf[JB_R13], |
| 154 | 'r14': jmpbuf[JB_R14], |
| 155 | 'r15': jmpbuf[JB_R15], |
| 156 | 'rip': glibc_ptr_demangle(jmpbuf[JB_PC], pointer_guard) } |
| 157 | |
| 158 | def symbol_lookup(addr): |
| 159 | # Example: "__clone3 + 44 in section .text of /lib64/libc.so.6" |
| 160 | result = gdb.execute(f"info symbol {hex(addr)}", to_string=True).strip() |
| 161 | try: |
| 162 | if "+" in result: |
| 163 | (func, result) = result.split(" + ") |
| 164 | (offset, result) = result.split(" in ") |
| 165 | else: |
| 166 | offset = "0" |
| 167 | (func, result) = result.split(" in ") |
| 168 | func_str = f"{func}<+{offset}> ()" |
| 169 | except: |
| 170 | return f"??? ({result})" |
| 171 | |
| 172 | # Example: Line 321 of "../util/coroutine-ucontext.c" starts at address |
| 173 | # 0x55cf3894d993 <qemu_coroutine_switch+99> and ends at 0x55cf3894d9ab |
| 174 | # <qemu_coroutine_switch+123>. |
| 175 | result = gdb.execute(f"info line *{hex(addr)}", to_string=True).strip() |
| 176 | if not result.startswith("Line "): |
| 177 | return func_str |
| 178 | result = result[5:] |
| 179 | |
| 180 | try: |
| 181 | result = result.split(" starts ")[0] |
| 182 | (line, path) = result.split(" of ") |
| 183 | path = path.replace("\"", "") |
| 184 | except: |
| 185 | return func_str |
| 186 | |
| 187 | return f"{func_str} at {path}:{line}" |
| 188 | |
| 189 | def run_with_pty(cmd): |
| 190 | # Create a PTY pair |
| 191 | master_fd, slave_fd = pty.openpty() |
| 192 | |
| 193 | pid = os.fork() |
| 194 | if pid == 0: # Child |
| 195 | os.close(master_fd) |
| 196 | # Attach stdin/stdout/stderr to the PTY slave side |
| 197 | os.dup2(slave_fd, 0) |
| 198 | os.dup2(slave_fd, 1) |
| 199 | os.dup2(slave_fd, 2) |
| 200 | os.close(slave_fd) |
| 201 | os.execvp("gdb", cmd) # Runs gdb and doesn't return |
| 202 | |
| 203 | # Parent |
| 204 | os.close(slave_fd) |
| 205 | |
| 206 | output = bytearray() |
| 207 | try: |
| 208 | while True: |
| 209 | data = os.read(master_fd, 65536) |
| 210 | if not data: |
| 211 | break |
| 212 | output.extend(data) |
| 213 | except OSError: # in case subprocess exits and we get EBADF on read() |
| 214 | pass |
| 215 | finally: |
| 216 | try: |
| 217 | os.close(master_fd) |
| 218 | except OSError: # in case we get EBADF on close() |
| 219 | pass |
| 220 | |
| 221 | # Wait for child to finish (reap zombie) |
| 222 | os.waitpid(pid, 0) |
| 223 | |
| 224 | return output.decode('utf-8') |
| 225 | |
| 226 | def dump_backtrace_patched(regs): |
| 227 | cmd = ['gdb', '-batch', |
| 228 | '-ex', 'set debuginfod enabled off', |
| 229 | '-ex', 'set complaints 0', |
| 230 | '-ex', 'set style enabled on', |
| 231 | '-ex', 'python print("----split----")', |
| 232 | '-ex', 'bt', coredump.executable, coredump.coredump] |
| 233 | |
| 234 | coredump.patch_regs(regs) |
| 235 | out = run_with_pty(cmd).split('----split----')[1] |
| 236 | gdb.write(out) |
| 237 | |
| 238 | def dump_backtrace(regs): |
| 239 | ''' |
| 240 | Backtrace dump with raw registers, mimic GDB command 'bt'. |
| 241 | ''' |
| 242 | # Here only rbp and rip that matter.. |
| 243 | rbp = regs['rbp'] |
| 244 | rip = regs['rip'] |
| 245 | i = 0 |
| 246 | |
| 247 | while rbp: |
| 248 | # For all return addresses on stack, we want to look up symbol/line |
| 249 | # on the CALL command, because the return address is the next |
| 250 | # instruction instead of the CALL. Here -1 would work for any |
| 251 | # sized CALL instruction. |
| 252 | print(f"#{i} {hex(rip)} in {symbol_lookup(rip if i == 0 else rip-1)}") |
| 253 | rip = gdb.parse_and_eval(f"*(uint64_t *)(uint64_t)({hex(rbp)} + 8)") |
| 254 | rbp = gdb.parse_and_eval(f"*(uint64_t *)(uint64_t)({hex(rbp)})") |
| 255 | i += 1 |
| 256 | |
| 257 | def dump_backtrace_live(regs): |
| 258 | ''' |
| 259 | Backtrace dump with gdb's 'bt' command, only usable in a live session. |
| 260 | ''' |
| 261 | old = dict() |
| 262 | |
| 263 | # remember current stack frame and select the topmost |
| 264 | # so that register modifications don't wreck it |
| 265 | selected_frame = gdb.selected_frame() |
| 266 | gdb.newest_frame().select() |
| 267 | |
| 268 | for i in regs: |
| 269 | old[i] = gdb.parse_and_eval('(uint64_t)$%s' % i) |
| 270 | |
| 271 | for i in regs: |
| 272 | gdb.execute('set $%s = %s' % (i, regs[i])) |
| 273 | |
| 274 | gdb.execute('bt') |
| 275 | |
| 276 | for i in regs: |
| 277 | gdb.execute('set $%s = %s' % (i, old[i])) |
| 278 | |
| 279 | selected_frame.select() |
| 280 | |
| 281 | def bt_jmpbuf(jmpbuf, detailed=False): |
| 282 | '''Backtrace a jmpbuf''' |
| 283 | regs = get_jmpbuf_regs(jmpbuf) |
| 284 | try: |
| 285 | # This reuses gdb's "bt" command, which can be slightly prettier |
| 286 | # but only works with live sessions. |
| 287 | dump_backtrace_live(regs) |
| 288 | except: |
| 289 | if detailed: |
| 290 | # Obtain detailed trace by patching regs in copied coredump |
| 291 | dump_backtrace_patched(regs) |
| 292 | else: |
| 293 | # If above doesn't work, fallback to poor man's unwind |
| 294 | dump_backtrace(regs) |
| 295 | |
| 296 | def co_cast(co): |
| 297 | return co.cast(gdb.lookup_type('CoroutineUContext').pointer()) |
| 298 | |
| 299 | def coroutine_to_jmpbuf(co): |
| 300 | coroutine_pointer = co_cast(co) |
| 301 | return coroutine_pointer['env']['__jmpbuf'] |
| 302 | |
| 303 | def init_coredump(): |
| 304 | global coredump |
| 305 | |
| 306 | files = gdb.execute('info files', False, True).split('\n') |
| 307 | |
| 308 | if not 'core dump' in files[1]: |
| 309 | return False |
| 310 | |
| 311 | core_path = re.search("`(.*)'", files[2]).group(1) |
| 312 | exec_path = re.match('^Symbols from "(.*)".$', files[0]).group(1) |
| 313 | |
| 314 | if coredump is None: |
| 315 | coredump = Coredump(core_path, exec_path) |
| 316 | |
| 317 | return True |
| 318 | |
| 319 | class CoroutineCommand(gdb.Command): |
| 320 | __doc__ = textwrap.dedent("""\ |
| 321 | Display coroutine backtrace |
| 322 | |
| 323 | Usage: qemu coroutine COROPTR [--detailed] |
| 324 | Show backtrace for a coroutine specified by COROPTR |
| 325 | |
| 326 | --detailed obtain detailed trace by copying coredump, patching |
| 327 | regs in it, and runing gdb subprocess to get |
| 328 | backtrace from the patched coredump |
| 329 | """) |
| 330 | |
| 331 | def __init__(self): |
| 332 | gdb.Command.__init__(self, 'qemu coroutine', gdb.COMMAND_DATA, |
| 333 | gdb.COMPLETE_NONE) |
| 334 | |
| 335 | def _usage(self): |
| 336 | gdb.write('usage: qemu coroutine <coroutine-pointer> [--detailed]\n') |
| 337 | return |
| 338 | |
| 339 | def invoke(self, arg, from_tty): |
| 340 | argv = gdb.string_to_argv(arg) |
| 341 | argc = len(argv) |
| 342 | if argc == 0 or argc > 2 or (argc == 2 and argv[1] != '--detailed'): |
| 343 | return self._usage() |
| 344 | detailed = True if argc == 2 else False |
| 345 | |
| 346 | is_coredump = init_coredump() |
| 347 | if detailed and not is_coredump: |
| 348 | gdb.write('--detailed is only valid when debugging core dumps\n') |
| 349 | return |
| 350 | |
| 351 | try: |
| 352 | bt_jmpbuf(coroutine_to_jmpbuf(gdb.parse_and_eval(argv[0])), |
| 353 | detailed=detailed) |
| 354 | finally: |
| 355 | coredump.restore_regs() |
| 356 | |
| 357 | class CoroutineBt(gdb.Command): |
| 358 | __doc__ = textwrap.dedent("""\ |
| 359 | Display backtrace including coroutine switches |
| 360 | |
| 361 | Usage: qemu bt [--detailed] |
| 362 | |
| 363 | --detailed obtain detailed trace by copying coredump, patching |
| 364 | regs in it, and runing gdb subprocess to get |
| 365 | backtrace from the patched coredump |
| 366 | """) |
| 367 | |
| 368 | def __init__(self): |
| 369 | gdb.Command.__init__(self, 'qemu bt', gdb.COMMAND_STACK, |
| 370 | gdb.COMPLETE_NONE) |
| 371 | |
| 372 | def _usage(self): |
| 373 | gdb.write('usage: qemu bt [--detailed]\n') |
| 374 | return |
| 375 | |
| 376 | def invoke(self, arg, from_tty): |
| 377 | argv = gdb.string_to_argv(arg) |
| 378 | argc = len(argv) |
| 379 | if argc > 1 or (argc == 1 and argv[0] != '--detailed'): |
| 380 | return self._usage() |
| 381 | detailed = True if argc == 1 else False |
| 382 | |
| 383 | is_coredump = init_coredump() |
| 384 | if detailed and not is_coredump: |
| 385 | gdb.write('--detailed is only valid when debugging core dumps\n') |
| 386 | return |
| 387 | |
| 388 | gdb.execute("bt") |
| 389 | |
| 390 | try: |
| 391 | # This only works with a live session |
| 392 | co_ptr = gdb.parse_and_eval("qemu_coroutine_self()") |
| 393 | except: |
| 394 | # Fallback to use hard-coded ucontext vars if it's coredump |
| 395 | co_ptr = gdb.parse_and_eval("co_tls_current") |
| 396 | |
| 397 | if co_ptr == False: |
| 398 | return |
| 399 | |
| 400 | try: |
| 401 | while True: |
| 402 | co = co_cast(co_ptr) |
| 403 | co_ptr = co["base"]["caller"] |
| 404 | if co_ptr == 0: |
| 405 | break |
| 406 | gdb.write("\nCoroutine at " + str(co_ptr) + ":\n") |
| 407 | bt_jmpbuf(coroutine_to_jmpbuf(co_ptr), detailed=detailed) |
| 408 | finally: |
| 409 | coredump.restore_regs() |
| 410 | |
| 411 | class CoroutineSPFunction(gdb.Function): |
| 412 | def __init__(self): |
| 413 | gdb.Function.__init__(self, 'qemu_coroutine_sp') |
| 414 | |
| 415 | def invoke(self, addr): |
| 416 | return get_jmpbuf_regs(coroutine_to_jmpbuf(addr))['rsp'].cast(VOID_PTR) |
| 417 | |
| 418 | class CoroutinePCFunction(gdb.Function): |
| 419 | def __init__(self): |
| 420 | gdb.Function.__init__(self, 'qemu_coroutine_pc') |
| 421 | |
| 422 | def invoke(self, addr): |
| 423 | return get_jmpbuf_regs(coroutine_to_jmpbuf(addr))['rip'].cast(VOID_PTR) |