import enum
from .util import UINT32_FMT
[docs]class Error(Exception):
"""
Base class for all Ducky exceptions.
:param str message: optional description.
"""
def __init__(self, message = None):
super(Error, self).__init__()
self.message = message or ''
def __str__(self):
return self.message
[docs] def log(self, logger):
logger('')
logger(self.message)
[docs]class InvalidResourceError(Error):
"""
Raised when an operation was requested on somehow invalid resource.
``message`` attribute will provide better idea about the fault.
"""
pass
[docs]class AccessViolationError(Error):
"""
Raised when an operation was requested without having adequate permission
to do so. ``message`` attribute will provide better idea about the fault.
"""
pass
[docs]class OperandMismatchError(Error):
"""
"""
def __init__(self, instr, expected, actual):
super(OperandMismatchError, self).__init__('Operand type mismatch: instr=%s, expected=%s, actual=%s' % (instr, expected, actual))
[docs]class AssemblerError(Error):
"""
Base class for all assembler-related exceptions. Provides common properties,
helping to locate related input in the source file.
:param ducky.cpu.assembly.SourceLocation location: if set, points to the
location in the source file that was processed when the exception occured.
:param str message: more detailed description.
:param str line: input source line.
:param info: additional details of the exception. This value is usually part
of the ``message``, but is stored as well.
"""
def __init__(self, location = None, message = None, line = None, info = None):
self.location = location
self.message = message
self.line = line
self.info = info
self.create_text()
super(AssemblerError, self).__init__(message = self.text[0])
[docs] def create_text(self):
text = ['{coor}: {message}'.format(coor = str(self.location), message = self.message)]
if self.line is not None:
text.append(self.line)
if self.location is not None and self.location.column is not None:
text.append(' ' * self.location.column + '^')
self.text = text
[docs] def log(self, logger):
logger('')
for line in self.text:
logger(line)
[docs]class AssemblyIllegalCharError(AssemblerError):
def __init__(self, c = None, **kwargs):
super(AssemblyIllegalCharError, self).__init__(message = 'Illegal character: c="%s"' % c, **kwargs)
self.c = c
[docs]class AssemblyParseError(AssemblerError):
def __init__(self, token = None, **kwargs):
super(AssemblyParseError, self).__init__(message = 'Parse error', **kwargs)
self.token = token
[docs]class UnknownInstructionError(AssemblerError):
def __init__(self, **kwargs):
super(UnknownInstructionError, self).__init__(message = 'Unknown instruction {instr}'.format(**kwargs))
[docs]class TooManyLabelsError(AssemblerError):
def __init__(self, **kwargs):
super(TooManyLabelsError, self).__init__(message = 'Too many consecutive labels', **kwargs)
[docs]class UnknownPatternError(AssemblerError):
def __init__(self, **kwargs):
super(UnknownPatternError, self).__init__(message = 'Unknown pattern: "{info}"'.format(**kwargs), **kwargs)
[docs]class IncompleteDirectiveError(AssemblerError):
def __init__(self, **kwargs):
super(IncompleteDirectiveError, self).__init__(message = 'Incomplete directive: {info}'.format(**kwargs), **kwargs)
[docs]class UnknownFileError(AssemblerError):
def __init__(self, **kwargs):
super(UnknownFileError, self).__init__(message = 'Unknown file: {info}'.format(**kwargs), **kwargs)
[docs]class DisassembleMismatchError(AssemblerError):
def __init__(self, **kwargs):
super(DisassembleMismatchError, self).__init__(message = 'Disassembled instruction does not match input: {info}'.format(**kwargs), **kwargs)
[docs]class UnalignedJumpTargetError(AssemblerError):
def __init__(self, **kwargs):
super(UnalignedJumpTargetError, self).__init__(message = 'Jump destination address is not 4-byte aligned: {info}'.format(**kwargs), **kwargs)
[docs]class EncodingLargeValueError(AssemblerError):
def __init__(self, **kwargs):
super(EncodingLargeValueError, self).__init__(message = 'Value cannot fit into field: {info}'.format(**kwargs), **kwargs)
[docs]class ConflictingNamesError(AssemblerError):
def __init__(self, **kwargs):
super(ConflictingNamesError, self).__init__(message = 'Label already defined: name="{name}", prev-location={prev_location}'.format(**kwargs), **kwargs)
#
# Linker errors
#
[docs]class LinkerError(Error):
pass
[docs]class UnknownSymbolError(LinkerError):
pass
[docs]class BadLinkerScriptError(LinkerError):
def __init__(self, script, exc, *args, **kwargs):
super(BadLinkerScriptError, self).__init__('Bad linker script: script=%s, error=%s' % (script, exc), *args, **kwargs)
self.script = script
self.exc = exc
[docs]class IncompatibleSectionFlagsError(LinkerError):
def __init__(self, dst_section, src_section, *args, **kwargs):
super(IncompatibleSectionFlagsError, self).__init__('Source section has different flags set: dst_header=%s, src_header=%s' % (dst_section.header, src_section.header), *args, **kwargs)
self.dst_section = dst_section
self.src_section = src_section
[docs]class UnknownDestinationSectionError(LinkerError):
def __init__(self, src_section, *args, **kwargs):
super(UnknownDestinationSectionError, self).__init__('Unknown destination section for source section %s' % src_section, *args, **kwargs)
self.src_section = src_section
[docs]class ExceptionList(enum.IntEnum):
"""
List of exception IDs (``EVT`` indices).
"""
FIRST_HW = 0
LAST_HW = 15
FIRST_SW = 16
LAST_SW = 31
COUNT = 32
# SW interrupts and exceptions
InvalidOpcode = 16
InvalidInstSet = 17
DivideByZero = 18
UnalignedAccess = 19
PrivilegedInstr = 20
DoubleFault = 21
MemoryAccess = 22
RegisterAccess = 23
InvalidException = 24
CoprocessorError = 25
[docs]class ExecutionException(Exception):
"""
Base class for all execution-related exceptions, i.e. exceptions raised
as a direct result of requests made by running code. Runnign code can then
take care of handling these exceptions, usually by preparing service
routines and setting up the ``EVT``.
Unless said otherwise, the exception is always raised *before* making
any changes in the VM state.
:param string msg: message describing exceptional state.
:param ducky.cpu.CPUCore core: CPU core that raised exception, if any.
:param u32_t ip: address of an instruction that caused exception, if any.
"""
def __init__(self, msg, core = None, ip = None):
super(ExecutionException, self).__init__(msg)
self.core = core
self.ip = ip or (core.current_ip if core is not None else None)
def __str__(self):
return ' '.join([
self.core.cpuid_prefix if self.core is not None else '<undef>:',
UINT32_FMT(self.ip) if self.ip is not None else '<undef>:',
self.message
])
[docs] def runtime_handle(self):
"""
This method is called by CPU code, to find out if it is possible for
runtime to handle the exception, and possibly recover from it. If it
is possible, this method should make necessary arrangements, and then
return ``True``. Many exceptions, e.g. when division by zero was requested,
will tell CPU to run exception service routine, and by returning ``True``
signal that it's not necessary to take care of such exception anymore.
:rtype: bool
:returns: ``True`` when it's no longer necessary for VM code to take
care of this exception.
"""
pass
[docs]class ExecutionException__SimpleESR(object):
"""
Helper mixin class - as one of parents, it brings to its children
very simle - and most of the time sufficient - implementation of
`runtime_handle` method. Such exceptions will tell CPU to run
exception service routine with a secific index, specified by class
variable ``EXCEPTION_INDEX``.
The address of the offensive instruction - or the value of ``IP``
when exception was raised, since there may be exceptions not raised
in response to the executed instruction - is passed to CPU as the
first argument of ESR.
"""
[docs] def runtime_handle(self):
self.core._handle_exception(self, self.__class__.EXCEPTION_INDEX, self.ip)
return True
[docs]class InvalidOpcodeError(ExecutionException__SimpleESR, ExecutionException):
"""
Raised when unknown or invalid opcode is found in instruction.
:param int opcode: wrong opcode.
"""
EXCEPTION_INDEX = ExceptionList.InvalidOpcode
def __init__(self, opcode, *args, **kwargs):
super(InvalidOpcodeError, self).__init__('Invalid opcode: opcode={}'.format(opcode), *args, **kwargs)
self.opcode = opcode
[docs]class DivideByZeroError(ExecutionException__SimpleESR, ExecutionException):
"""
Raised when divisor in a mathematical operation was equal to zero.
"""
EXCEPTION_INDEX = ExceptionList.DivideByZero
def __init__(self, *args, **kwargs):
super(DivideByZeroError, self).__init__('Divide by zero not allowed', *args, **kwargs)
[docs]class PrivilegedInstructionError(ExecutionException__SimpleESR, ExecutionException):
"""
Raised when privileged instruction was about to be executed in non-privileged
mode.
"""
EXCEPTION_INDEX = ExceptionList.PrivilegedInstr
def __init__(self, *args, **kwargs):
super(PrivilegedInstructionError, self).__init__('Attempt to execute privileged instruction', *args, **kwargs)
[docs]class InvalidInstructionSetError(ExecutionException__SimpleESR, ExecutionException):
"""
Raised when switch to unknown or invalid instruction set was requested.
:param int inst_set: instruction set id.
"""
EXCEPTION_INDEX = ExceptionList.InvalidInstSet
def __init__(self, inst_set, *args, **kwargs):
super(InvalidInstructionSetError, self).__init__('Invalid instruction set requested: inst_set={}'.format(inst_set), *args, **kwargs)
self.inst_set = inst_set
[docs]class UnalignedAccessError(ExecutionException__SimpleESR, ExecutionException):
"""
Raised when only properly aligned memory access is allowed, and running code
attempts to access memory without honoring this restriction (e.g. ``LW``
reading from byte-aligned address).
"""
EXCEPTION_INDEX = ExceptionList.UnalignedAccess
def __init__(self, *args, **kwargs):
super(UnalignedAccessError, self).__init__('Invalid memory access alignment', *args, **kwargs)
[docs]class InvalidFrameError(ExecutionException__SimpleESR, ExecutionException):
"""
Raised when VM checks each call stack frame to be cleaned before it's left,
and there are some values on stack when ``ret`` or ``retint`` are executed.
In such case, the actual ``SP`` values does not equal to a saved one, and
exception is raised.
:param u32_t saved_sp: ``SP`` as saved at the moment the frame was created.
:param u32_t current_sp: current ``SP``
"""
def __init__(self, saved_sp, current_sp, *args, **kwargs):
super(InvalidFrameError, self).__init__('Leaving weird frame: saved SP=%s, current SP=%s' % (UINT32_FMT(saved_sp), UINT32_FMT(current_sp)), *args, **kwargs)
self.current_sp = current_sp
self.saved_sp = saved_sp
[docs] def runtime_handle(self):
return False
[docs]class MemoryAccessError(ExecutionException__SimpleESR, ExecutionException):
"""
Raised when MMU decides the requested memory operation is now allowed, e.g.
when page tables are enabled, and corresponding ``PTE`` denies write access.
:param str access: ``read``, ``write`` or ``execute``.
:param u32_t address: address where memory access shuld have happen.
:param ducky.mm.PageTableEntry pte: ``PTE`` guarding this particular memory
location.
"""
EXCEPTION_INDEX = ExceptionList.MemoryAccess
def __init__(self, access, address, pte, *args, **kwargs):
super(MemoryAccessError, self).__init__('Memory access error: access=%s, address=%s, pte=%s' % ((access, UINT32_FMT(address), pte.to_string())))
self.access = access
self.address = address
self.pte = pte.to_string()
[docs]class RegisterAccessError(ExecutionException__SimpleESR, ExecutionException):
"""
Raised when instruction tries to access a registerall which is not available
for requested operation, e.g. writing into read-only control register will
raise this exception.
:param str access: ``read`` or ``write``.
:param str reg: register name.
"""
EXCEPTION_INDEX = ExceptionList.RegisterAccess
def __init__(self, access, reg, *args, **kwargs):
super(RegisterAccessError, self).__init__('Register access error: access=%s, reg=%s' % (access, reg))
self.access = access
self.reg = reg
[docs]class InvalidExceptionError(ExecutionException__SimpleESR, ExecutionException):
"""
Raised when requested exception index is invalid (out of bounds).
"""
EXCEPTION_INDEX = ExceptionList.InvalidException
def __init__(self, exc_index, *args, **kwargs):
super(InvalidExceptionError, self).__init__('Invalid exception index: index=%s' % UINT32_FMT(exc_index))
self.exc_index = exc_index
[docs]class CoprocessorError(ExecutionException__SimpleESR, ExecutionException):
"""
Base class for coprocessor errors. Raised when coprocessors needs to signal
its own exception, when none of alread yavailable exceptions would do.
"""
EXCEPTION_INDEX = ExceptionList.CoprocessorError
def __init__(self, msg, *args, **kwargs):
super(CoprocessorError, self).__init__('Coprocessor error: %s' % msg)