master
py 423 lines 13.3 KB
Raw
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)