예제 #1
0
파일: diverge.py 프로젝트: wtwofire/panda
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
예제 #2
0
파일: diverge.py 프로젝트: mewbak/panda
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
예제 #3
0
파일: diverge.py 프로젝트: m000/panda
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
예제 #4
0
파일: diverge.py 프로젝트: zshu1/panda
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