def co_update_device_tree(qemu_exec, src_path, arch_name, root): dic = create_dwarf_cache(qemu_exec) gvl_adptr = GitLineVersionAdapter(src_path) qomtr = QOMTreeReverser(dic, interrupt=True, verbose=True, line_adapter=gvl_adptr) port = find_free_port(4321) qemu_debug_addr = "localhost:%u" % port Popen(["gdbserver", qemu_debug_addr, qemu_exec]) if not wait_for_tcp_port(port): raise RuntimeError("gdbserver does not listen %u" % port) yield True qemu_debugger = AMD64(str(port), noack=True) rt = Runtime(qemu_debugger, dic) qomtr.init_runtime(rt) yield rt.co_run_target() device_subtree = qomtr.tree.name2type.get("device", None) if device_subtree is None: raise RuntimeError( 'No "device" QOM subtree. Did you forget to pass ' + '"--extra-cflags=-no-pie" and/or "--disable-pie" to `configure`?') yield co_fill_children(device_subtree, root, arch_name)
def reset(self, srcfile, elffile): self.srcfile = srcfile self.elffile = elffile self.elf = InMemoryELFFile(elffile) di = self.elf.get_dwarf_info() dic = DWARFInfoCache(di, symtab=self.elf.get_section_by_name(".symtab")) if dic.aranges is None: dic.account_all_subprograms() self.rt = Runtime(self.rsp, dic) self.addr2line = {} self.ch_line2var = defaultdict(list) self.chc_line2var = defaultdict(list)
class DebugSession(object): """ This class manages debugging session """ def __init__(self, target, srcfile, port, elffile, queue, verbose): super(DebugSession, self).__init__() self.rsp = target(port, verbose=verbose) self.port = port self.queue = queue self.reset(srcfile, elffile) self.session_type = None def reset(self, srcfile, elffile): self.srcfile = srcfile self.elffile = elffile self.elf = InMemoryELFFile(elffile) di = self.elf.get_dwarf_info() dic = DWARFInfoCache(di, symtab=self.elf.get_section_by_name(".symtab")) if dic.aranges is None: dic.account_all_subprograms() self.rt = Runtime(self.rsp, dic) self.addr2line = {} self.ch_line2var = defaultdict(list) self.chc_line2var = defaultdict(list) def set_br_by_line(self, lineno, cb): line_map = self.rt.dic.find_line_map(bstr(basename(self.srcfile))) line_descs = line_map[lineno] for desc in line_descs: # TODO: set a breakpoint at one address by line number? # if desc.state.is_stmt: addr = self.rt.target.reg_fmt % desc.state.address self.addr2line[addr] = lineno self.rt.add_br(addr, cb) # break def _execute_debug_comment(self): lineno = 1 with open(self.srcfile, 'r') as f: re_comment = compile("^.*//\$(.+)$") for line in f: mi = re_comment.match(line) if mi: glob = DebugCommandExecutor(locals(), lineno) exec(mi.group(1), glob) lineno += 1 @property def _var_size(self): re_size = compile("^.+_(?:u?(\d+))_.+$") size_str = re_size.match(basename(self.srcfile)).group(1) return int(size_str) // 8 def _dump_var(self, addr, lineno, var_names): dump = (self.session_type, self.srcfile, dict(elf=self.elffile, addr=addr, lineno=lineno, vars=dict( map(lambda x: (x, self.rt[x].fetch(self._var_size)), var_names if var_names else self.rt)), regs=self.rt.target.regs)) if self.rt.target.verbose: print(dump[2].values()) self.queue.put(dump) def _dump(self, addr, lineno): dump = (self.session_type, self.srcfile, dict(elf=self.elffile, addr=addr, lineno=lineno, regs=self.rt.target.regs)) if self.rt.target.verbose: print(dump[2].values()) self.queue.put(dump) # debugging callbacks def check_cb(self): addr = self.rt.target.regs[self.rt.target.pc_reg] lineno = self.addr2line[addr] self._dump(addr, lineno) self.rt.remove_br(addr, self.check_cb) def cycle_check_cb(self): addr = self.rt.target.regs[self.rt.target.pc_reg] lineno = self.addr2line[addr] self._dump(addr, lineno) def check_vars_cb(self): addr = self.rt.target.regs[self.rt.target.pc_reg] lineno = self.addr2line[addr] var_names = self.ch_line2var.get(lineno) self._dump_var(addr, lineno, var_names) self.rt.remove_br(addr, self.check_vars_cb) def cycle_check_vars_cb(self): addr = self.rt.target.regs[self.rt.target.pc_reg] lineno = self.addr2line[addr] var_names = self.chc_line2var.get(lineno) self._dump_var(addr, lineno, var_names) def finish_cb(self): addr = self.rt.target.regs[self.rt.target.pc_reg] self.rt.remove_br(addr, self.finish_cb) for br in list(self.rt.target.br): self.rt.target.del_br(br) self.rt.target.exit = True # end debugging callbacks def kill(self): self.rt.target.send(b'k') def detach(self): self.rt.target.send(b'D') def port_close(self): self.rt.target.port.close()
def main(): ap = QArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter, description="QEMU runtime introspection tool") ap.add_argument("-q", dest="qsrc", help="QEMU src directory.") ap.add_argument( "-c", "--connect", nargs="?", metavar="HOST", const="127.0.0.1", # default if `-c` is given without a value # Suppress reasons: # 1. do not print incorrect default in help by # `ArgumentDefaultsHelpFormatter` (correct is `const`) # 2. do not add the attribute to parsed args if the arg is missed default=SUPPRESS, help="connect to existing gdbstub (default: %(const)s)") ap.add_argument("-p", "--port", type=int, metavar="PORT", default=4321, help="start search for unused port from this number") ap.add_argument( "qarg", nargs="+", help="QEMU executable and arguments to it. Prefix them with `--`.") args = ap.parse_args() # executable qemu_cmd_args = args.qarg # src directory qemu_src_dir = args.qsrc # debug info qemu_debug = qemu_cmd_args[0] elf = InMemoryELFFile(qemu_debug) if not elf.has_dwarf_info(): stderr("%s does not have DWARF info. Provide a debug QEMU build\n" % (qemu_debug)) return -1 di = elf.get_dwarf_info() if not di.debug_pubnames_sec: print("%s does not contain .debug_pubtypes section. Provide" " -gpubnames flag to the compiler" % qemu_debug) dic = DWARFInfoCache(di, symtab=elf.get_section_by_name(".symtab")) if qemu_src_dir: gvl_adptr = GitLineVersionAdapter(qemu_src_dir) else: gvl_adptr = None qomtr = QOMTreeReverser(dic, interrupt=False, verbose=True, line_adapter=gvl_adptr) if "-i386" in qemu_debug or "-x86_64" in qemu_debug: MWClass = PCMachineWatcher else: MWClass = MachineWatcher mw = MWClass(dic, qomtr.tree, interrupt=True, line_adapter=gvl_adptr) proj = GUIProject() pht = GUIProjectHistoryTracker(proj, proj.history) MachineReverser(mw, pht) try: qemu_debug_addr_fmt = args.connect + ":%u" except AttributeError: # no -c/--connect option # auto select free port for gdb-server port = find_free_port(args.port) qemu_debug_addr = "localhost:%u" % port qemu_proc = Popen(["gdbserver", qemu_debug_addr] + qemu_cmd_args) else: port = args.port qemu_debug_addr = qemu_debug_addr_fmt % port qemu_proc = None if not wait_for_tcp_port(port): raise RuntimeError("gdbserver does not listen %u" % port) qemu_debugger = AMD64(str(port), noack=True) rt = Runtime(qemu_debugger, dic) qomtr.init_runtime(rt) mw.init_runtime(rt) # Because pyrsp (with machine reconstruction suite) works in a separate # thread, tracker's "changed" notifications are racing with GUI. So, GUI # must not watch those notifications. To maintain GUI consistency # other project and tracker are created with same history. The GUI is # watching this second tracker. A special coroutine working in GUI thread # will poll first (master) tracker position and adjust second (slave) # tracker updating the GUI without races. proj2 = GUIProject() # different context (project) but same history slave_pht = GUIProjectHistoryTracker(proj2, proj.history) def co_syncronizer(): while True: if slave_pht.pos != pht.pos: yield True slave_pht.do() else: yield False tk = QEmuWatcherGUI(slave_pht, rt) tk.task_manager.enqueue(co_syncronizer()) tk.geometry("1024x1024") tk.mainloop() qomtr.to_file("qom-by-q.i.dot") if qemu_proc is not None: qemu_proc.wait() if gvl_adptr is not None: gvl_adptr.cm.store_cache()