class RRInstance(object): def __init__(self, description, rr_replay, source_pane): self.rr_replay = rr_replay self.description = description self.spawn_cmd = "{} replay {}".format(shlex.quote(cli_args.rr), shlex.quote(rr_replay)) self.source_pane = source_pane self.breakpoints = {} self.watches_set = 0 self.instr_to_checkpoint = sorteddict() def __repr__(self): return "RRInstance({!r})".format(self.description) @cached_property def arch(self): rr_ps = check_output(['rr', 'ps', self.rr_replay]) qemu_regex = r"qemu-system-({})".format("|".join( SUPPORTED_ARCHS.keys())) re_result = re.search(qemu_regex, rr_ps) if not re_result: raise RuntimeError("Unsupported architecture!") return SUPPORTED_ARCHS[re_result.group(1)] # Runs in child process. def sendline(self, msg): check_call(['tmux', 'send-keys', '-t', self.pane, '-l', msg]) check_call(['tmux', 'send-keys', '-t', self.pane, 'ENTER']) # Runs in child process. def kill(self): check_call(['tmux', 'kill-pane', '-t', self.pane]) def __enter__(self): self.tempdir_obj = TempDir() tempdir = self.tempdir_obj.__enter__() logfile = join(tempdir, self.description + "out") os.mkfifo(logfile) bash_command = "{} 2>&1 | tee -i --output-error=warn {} | tee -i --output-error=warn {}_log.txt".format( self.spawn_cmd, shlex.quote(logfile), self.description) self.pane = check_output([ 'tmux', 'split-window', '-hdP', '-F', '#{pane_id}', '-t', pane, 'bash', '-c', bash_command ]).strip() self.proc = Expect(os.open(logfile, os.O_RDONLY | os.O_NONBLOCK), quiet=True) try: self.proc.expect("(rr) ", timeout=3) except TimeoutExpired: print(self.proc.sofar) raise return self def __exit__(self, exc_type, exc_value, traceback): if exc_type: self.kill() self.tempdir_obj.__exit__(exc_type, exc_value, traceback) def gdb(self, *args, **kwargs): timeout = kwargs.get('timeout', None) cmd = " ".join(map(str, args)) print("(rr-{}) {}".format(self.description, cmd)) sys.stdout.flush() expect_prompt = kwargs.get("expect_prompt", "(rr) ") while True: try: os.read(self.proc.fd, 1024) except OSError as e: if e.errno in [EAGAIN, EWOULDBLOCK]: break else: raise self.sendline(cmd) try: output = self.proc.expect(expect_prompt, timeout=timeout) except TimeoutExpired: print(self.proc.sofar) print("EXCEPTION!") sys.stdout.flush() if output.endswith(expect_prompt): output = output[:-len(expect_prompt)] if output.startswith(cmd): output = output[len(cmd):] return output.strip() def quit(self): self.gdb("set confirm off") self.sendline("quit") def gdb_int_re(self, result_re, *args): result = self.gdb(*args) return re_search_int(result_re, result) def breakpoint(self, break_arg): bp_num = self.gdb_int_re(r"Breakpoint ([0-9]+) at", "break", break_arg) self.breakpoints[break_arg] = bp_num def disable_all(self): self.gdb("disable") self.watches_set = 0 def enable(self, break_arg): self.gdb("enable", self.breakpoints[break_arg]) def enable_only(self, *breaks): self.disable_all() for break_arg in breaks: self.enable(break_arg) def condition(self, break_arg, cond): self.gdb("condition", self.breakpoints[break_arg], cond) def display(self, cmd): self.gdb("display", cmd) def checkpoint(self): return self.gdb_int_re(r"Checkpoint ([0-9]+) at", "checkpoint") def restart(self, checkpoint): self.disable_all() self.gdb("restart", checkpoint) def restart_instr(self, instr): self.restart(self.instr_to_checkpoint[instr]) def get_value(self, expr): return self.gdb_int_re(r"\$[0-9]+ = ([0-9]+)", "print/u", expr) def instr_count(self): return self.get_value("cpus->tqh_first->rr_guest_instr_count") @cached_property def instr_count_ptr(self): return self.get_value("&cpus->tqh_first->rr_guest_instr_count") def condition_instr(self, break_arg, op, instr): self.condition( break_arg, "*(uint64_t *){} {} {}".format(self.instr_count_ptr, op, instr)) def set_breakpoint_commands(self, break_num): self.gdb("commands", break_num, expect_prompt=">") # self.gdb("p/u cpus->tqh_first->rr_guest_instr_count", expect_prompt = ">") self.gdb("call target_disas(stdout, cpu, tb->pc, tb->size, 0)", expect_prompt=">") self.gdb("end") def display_commands(self): self.display("cpus->tqh_first->rr_guest_instr_count") self.display("cpus->tqh_first->exception_index") self.display("cpus->tqh_first->exit_request") self.gdb("set $env = ((CPUPPCState*) cpus->tqh_first->env_ptr)") self.display("$env->pending_interrupts") @cached_property def ram_ptr(self): return self.get_value( "memory_region_find(" + "get_system_memory(), 0x2000000, 1).mr->ram_block.host") def crc32_ram(self, low, size): step = 1 << 31 if size > (1 << 31) else size crc32s = 0 for start in range(low, low + size, step): crc32s ^= self.get_value("crc32(0, {} + {}, {})".format( hex(self.ram_ptr), hex(start), hex(step))) return crc32s @cached_property def ram_size(self): return self.get_value('ram_size') @cached_property def reg_size(self): return self.get_value("sizeof (({}*)0)->{}[0]".format( self.arch.cpu_state_name, self.arch.reg_name)) @cached_property def num_regs(self): return self.get_value("sizeof (({}*)0)->{}".format( self.arch.cpu_state_name, self.arch.reg_name)) // self.reg_size def env_value(self, name): return self.get_value("(({}*)cpus->tqh_first->env_ptr)->{}".format( self.arch.cpu_state_name, name)) def env_ptr(self, name): return self.get_value("&(({}*)cpus->tqh_first->env_ptr)->{}".format( self.arch.cpu_state_name, name)) def checksum(self): # NB: Only run when you are at a breakpoint in CPU thread! memory = self.crc32_ram(0, self.ram_size) regs = self.get_value("rr_checksum_regs()") return (memory, regs) def when(self): return self.gdb_int_re(r"Current event: ([0-9]+)", "when") def cont(self): return self.gdb("continue", timeout=None) def reverse_cont(self): return self.gdb("reverse-continue", timeout=None) # x86 debug registers can only watch 4 locations of 8 bytes. # we need to make sure to enforce that. # returns true if can set more watchpoints. false if we're full up. def watch_addr(self, addr, size): assert size in [1, 2, 4, 8] bits = size * 8 num = self.gdb_int_re(r"Hardware watchpoint ([0-9]+):", "watch", "*(uint{}_t *)0x{:x}".format(bits, addr)) self.watches_set += 1 if self.watches_set > 4: print() print( "WARNING: Too much divergence! Not watching some diverged points." ) print("(watchpoints are full...)") print() return num def watch(self, watchpoint): return self.watch_addr(*watchpoint.render(self)) def record_instr_checkpoint(self): instr_count = self.instr_count() if instr_count not in self.instr_to_checkpoint: self.instr_to_checkpoint[instr_count] = self.checkpoint() return self.instr_to_checkpoint[instr_count] # Get as close to instr as possible. def goto_rough(self, target_instr): print("Moving", self, "to instr", target_instr) current_instr = self.instr_count() if target_instr in self.instr_to_checkpoint: run_instr = target_instr else: index = self.instr_to_checkpoint.keys().bisect_left( target_instr) - 1 run_instr = self.instr_to_checkpoint.keys()[index] if current_instr > target_instr or current_instr < run_instr: self.restart(self.instr_to_checkpoint[run_instr]) # We should have now guaranteed that both will be in [run_instr, target_instr]. # Now run them forwards to as close to target_instr as we can get. # debug_counter fires every 128k instrs, so move to last debug_counter # before desired instr count. run_instr = target_instr - DEBUG_COUNTER_PERIOD current_instr = self.instr_count() if current_instr < run_instr: print("Moving from {} to {} below {}".format( current_instr, run_instr, target_instr)) self.enable_only("debug_counter") self.condition_instr("debug_counter", ">=", run_instr) self.cont() current_instr = self.instr_count() # unfortunately, we might have gone too far above. move back one # debug_counter fire if necessary. if current_instr > target_instr: print("Moving back to {}".format(target_instr)) self.enable_only("debug_counter") self.condition_instr("debug_counter", "<=", target_instr) self.reverse_cont() current_instr = self.instr_count() if current_instr != target_instr: print("Moving precisely to", target_instr) self.enable_only("cpu_loop_exec_tb") self.condition_instr("cpu_loop_exec_tb", ">=", target_instr) self.cont() # Go from beginning of program to execution of first TB after record/replay. # Return number of that checkpoint. def goto_first_tb(self): self.enable_only("rr_do_begin_record", "rr_do_begin_replay") self.cont() self.enable_only("cpu_loop_exec_tb") self.cont() return self.record_instr_checkpoint() def find_last_instr(self, cli_args, last_event): first_tb_checkpoint = self.goto_first_tb() if cli_args.instr_max is not None: instr_count_max = cli_args.instr_max else: # get last instruction in failed replay self.gdb("run", last_event, timeout=None) self.enable_only("cpu_loop_exec_tb") self.reverse_cont() # go backwards through failure signal self.reverse_cont() # land on last TB exec instr_count_max = self.instr_count() # reset replay so it is in same state as record self.restart(first_tb_checkpoint) return instr_count_max
class RRInstance(object): def __init__(self, description, rr_replay, source_pane): self.description = description self.spawn_cmd = "{} replay {}".format( pipes.quote(cli_args.rr), pipes.quote(rr_replay)) self.source_pane = source_pane self.breakpoints = {} self.watches_set = 0 def __repr__(self): return "RRInstance({!r})".format(self.description) # Runs in child process. def sendline(self, msg): check_call(['tmux', 'send-keys', '-t', self.pane, '-l', msg]) check_call(['tmux', 'send-keys', '-t', self.pane, 'ENTER']) # Runs in child process. def kill(self): check_call(['tmux', 'kill-pane', '-t', self.pane]) def __enter__(self): self.tempdir_obj = TempDir() tempdir = self.tempdir_obj.__enter__() logfile = join(tempdir, self.description + "out") os.mkfifo(logfile) bash_command = "{} 2>&1 | tee -i --output-error=warn {} | tee -i --output-error=warn {}_log.txt".format( self.spawn_cmd, pipes.quote(logfile), self.description) self.pane = check_output([ 'tmux', 'split-window', '-hdP', '-F', '#{pane_id}', '-t', pane, 'bash', '-c', bash_command]).strip() self.proc = Expect(os.open(logfile, os.O_RDONLY | os.O_NONBLOCK), quiet=True) self.proc.expect("(rr) ") return self def __exit__(self, exc_type, exc_value, traceback): if exc_type: self.kill() self.tempdir_obj.__exit__(exc_type, exc_value, traceback) def gdb(self, *args, **kwargs): timeout = kwargs.get('timeout', None) cmd = " ".join(map(str, args)) print "(rr-{}) {}".format(self.description, cmd) sys.stdout.flush() while True: try: os.read(self.proc.fd, 1024) except OSError as e: if e.errno in [EAGAIN, EWOULDBLOCK]: break else: raise self.sendline(cmd) try: output = self.proc.expect("(rr) ", timeout=timeout) except TimeoutExpired: print self.proc.sofar print "EXCEPTION!" sys.stdout.flush() return output def quit(self): self.gdb("set confirm off") self.sendline("quit") def breakpoint(self, break_arg): result = self.gdb("break", break_arg) bp_num = int(re.search(r"Breakpoint ([0-9]+) at", result).group(1)) self.breakpoints[break_arg] = bp_num def disable_all(self): self.gdb("disable") def enable(self, break_arg): self.gdb("enable", self.breakpoints[break_arg]) def condition(self, break_arg, cond): self.gdb("condition", self.breakpoints[break_arg], cond) def condition_instr(self, break_arg, op, instr): if not hasattr(self, 'instr_count_ptr'): self.instr_count_ptr = self.get_value( "&cpus->tqh_first->rr_guest_instr_count") self.condition( break_arg, "*(uint64_t *){} {} {}".format(self.instr_count_ptr, op, instr)) def get_value(self, value_str): result = self.gdb("print/u", value_str) re_result = re.search(r"\$[0-9]+ = ([0-9]+)", result) if re_result: return long(re_result.group(1)) else: print "get_value failed. result:", result raise RuntimeError("get_value") def instr_count(self): return self.get_value("cpus->tqh_first->rr_guest_instr_count") def ram_ptr(self): if not hasattr(self, '_ram_ptr'): self._ram_ptr = self.get_value( "memory_region_find(" + "get_system_memory(), 0x2000000, 1).mr->ram_block.host") return self._ram_ptr def crc32_ram(self, low, size): step = 1 << 31 if size > (1 << 31) else size crc32s = 0 for start in range(low, low + size, step): crc32s ^= self.get_value("crc32(0, {} + {}, {})".format( hex(self.ram_ptr()), hex(start), hex(step))) return crc32s def ram_size(self): if not hasattr(self, '_ram_size'): self._ram_size = self.get_value('ram_size') return self._ram_size def checksum(self): # NB: Only run when you are at a breakpoint in CPU thread! memory = self.crc32_ram(0, self.ram_size()) regs = self.get_value("rr_checksum_regs()") return (memory, regs) def when(self): result = self.gdb("when") re_result = re.search(r"Current event: ([0-9]+)", result) if re_result: return int(re_result.group(1)) else: print "when failed. result:", result raise RuntimeError("when") def cont(self): self.gdb("continue", timeout=None) def reverse_cont(self): self.gdb("reverse-continue", timeout=None) def run_event(self, event): self.gdb("run", event, timeout=None) # x86 debug registers can only watch 4 locations of 8 bytes. # we need to make sure to enforce that. # returns true if can set more watchpoints. false if we're full up. def watch(self, addr, size): assert size in [1, 2, 4, 8] bits = size * 8 self.gdb("watch *(uint{}_t *)0x{:x}".format(bits, addr)) self.watches_set += 1 if self.watches_set >= 4: print print "WARNING: Too much divergence! Not watching some diverged points." print "(watchpoints are full...)" print # watch a location in guest ram. def watch_ram(self, ram_addr, size): self.watch(self.ram_ptr() + ram_addr, size) # Get as close to instr as possible. def goto(self, target_instr): print "Moving", self, "to instr", target_instr self.disable_all() current_instr = self.instr_count() if target_instr in instr_to_event: run_instr = target_instr else: index = instr_to_event.keys().bisect_left(target_instr) - 1 run_instr = instr_to_event.keys()[index] if current_instr > target_instr or current_instr < run_instr: self.run_event(instr_to_event[run_instr][self]) # We should have now guaranteed that both will be in [run_instr, target_instr]. # Now run them forwards to as close to target_instr as we can get. # debug_counter fires every 128k instrs, so move to last debug_counter # before desired instr count. run_instr = target_instr - DEBUG_COUNTER_PERIOD current_instr = self.instr_count() if current_instr < run_instr: print "Moving from {} to {} below {}".format(current_instr, run_instr, target_instr) self.enable("debug_counter") self.condition_instr("debug_counter", ">=", run_instr) self.cont() # unfortunately, we might have gone too far above. move back one # debug_counter fire if necessary. current_instr = self.instr_count() if current_instr > target_instr: print "Moving back to {}".format(target_instr) self.enable("debug_counter") self.condition_instr("debug_counter", "<=", target_instr) self.reverse_cont() current_instr = self.instr_count() if current_instr != target_instr: print "Moving precisely to", target_instr self.disable_all() self.enable("cpu_loop_exec_tb") self.condition_instr("cpu_loop_exec_tb", ">=", target_instr) self.cont() # Go from beginning of program to execution of first TB after record/replay. def goto_first_tb(self): self.disable_all() self.enable("rr_do_begin_record") self.enable("rr_do_begin_replay") self.cont() self.enable("cpu_loop_exec_tb") self.cont() def find_last_instr(self, cli_args, last_event): self.goto_first_tb() if cli_args.instr_max: instr_count_max = int(cli_args.instr_max) else: # get last instruction in failed replay self.run_event(last_event) self.disable_all() self.enable("cpu_loop_exec_tb") self.reverse_cont() self.reverse_cont() instr_count_max = self.instr_count() # reset replay so it is in same state as record self.run_event(0) self.goto_first_tb() return instr_count_max
class RRInstance(object): def __init__(self, description, rr_replay, source_pane): self.rr_replay = rr_replay self.description = description self.spawn_cmd = "{} replay {}".format( shlex.quote(cli_args.rr), shlex.quote(rr_replay)) self.source_pane = source_pane self.breakpoints = {} self.watches_set = 0 self.instr_to_checkpoint = sorteddict() def __repr__(self): return "RRInstance({!r})".format(self.description) @cached_property def arch(self): rr_ps = check_output([cli_args.rr, 'ps', self.rr_replay]) qemu_regex = r"qemu-system-({})".format("|".join(SUPPORTED_ARCHS.keys())) re_result = re.search(qemu_regex, rr_ps) if not re_result: raise RuntimeError("Unsupported architecture!") return SUPPORTED_ARCHS[re_result.group(1)] # Runs in child process. def sendline(self, msg): check_call(['tmux', 'send-keys', '-t', self.pane, '-l', msg]) check_call(['tmux', 'send-keys', '-t', self.pane, 'ENTER']) # Runs in child process. def kill(self): check_call(['tmux', 'kill-pane', '-t', self.pane]) def __enter__(self): self.tempdir_obj = TempDir() tempdir = self.tempdir_obj.__enter__() logfile = join(tempdir, self.description + "out") os.mkfifo(logfile) bash_command = "{} 2>&1 | tee -i --output-error=warn {} | tee -i --output-error=warn {}_log.txt".format( self.spawn_cmd, shlex.quote(logfile), self.description) self.pane = check_output([ 'tmux', 'split-window', '-hdP', '-F', '#{pane_id}', '-t', pane, 'bash', '-c', bash_command]).strip() self.proc = Expect(os.open(logfile, os.O_RDONLY | os.O_NONBLOCK), quiet=True) try: self.proc.expect("(rr) ", timeout=3) except TimeoutExpired: print(self.proc.sofar) raise return self def __exit__(self, exc_type, exc_value, traceback): if exc_type: self.kill() self.tempdir_obj.__exit__(exc_type, exc_value, traceback) def gdb(self, *args, **kwargs): timeout = kwargs.get('timeout', None) cmd = " ".join(map(str, args)) print("(rr-{}) {}".format(self.description, cmd)) sys.stdout.flush() expect_prompt = kwargs.get("expect_prompt", "(rr) ") while True: try: os.read(self.proc.fd, 1024) except OSError as e: if e.errno in [EAGAIN, EWOULDBLOCK]: break else: raise self.sendline(cmd) try: output = self.proc.expect(expect_prompt, timeout=timeout) except TimeoutExpired: print(self.proc.sofar) print("EXCEPTION!") sys.stdout.flush() if output.endswith(expect_prompt): output = output[:-len(expect_prompt)] if output.startswith(cmd): output = output[len(cmd):] return output.strip() def quit(self): self.gdb("set confirm off") self.sendline("quit") def gdb_int_re(self, result_re, *args): result = self.gdb(*args) return re_search_int(result_re, result) def breakpoint(self, break_arg): bp_num = self.gdb_int_re(r"Breakpoint ([0-9]+) at", "break", break_arg) self.breakpoints[break_arg] = bp_num def disable_all(self): self.gdb("disable") self.watches_set = 0 def enable(self, break_arg): self.gdb("enable", self.breakpoints[break_arg]) def enable_only(self, *breaks): self.disable_all() for break_arg in breaks: self.enable(break_arg) def condition(self, break_arg, cond): self.gdb("condition", self.breakpoints[break_arg], cond) def display(self, cmd): self.gdb("display", cmd) def checkpoint(self): return self.gdb_int_re(r"Checkpoint ([0-9]+) at", "checkpoint") def restart(self, checkpoint): self.disable_all() self.gdb("restart", checkpoint) def restart_instr(self, instr): self.restart(self.instr_to_checkpoint[instr]) def get_value(self, expr): return self.gdb_int_re(r"\$[0-9]+ = ([0-9]+)", "print/u", expr) def instr_count(self): return self.get_value("cpus->tqh_first->rr_guest_instr_count") @cached_property def instr_count_ptr(self): return self.get_value("&cpus->tqh_first->rr_guest_instr_count") def condition_instr(self, break_arg, op, instr): self.condition( break_arg, "*(uint64_t *){} {} {}".format(self.instr_count_ptr, op, instr)) def set_breakpoint_commands(self, break_num): self.gdb("commands", break_num, expect_prompt = ">") # self.gdb("p/u cpus->tqh_first->rr_guest_instr_count", expect_prompt = ">") self.gdb("call target_disas(stdout, cpu, tb->pc, tb->size, 0)", expect_prompt = ">") self.gdb("end") def display_commands(self): self.display("cpus->tqh_first->rr_guest_instr_count") self.display("cpus->tqh_first->exception_index") self.display("cpus->tqh_first->exit_request") self.gdb("set $env = ((CPUPPCState*) cpus->tqh_first->env_ptr)") self.display("$env->pending_interrupts") @cached_property def ram_ptr(self): return self.get_value( "memory_region_find(" + "get_system_memory(), 0x2000000, 1).mr->ram_block.host") def crc32_ram(self, low, size): step = 1 << 31 if size > (1 << 31) else size crc32s = 0 for start in range(low, low + size, step): crc32s ^= self.get_value("crc32(0, {} + {}, {})".format( hex(self.ram_ptr), hex(start), hex(step))) return crc32s @cached_property def ram_size(self): return self.get_value('ram_size') @cached_property def reg_size(self): return self.get_value("sizeof (({}*)0)->{}[0]".format( self.arch.cpu_state_name, self.arch.reg_name)) @cached_property def num_regs(self): return self.get_value("sizeof (({}*)0)->{}".format( self.arch.cpu_state_name, self.arch.reg_name)) // self.reg_size def env_value(self, name): return self.get_value("(({}*)cpus->tqh_first->env_ptr)->{}".format( self.arch.cpu_state_name, name)) def env_ptr(self, name): return self.get_value("&(({}*)cpus->tqh_first->env_ptr)->{}".format( self.arch.cpu_state_name, name)) def checksum(self): # NB: Only run when you are at a breakpoint in CPU thread! memory = self.crc32_ram(0, self.ram_size) regs = self.get_value("rr_checksum_regs()") return (memory, regs) def when(self): return self.gdb_int_re(r"Current event: ([0-9]+)", "when") def cont(self): return self.gdb("continue", timeout=None) def reverse_cont(self): return self.gdb("reverse-continue", timeout=None) # x86 debug registers can only watch 4 locations of 8 bytes. # we need to make sure to enforce that. # returns true if can set more watchpoints. false if we're full up. def watch_addr(self, addr, size): assert size in [1, 2, 4, 8] bits = size * 8 num = self.gdb_int_re(r"Hardware watchpoint ([0-9]+):", "watch", "*(uint{}_t *)0x{:x}".format(bits, addr)) self.watches_set += 1 if self.watches_set > 4: print() print("WARNING: Too much divergence! Not watching some diverged points.") print("(watchpoints are full...)") print() return num def watch(self, watchpoint): return self.watch_addr(*watchpoint.render(self)) def record_instr_checkpoint(self): instr_count = self.instr_count() if instr_count not in self.instr_to_checkpoint: self.instr_to_checkpoint[instr_count] = self.checkpoint() return self.instr_to_checkpoint[instr_count] # Get as close to instr as possible. def goto_rough(self, target_instr): print("Moving", self, "to instr", target_instr) current_instr = self.instr_count() if target_instr in self.instr_to_checkpoint: run_instr = target_instr else: index = self.instr_to_checkpoint.keys().bisect_left(target_instr) - 1 run_instr = self.instr_to_checkpoint.keys()[index] if current_instr > target_instr or current_instr < run_instr: self.restart(self.instr_to_checkpoint[run_instr]) # We should have now guaranteed that both will be in [run_instr, target_instr]. # Now run them forwards to as close to target_instr as we can get. # debug_counter fires every 128k instrs, so move to last debug_counter # before desired instr count. run_instr = target_instr - DEBUG_COUNTER_PERIOD current_instr = self.instr_count() if current_instr < run_instr: print("Moving from {} to {} below {}".format(current_instr, run_instr, target_instr)) self.enable_only("debug_counter") self.condition_instr("debug_counter", ">=", run_instr) self.cont() current_instr = self.instr_count() # unfortunately, we might have gone too far above. move back one # debug_counter fire if necessary. if current_instr > target_instr: print("Moving back to {}".format(target_instr)) self.enable_only("debug_counter") self.condition_instr("debug_counter", "<=", target_instr) self.reverse_cont() current_instr = self.instr_count() if current_instr != target_instr: print("Moving precisely to", target_instr) self.enable_only("cpu_loop_exec_tb") self.condition_instr("cpu_loop_exec_tb", ">=", target_instr) self.cont() # Go from beginning of program to execution of first TB after record/replay. # Return number of that checkpoint. def goto_first_tb(self): self.enable_only("rr_do_begin_record", "rr_do_begin_replay") self.cont() self.enable_only("cpu_loop_exec_tb") self.cont() return self.record_instr_checkpoint() def find_last_instr(self, cli_args, last_event): first_tb_checkpoint = self.goto_first_tb() if cli_args.instr_max is not None: instr_count_max = cli_args.instr_max else: # get last instruction in failed replay self.gdb("run", last_event, timeout=None) self.enable_only("cpu_loop_exec_tb") self.reverse_cont() # go backwards through failure signal self.reverse_cont() # land on last TB exec instr_count_max = self.instr_count() # reset replay so it is in same state as record self.restart(first_tb_checkpoint) return instr_count_max
class RRInstance(object): def __init__(self, description, rr_replay, source_pane): self.description = description self.spawn_cmd = "{} replay {}".format(pipes.quote(cli_args.rr), pipes.quote(rr_replay)) self.source_pane = source_pane self.breakpoints = {} self.watches_set = 0 def __repr__(self): return "RRInstance({!r})".format(self.description) # Runs in child process. def sendline(self, msg): check_call(['tmux', 'send-keys', '-t', self.pane, '-l', msg]) check_call(['tmux', 'send-keys', '-t', self.pane, 'ENTER']) # Runs in child process. def kill(self): check_call(['tmux', 'kill-pane', '-t', self.pane]) def __enter__(self): self.tempdir_obj = TempDir() tempdir = self.tempdir_obj.__enter__() logfile = join(tempdir, self.description + "out") os.mkfifo(logfile) bash_command = "{} 2>&1 | tee -i --output-error=warn {} | tee -i --output-error=warn {}_log.txt".format( self.spawn_cmd, pipes.quote(logfile), self.description) self.pane = check_output([ 'tmux', 'split-window', '-hdP', '-F', '#{pane_id}', '-t', pane, 'bash', '-c', bash_command ]).strip() self.proc = Expect(os.open(logfile, os.O_RDONLY | os.O_NONBLOCK), quiet=True) self.proc.expect("(rr) ") return self def __exit__(self, exc_type, exc_value, traceback): if exc_type: self.kill() self.tempdir_obj.__exit__(exc_type, exc_value, traceback) def gdb(self, *args, **kwargs): timeout = kwargs.get('timeout', None) cmd = " ".join(map(str, args)) print "(rr-{}) {}".format(self.description, cmd) sys.stdout.flush() while True: try: os.read(self.proc.fd, 1024) except OSError as e: if e.errno in [EAGAIN, EWOULDBLOCK]: break else: raise self.sendline(cmd) try: output = self.proc.expect("(rr) ", timeout=timeout) except TimeoutExpired: print self.proc.sofar print "EXCEPTION!" sys.stdout.flush() return output def quit(self): self.gdb("set confirm off") self.sendline("quit") def breakpoint(self, break_arg): result = self.gdb("break", break_arg) bp_num = int(re.search(r"Breakpoint ([0-9]+) at", result).group(1)) self.breakpoints[break_arg] = bp_num def disable_all(self): self.gdb("disable") def enable(self, break_arg): self.gdb("enable", self.breakpoints[break_arg]) def condition(self, break_arg, cond): self.gdb("condition", self.breakpoints[break_arg], cond) def condition_instr(self, break_arg, op, instr): if not hasattr(self, 'instr_count_ptr'): self.instr_count_ptr = self.get_value( "&cpus->tqh_first->rr_guest_instr_count") self.condition( break_arg, "*(uint64_t *){} {} {}".format(self.instr_count_ptr, op, instr)) def get_value(self, value_str): result = self.gdb("print/u", value_str) re_result = re.search(r"\$[0-9]+ = ([0-9]+)", result) if re_result: return long(re_result.group(1)) else: print "get_value failed. result:", result raise RuntimeError("get_value") def instr_count(self): return self.get_value("cpus->tqh_first->rr_guest_instr_count") def ram_ptr(self): if not hasattr(self, '_ram_ptr'): self._ram_ptr = self.get_value( "memory_region_find(" + "get_system_memory(), 0x2000000, 1).mr->ram_block.host") return self._ram_ptr def crc32_ram(self, low, size): step = 1 << 31 if size > (1 << 31) else size crc32s = 0 for start in range(low, low + size, step): crc32s ^= self.get_value("crc32(0, {} + {}, {})".format( hex(self.ram_ptr()), hex(start), hex(step))) return crc32s def ram_size(self): if not hasattr(self, '_ram_size'): self._ram_size = self.get_value('ram_size') return self._ram_size def checksum(self): # NB: Only run when you are at a breakpoint in CPU thread! memory = self.crc32_ram(0, self.ram_size()) regs = self.get_value("rr_checksum_regs()") return (memory, regs) def when(self): result = self.gdb("when") re_result = re.search(r"Current event: ([0-9]+)", result) if re_result: return int(re_result.group(1)) else: print "when failed. result:", result raise RuntimeError("when") def cont(self): self.gdb("continue", timeout=None) def reverse_cont(self): self.gdb("reverse-continue", timeout=None) def run_event(self, event): self.gdb("run", event, timeout=None) # x86 debug registers can only watch 4 locations of 8 bytes. # we need to make sure to enforce that. # returns true if can set more watchpoints. false if we're full up. def watch(self, addr, size): assert size in [1, 2, 4, 8] bits = size * 8 self.gdb("watch *(uint{}_t *)0x{:x}".format(bits, addr)) self.watches_set += 1 if self.watches_set >= 4: print print "WARNING: Too much divergence! Not watching some diverged points." print "(watchpoints are full...)" print # watch a location in guest ram. def watch_ram(self, ram_addr, size): self.watch(self.ram_ptr() + ram_addr, size) # Get as close to instr as possible. def goto(self, target_instr): print "Moving", self, "to instr", target_instr self.disable_all() current_instr = self.instr_count() if target_instr in instr_to_event: run_instr = target_instr else: index = instr_to_event.keys().bisect_left(target_instr) - 1 run_instr = instr_to_event.keys()[index] if current_instr > target_instr or current_instr < run_instr: self.run_event(instr_to_event[run_instr][self]) # We should have now guaranteed that both will be in [run_instr, target_instr]. # Now run them forwards to as close to target_instr as we can get. # debug_counter fires every 128k instrs, so move to last debug_counter # before desired instr count. run_instr = target_instr - DEBUG_COUNTER_PERIOD current_instr = self.instr_count() if current_instr < run_instr: print "Moving from {} to {} below {}".format( current_instr, run_instr, target_instr) self.enable("debug_counter") self.condition_instr("debug_counter", ">=", run_instr) self.cont() # unfortunately, we might have gone too far above. move back one # debug_counter fire if necessary. current_instr = self.instr_count() if current_instr > target_instr: print "Moving back to {}".format(target_instr) self.enable("debug_counter") self.condition_instr("debug_counter", "<=", target_instr) self.reverse_cont() current_instr = self.instr_count() if current_instr != target_instr: print "Moving precisely to", target_instr self.disable_all() self.enable("cpu_loop_exec_tb") self.condition_instr("cpu_loop_exec_tb", ">=", target_instr) self.cont() # Go from beginning of program to execution of first TB after record/replay. def goto_first_tb(self): self.disable_all() self.enable("rr_do_begin_record") self.enable("rr_do_begin_replay") self.cont() self.enable("cpu_loop_exec_tb") self.cont() def find_last_instr(self, cli_args, last_event): self.goto_first_tb() if cli_args.instr_max: instr_count_max = int(cli_args.instr_max) else: # get last instruction in failed replay self.run_event(last_event) self.disable_all() self.enable("cpu_loop_exec_tb") self.reverse_cont() self.reverse_cont() instr_count_max = self.instr_count() # reset replay so it is in same state as record self.run_event(0) self.goto_first_tb() return instr_count_max