Esempio n. 1
0
def test_dump_state(capsys):
    dump_state(VirtualMachine(), Settings(volume=VOLUME_VERBOSE))

    assert (
        capsys.readouterr().err
        == """\

Virtual machine state after execution (HERA v2.4.0):
    R1  = 0x0000 = 0
    R2  = 0x0000 = 0
    R3  = 0x0000 = 0
    R4  = 0x0000 = 0
    R5  = 0x0000 = 0
    R6  = 0x0000 = 0
    R7  = 0x0000 = 0
    R8  = 0x0000 = 0
    R9  = 0x0000 = 0
    R10 = 0x0000 = 0
    R11 = 0x0000 = 0
    R12 = 0x0000 = 0
    R13 = 0x0000 = 0
    R14 = 0x0000 = 0
    R15 = 0x0000 = 0

    Carry-block flag is OFF
    Carry flag is OFF
    Overflow flag is OFF
    Zero flag is OFF
    Sign flag is OFF
"""
    )
Esempio n. 2
0
    def __init__(self, program: Program, settings: Settings) -> None:
        self.settings = settings
        self.program = program
        self.symbol_table = program.symbol_table
        # A map from instruction numbers (i.e., possible values of the program counter)
        # to human-readable line numbers.
        self.breakpoints = {}  # type: Dict[int, str]
        self.vm = VirtualMachine()
        # How many CALLs without RETURNs?
        self.calls = 0
        # Back-up of the debugger's state, to implement the "undo" command. Implicitly
        # defines a linked list that traverses the debugger's entire history. Set to
        # None when no operations have been performed.
        self.old = None  # type: Optional[Debugger]

        # In the future, step-by-step execution of data operations may be supported, but
        # for now they're just executed before interactive debugging starts.
        for data_op in self.program.data:
            data_op.execute(self.vm)
Esempio n. 3
0
def vm():
    return VirtualMachine()
Esempio n. 4
0
class Debugger:
    """
    A class for debugging. External users should generally use the `debug` function
    in `shell.py` instead of instantiating this class directly.
    """
    def __init__(self, program: Program, settings: Settings) -> None:
        self.settings = settings
        self.program = program
        self.symbol_table = program.symbol_table
        # A map from instruction numbers (i.e., possible values of the program counter)
        # to human-readable line numbers.
        self.breakpoints = {}  # type: Dict[int, str]
        self.vm = VirtualMachine()
        # How many CALLs without RETURNs?
        self.calls = 0
        # Back-up of the debugger's state, to implement the "undo" command. Implicitly
        # defines a linked list that traverses the debugger's entire history. Set to
        # None when no operations have been performed.
        self.old = None  # type: Optional[Debugger]

        # In the future, step-by-step execution of data operations may be supported, but
        # for now they're just executed before interactive debugging starts.
        for data_op in self.program.data:
            data_op.execute(self.vm)

    def save(self) -> None:
        self.old = copy.copy(self)
        self.old.symbol_table = self.program.symbol_table.copy()
        self.old.breakpoints = self.breakpoints.copy()
        self.old.vm = self.vm.copy()

    def get_breakpoints(self) -> "Dict[int, str]":
        """Get the breakpoints dictionary."""
        return self.breakpoints

    def set_breakpoint(self, b: int) -> None:
        """Set a breakpoint at the given instruction number (not line number)."""
        self.breakpoints[b] = self.instruction_number_to_location(b)

    def at_breakpoint(self) -> bool:
        """Return True if the debugger is currently at a breakpoint."""
        return not self.finished() and self.vm.pc in self.breakpoints

    def next(self, *, step: bool) -> None:
        """
        Advance the debugger by one original instruction.

        When `step` is True and the current operation is a CALL, the entire function
        call up to the matching RETURN is executed. When `step` is False, just the CALL
        operation is executed, and the debugger is left on the first operation of the
        body of the function.
        """
        if self.finished():
            return

        if not step and self.program.code[self.vm.pc].original.name == "CALL":
            calls = self.calls
            # step=True prevents infinite regress.
            self.next(step=True)
            while (not self.finished() and not self.at_breakpoint()
                   and self.calls > calls):
                self.next(step=True)
        else:
            for real_op in self.real_ops():
                if real_op.name == "CALL":
                    self.calls += 1
                elif real_op.name == "RETURN":
                    self.calls -= 1

                real_op.execute(self.vm)

    def reset(self) -> None:
        """Reset the internal state of the debugger."""
        self.vm.reset()

    def op(self, index=None) -> AbstractOperation:
        """
        Return the original operation at the given index, which defaults to the current
        program counter.
        """
        if index is None:
            index = self.vm.pc
        return self.program.code[index].original

    def real_ops(self) -> "List[AbstractOperation]":
        """
        Return the real operations that correspond to the current original operation.
        """
        original = self.op()
        end = self.vm.pc
        while end < len(self.program.code) and self.op(end) == original:
            end += 1

        return self.program.code[self.vm.pc:end]

    def location_to_instruction_number(self, b: str) -> int:
        """Resolve a user-supplied location string into an instruction number."""
        if b == ".":
            return self.vm.pc

        if ":" in b:
            path, lineno = b.split(":", maxsplit=1)
        else:
            path = self.op().loc.path
            lineno = b

        try:
            lineno_as_int = int(lineno)
        except ValueError:
            try:
                opno = self.program.symbol_table[b]
                assert isinstance(opno, Label)
                return opno
            except (KeyError, AssertionError):
                raise ValueError(
                    "could not locate label `{}`.".format(b)) from None
        else:
            for pc, op in enumerate(self.program.code):
                if op.loc.path == path and op.loc.line == lineno_as_int:
                    return pc

            raise ValueError("could not find corresponding line.")

    def instruction_number_to_location(self,
                                       b: int,
                                       *,
                                       append_label=True) -> str:
        """
        Turn an instruction number into a human-readable location string with the file
        path and line number.
        """
        op = self.program.code[b].original or self.program.code[b]
        path = "<stdin>" if op.loc.path == "-" else op.loc.path
        loc = path + ":" + str(op.loc.line)

        if append_label:
            # Look for a label corresponding to the breakpoint.
            label = self.find_label(b)
            if label is not None:
                return "{} ({})".format(loc, label)

        return loc

    def find_label(self, ino: int) -> "Optional[str]":
        """Find a label, if one exists, corresponding to the instruction number."""
        for symbol, value in self.program.symbol_table.items():
            if value == ino and isinstance(value, Label):
                return symbol
        return None

    def finished(self) -> bool:
        """Return True if the debugger has finished executing the program."""
        return self.vm.halted or self.vm.pc >= len(self.program.code)

    def empty(self) -> bool:
        """Return True if the debugged program is empty."""
        return len(self.program.code) == 0
Esempio n. 5
0
def vm():
    return VirtualMachine(Settings(color=False))