diff --git a/infernal_interpreter/AsciiPainter.py b/infernal_interpreter/AsciiPainter.py index 787f3f0..9992009 100644 --- a/infernal_interpreter/AsciiPainter.py +++ b/infernal_interpreter/AsciiPainter.py @@ -1,25 +1,24 @@ - import subprocess NORMAL_COLOR = '\033[0m' REG_STATUS_TO_COLOR = { - "insert": '\033[32m', - "change": '\033[33m', - "remove": '\033[31m', - "none": '\033[0m', - "jump": '\033[36m' + 'insert': '\033[32m', + 'change': '\033[33m', + 'remove': '\033[31m', + 'none': '\033[0m', + 'jump': '\033[36m', } -class AsciiPainter: - def __init__ (self, registers=None, max_stack_size=8, stack_draw_mode="up"): +class AsciiPainter: + def __init__(self, registers=None, max_stack_size=8, stack_draw_mode='up'): self.registers = registers if registers else REGISTERS self.states = [] self.max_stack_size = max_stack_size self.stack_draw_mode = stack_draw_mode - def saveState (self, emu, line_nr: int | None=None) -> None: + def saveState(self, emu, line_nr: int | None = None) -> None: # Init state state = [] self.states.append(state) @@ -27,87 +26,113 @@ class AsciiPainter: if line_nr is not None: signature = emu.getLineSignature(line_nr) for token in signature: - state.append({ 'state': 'none', 'val': token }) + state.append({'state': 'none', 'val': token}) # - for i in range(3-len(state)): state.append({'state':'none', 'val': ''}) - for i in range(len(state)-3): state.pop() - state.append({'state':'none', 'val':''}) + for i in range(3 - len(state)): + state.append({'state': 'none', 'val': ''}) + for i in range(len(state) - 3): + state.pop() + state.append({'state': 'none', 'val': ''}) # Write registers to state for reg_name in self.registers: - if reg_name =="": - state.append({'state':'none', 'val':''}) + if reg_name == '': + state.append({'state': 'none', 'val': ''}) continue reg_state = emu.regState(reg_name) - state.append({ 'state': reg_state, 'val': emu.getVal(reg_name) }) + state.append({'state': reg_state, 'val': emu.getVal(reg_name)}) # Write stack to state stack_base_pointer = emu.getVal('%rbp') base_sp = emu.getVal('%rsp') - assert(isinstance(emu.getVal('%rsp'), int)) - state.append({'state':'none', 'val': ''}) - if emu.regState('m'+str(base_sp-1)) == 'remove': - state.append({'state': emu.regState('m'+str(base_sp-1)), 'val': emu.stack[base_sp-1]}) + assert isinstance(emu.getVal('%rsp'), int) + state.append({'state': 'none', 'val': ''}) + if emu.regState('m' + str(base_sp - 1)) == 'remove': + state.append( + { + 'state': emu.regState('m' + str(base_sp - 1)), + 'val': emu.stack[base_sp - 1], + }, + ) else: state.append({'state': 'none', 'val': ''}) for index in range(0, self.max_stack_size): stack_i = base_sp + index if stack_i < emu.max_stack_size: - state.append({ 'state': emu.regState("m"+str(stack_i)), 'val': emu.stack[stack_i] }) + state.append( + { + 'state': emu.regState('m' + str(stack_i)), + 'val': emu.stack[stack_i], + }, + ) if stack_base_pointer == index: stack_base_pointer = emu.stack[base_pointer] # TODO: Draw stack frame seperators else: - state.append({'state':'none', 'val': ''}) + state.append({'state': 'none', 'val': ''}) - def getWidthOfColumns(self, number_states: int, number_states_per_block: int, number_state_vars: int) -> list[int]: + def getWidthOfColumns( + self, number_states: int, number_states_per_block: int, number_state_vars: int, + ) -> list[int]: widths = [0 for i in range(0, number_states_per_block)] for base_state in range(0, number_states, number_states_per_block): - for var_i in range(0, number_state_vars): - for state_i in range(0, min(number_states_per_block, number_states - base_state)): - width = len(str(self.states[base_state+state_i][var_i]['val'])) - widths[state_i] = max(widths[state_i], width) + for var_i in range(0, number_state_vars): + for state_i in range( + 0, min(number_states_per_block, number_states - base_state), + ): + width = len(str(self.states[base_state + state_i][var_i]['val'])) + widths[state_i] = max(widths[state_i], width) return widths - def getWidthOfNameColumn (self, number_state_vars: int) -> int: + def getWidthOfNameColumn(self, number_state_vars: int) -> int: widest = 0 for var_i in range(0, number_state_vars): - widest = max(widest, len(self.nameOfVar(var_i-4))) + widest = max(widest, len(self.nameOfVar(var_i - 4))) return widest - def to_string (self, emu) -> str: + def to_string(self, emu) -> str: number_states = len(self.states) number_state_vars = len(self.states[0]) term_width = int(subprocess.check_output(['tput', 'cols'])) - number_states_per_block: int = term_width // (10+2) - 1 + number_states_per_block: int = term_width // (10 + 2) - 1 separator = '-' * term_width output: list[str] = [] - name_fmt = '{}{:'+str(self.getWidthOfNameColumn(number_state_vars))+'} ' - column_fmt = ['{}{:>'+str(width)+'} ' for width in self.getWidthOfColumns(number_states, number_states_per_block, number_state_vars)] + name_fmt = '{}{:' + str(self.getWidthOfNameColumn(number_state_vars)) + '} ' + column_fmt = [ + '{}{:>' + str(width) + '} ' + for width in self.getWidthOfColumns( + number_states, number_states_per_block, number_state_vars, + ) + ] for base_state in range(0, number_states, number_states_per_block): output.append(separator) for var_i in range(0, number_state_vars): - output.append(name_fmt.format(NORMAL_COLOR, self.nameOfVar(var_i-4))) - for state_i in range(0, min(number_states_per_block, number_states - base_state)): - var = self.states[base_state+state_i][var_i] - output.append(column_fmt[state_i].format(REG_STATUS_TO_COLOR[var['state']], var['val'], NORMAL_COLOR)) + output.append(name_fmt.format(NORMAL_COLOR, self.nameOfVar(var_i - 4))) + for state_i in range( + 0, min(number_states_per_block, number_states - base_state), + ): + var = self.states[base_state + state_i][var_i] + output.append( + column_fmt[state_i].format( + REG_STATUS_TO_COLOR[var['state']], var['val'], NORMAL_COLOR, + ), + ) output.append('\n') output.append(separator) return ''.join(output) - def nameOfVar (self, i): + def nameOfVar(self, i): if i < 0: return '' if i < len(self.registers): return self.registers[i] elif i != len(self.registers): - state_i = i-len(self.registers) - 2 + state_i = i - len(self.registers) - 2 return 'TOS{}{}'.format('+' if state_i < 0 else '-', abs(state_i)) else: return '' - def __str__ (self): + def __str__(self): return 'AsciiPainter[]' - diff --git a/infernal_interpreter/Emulator.py b/infernal_interpreter/Emulator.py index c9797fb..0f79fa2 100644 --- a/infernal_interpreter/Emulator.py +++ b/infernal_interpreter/Emulator.py @@ -1,24 +1,40 @@ - import re from . import Junk from .opcodes import OPCODES -REGISTERS=["%rax", "%rbx", "%rcx", "%rdx", "%rsp", "%rbp", "%rsi", "%rdi", - "%r8", "%r9", "%r10", "%r11", "%r12", "%r13", "%r14", "%r15"] +REGISTERS = [ + '%rax', + '%rbx', + '%rcx', + '%rdx', + '%rsp', + '%rbp', + '%rsi', + '%rdi', + '%r8', + '%r9', + '%r10', + '%r11', + '%r12', + '%r13', + '%r14', + '%r15', +] -class CodeParseException (BaseException): - def __init__ (self, line_nr, string): +class CodeParseException(BaseException): + def __init__(self, line_nr, string): self.line_nr = line_nr self.string = string -class InternalExecutionException (BaseException): + +class InternalExecutionException(BaseException): pass -class Emulator: - def __init__ (self, source_text, max_stack_size=1000): +class Emulator: + def __init__(self, source_text, max_stack_size=1000): self.source = source_text self.registers = {} for reg_name in REGISTERS: @@ -29,16 +45,15 @@ class Emulator: self.code = [] self.labels = {} self.changes = {} - self.status = {'i':True} + self.status = {'i': True} self.last_sp = 0 self.max_stack_size = 0 index = 0 for line in iter(source_text.splitlines()): index = self.processSourceLine(line, index) - def setStack (self, *stack_list, **kwargs): - """ - Sets various stack elements, starting from 0 and going up. + def setStack(self, *stack_list, **kwargs): + """Sets various stack elements, starting from 0 and going up. Automatically sets rsp. This can be disabled by passing set_rsp=False. """ i = len(self.stack) @@ -46,33 +61,33 @@ class Emulator: i -= 1 self.stack[i] = element self.setRegs(rsp=i) - self.last_sp = i-1 + self.last_sp = i - 1 - def setRegs (self, **reg_dict): + def setRegs(self, **reg_dict): for reg_name, reg_val in reg_dict.items(): - assert(reg_name[0] != '%') - self.registers["%"+reg_name] = reg_val + assert reg_name[0] != '%' + self.registers['%' + reg_name] = reg_val - def getVal (self, val_text): - if val_text[0] == "$": + def getVal(self, val_text): + if val_text[0] == '$': return int(val_text[1:]) - elif val_text[0] == "%": + elif val_text[0] == '%': return self.registers[val_text] else: - raise ValueError('{} is not an usable value name'.format(val_text)) + raise ValueError(f'{val_text} is not an usable value name') - def compareVal (self, valT1, valT2): + def compareVal(self, valT1, valT2): val1 = self.getVal(valT2) val2 = self.getVal(valT1) - if isinstance(val1,Junk.Junk) or isinstance(val2,Junk.Junk): - self.status["i"] = (True,) + if isinstance(val1, Junk.Junk) or isinstance(val2, Junk.Junk): + self.status['i'] = (True,) return - self.status["g"] = val1>val2 - self.status["l"] = val1 val2 + self.status['l'] = val1 < val2 + self.status['e'] = val1 == val2 + self.status['i'] = False - def changedRegisters (self, *args): + def changedRegisters(self, *args): for reg, val in args: self.changes[reg] = val @@ -84,35 +99,42 @@ class Emulator: self.labels[tokens[0][:-1]] = index tokens = tokens[1:] for i in range(len(tokens)): - if tokens[i][0] == "#": + if tokens[i][0] == '#': tokens = tokens[:i] break self.code.append(tokens) return index + 1 - def regState (self, reg_name): + def regState(self, reg_name): if reg_name in self.changes: return self.changes[reg_name] else: - return "none" + return 'none' - def getLineSignature (self, line_nr): - return [ "\\"+token if token[0]=="$" or token[0]=="%" else token for token in self.code[line_nr] ] + def getLineSignature(self, line_nr): + return [ + '\\' + token if token[0] == '$' or token[0] == '%' else token + for token in self.code[line_nr] + ] - def pushToStack (self, new_element): + def pushToStack(self, new_element): self.registers['%rsp'] -= 1 self.stack[self.registers['%rsp']] = new_element - self.changedRegisters(('%rsp',"change"), ("m"+str(self.registers['%rsp']),"insert")) + self.changedRegisters( + ('%rsp', 'change'), ('m' + str(self.registers['%rsp']), 'insert'), + ) self.max_stack_size = max(self.registers['%rsp'], self.max_stack_size) - def popFromStack (self): + def popFromStack(self): temp = self.stack[self.registers['%rsp']] - self.changedRegisters(('%rsp',"change"),("m"+str(self.registers['%rsp']),"remove")) + self.changedRegisters( + ('%rsp', 'change'), ('m' + str(self.registers['%rsp']), 'remove'), + ) self.registers['%rsp'] += 1 return temp - def jump (self, label, cond_op="or", **conditions): - if len(conditions)>0 and self.status["i"]: + def jump(self, label, cond_op='or', **conditions): + if len(conditions) > 0 and self.status['i']: raise Junk.JunkComparisonException(*self.status['i']) and_, or_ = True, False for cnd_name, cnd_val in conditions.items(): @@ -120,19 +142,22 @@ class Emulator: or_ = True else: and_ = False - if or_ if cond_op=="or" else and_: + if or_ if cond_op == 'or' else and_: self.registers['%rip'] = self.labels[label] return True else: return False - def iterate (self): + def iterate(self): old_rip = self.registers['%rip'] self.last_sp = self.registers['%rsp'] - if isinstance(old_rip , Junk.Junk) or old_rip >= len(self.code): + if isinstance(old_rip, Junk.Junk) or old_rip >= len(self.code): return None if not isinstance(self.registers['%rip'], int): - raise InternalExecutionException("Register %rip should be integer, but was "+str(self.registers['%rip'])) + raise InternalExecutionException( + 'Register %rip should be integer, but was ' + + str(self.registers['%rip']), + ) instruct = self.code[self.registers['%rip']] opcode = instruct[0] self.changes = {} @@ -142,7 +167,7 @@ class Emulator: if self.registers['%rip'] == old_rip: self.registers['%rip'] += 1 else: - self.changedRegisters(('%rip',"jump")) + self.changedRegisters(('%rip', 'jump')) return old_rip @@ -156,6 +181,9 @@ class Emulator: else: return output - def getUsedRegisters (self): - return [reg_name for reg_name, reg_val in self.registers.items() if not isinstance(reg_val, Junk.Junk)] - + def getUsedRegisters(self): + return [ + reg_name + for reg_name, reg_val in self.registers.items() + if not isinstance(reg_val, Junk.Junk) + ] diff --git a/infernal_interpreter/Junk.py b/infernal_interpreter/Junk.py index 1d580f4..a2ba46c 100644 --- a/infernal_interpreter/Junk.py +++ b/infernal_interpreter/Junk.py @@ -1,33 +1,32 @@ - import dataclasses -class JunkComparisonException(BaseException): - def __str__ (self): - return "Attempting to perform calculations involving junk values." +class JunkComparisonException(BaseException): + def __str__(self): + return 'Attempting to perform calculations involving junk values.' + @dataclasses.dataclass class Junk: represents: str | None = None - def __str__ (self): + def __str__(self): return '[{}]'.format(self.represents or 'junk') - def __repr__ (self): + def __repr__(self): return self.__str__() - def __format__ (self, format): + def __format__(self, format): return self.__str__() - def __add__ (self, other): + def __add__(self, other): return self - def __radd__ (self, other): + def __radd__(self, other): return self - def __sub__ (self, other): + def __sub__(self, other): return self - def __rsub__ (self, other): + def __rsub__(self, other): return self - diff --git a/infernal_interpreter/TikzPainter.py b/infernal_interpreter/TikzPainter.py index f83218c..2b2e71c 100644 --- a/infernal_interpreter/TikzPainter.py +++ b/infernal_interpreter/TikzPainter.py @@ -1,21 +1,22 @@ - import re REG_STATUS_TO_COLOR = { - "insert": "green", - "change": "yellow", - "remove": "red", - "none": "black", - "jump": "orange" + 'insert': 'green', + 'change': 'yellow', + 'remove': 'red', + 'none': 'black', + 'jump': 'orange', } DEFAULT_OPTIONS = """register_node/.style={rectangle, draw=black!30, fill=black!5, very thick, minimum size=0.5, minimum width=20mm}, text_node/.style={}""" -class TikzPainter: - def __init__ (self, registers=None, max_stack_size=8, stack_draw_mode="up", options=None): +class TikzPainter: + def __init__( + self, registers=None, max_stack_size=8, stack_draw_mode='up', options=None, + ): self.registers = registers if registers else REGISTERS self.text = [] self.pos_x = 0 @@ -23,58 +24,80 @@ class TikzPainter: self.stack_draw_mode = stack_draw_mode self.options = options if options else DEFAULT_OPTIONS - def addText (self, str, *args): - text = re.sub(r"[^\\]%", "\\%", str.format(*args)) + def addText(self, str, *args): + text = re.sub(r'[^\\]%', '\\%', str.format(*args)) self.text.append(text) - def getRegColor (self, reg_state): + def getRegColor(self, reg_state): return REG_STATUS_TO_COLOR[reg_state] - def drawStackUpward (self, pos, emu, x): + def drawStackUpward(self, pos, emu, x): base_pointer = emu.getVal('%rbp') pos -= 1.5 base_sp = min(emu.last_sp, emu.getVal('%rsp')) - pos += 0.5 * max(0, emu.getVal('%rsp')-emu.last_sp) - assert(isinstance(emu.getVal('%rsp'), int)) - for index in range(base_sp, 3+min(emu.max_stack_size, emu.getVal('%rsp')+self.max_stack_size)): - reg_state = emu.regState("m"+str(index)) - reg_dect = "register_node, fill="+self.getRegColor(reg_state)+"!10" - self.addText("\t\\node[{}]() at ({}, {}){{{}}};\n", - reg_dect, x, pos, emu.stack[index]) + pos += 0.5 * max(0, emu.getVal('%rsp') - emu.last_sp) + assert isinstance(emu.getVal('%rsp'), int) + for index in range( + base_sp, + 3 + min(emu.max_stack_size, emu.getVal('%rsp') + self.max_stack_size), + ): + reg_state = emu.regState('m' + str(index)) + reg_dect = 'register_node, fill=' + self.getRegColor(reg_state) + '!10' + self.addText( + '\t\\node[{}]() at ({}, {}){{{}}};\n', + reg_dect, + x, + pos, + emu.stack[index], + ) if base_pointer == index: - self.addText("\t\\draw ({0},{2}) -- ({1},{2});\n",x-1.1,x+1.1,pos-0.25) + self.addText( + '\t\\draw ({0},{2}) -- ({1},{2});\n', x - 1.1, x + 1.1, pos - 0.25, + ) base_pointer = emu.stack[base_pointer] pos -= 0.5 return pos, x - def drawStackDownward (self, pos, emu, x): + def drawStackDownward(self, pos, emu, x): pos -= 1 - for index in range(emu.getVal('%rsp')-8, max(emu.last_sp, emu.getVal('%rsp'))): - reg_state = emu.regState("m"+str(index)) - reg_dect = "register_node, fill="+self.getRegColor(reg_state)+"!10" - self.addText("\t\\node[{}]() at ({}, {}){{{}}};\n", - reg_dect, x, pos, emu.stack[index]) + for index in range( + emu.getVal('%rsp') - 8, max(emu.last_sp, emu.getVal('%rsp')), + ): + reg_state = emu.regState('m' + str(index)) + reg_dect = 'register_node, fill=' + self.getRegColor(reg_state) + '!10' + self.addText( + '\t\\node[{}]() at ({}, {}){{{}}};\n', + reg_dect, + x, + pos, + emu.stack[index], + ) pos -= 0.5 return pos, x - def saveState (self, emu, line_nr=None): - x = (self.pos_x+1)*2.5-0.5 + def saveState(self, emu, line_nr=None): + x = (self.pos_x + 1) * 2.5 - 0.5 self.pos_x += 1 # Draw register cells pos = 0.5 for reg_name in self.registers: pos -= 0.5 - if reg_name =="": + if reg_name == '': continue reg_state = emu.regState(reg_name) - reg_dect = "register_node, fill="+self.getRegColor(reg_state)+"!10" - self.addText("\t\\node[{}]() at ({}, {}){{{}}};\n", - reg_dect, x, pos, emu.getVal(reg_name)) + reg_dect = 'register_node, fill=' + self.getRegColor(reg_state) + '!10' + self.addText( + '\t\\node[{}]() at ({}, {}){{{}}};\n', + reg_dect, + x, + pos, + emu.getVal(reg_name), + ) # Draw stack - if self.stack_draw_mode == "up": + if self.stack_draw_mode == 'up': pos, x = self.drawStackUpward(pos, emu, x) else: pos, x = self.drawStackDownward(pos, emu, x) @@ -85,45 +108,49 @@ class TikzPainter: pos = 2 signature = emu.getLineSignature(line_nr) for token in signature: - self.addText("\t\\node[text_node]() at ({}, {}){{{}}};\n", - x-1.25, pos, token) + self.addText( + '\t\\node[text_node]() at ({}, {}){{{}}};\n', x - 1.25, pos, token, + ) pos -= 0.5 - def drawNames (self, emu): + def drawNames(self, emu): x = 0 pos = 0 for reg_name in self.registers: - self.addText("\t\\node[text_node]() at ({}, {}){{{}}};\n", - x, pos, reg_name[1:]) + self.addText( + '\t\\node[text_node]() at ({}, {}){{{}}};\n', x, pos, reg_name[1:], + ) pos -= 0.5 # Draw stack - if self.stack_draw_mode == "up": + if self.stack_draw_mode == 'up': self.drawStackNamesUpward(pos, emu, x) else: self.drawStackNamesDownward(pos, emu, x) - def drawStackNamesUpward (self, pos, emu, x): - self.addText("\t\\node[text_node]() at ({}, {}){{TOS+1}};\n", - x, pos-0.5) - self.addText("\t\\node[text_node]() at ({}, {}){{TOS}};\n", - x, pos-1.0) + def drawStackNamesUpward(self, pos, emu, x): + self.addText('\t\\node[text_node]() at ({}, {}){{TOS+1}};\n', x, pos - 0.5) + self.addText('\t\\node[text_node]() at ({}, {}){{TOS}};\n', x, pos - 1.0) pos -= 1 - for index in range(1,self.max_stack_size): + for index in range(1, self.max_stack_size): pos -= 0.5 - self.addText("\t\\node[text_node]() at ({}, {}){{TOS-{}}};\n", - x, pos, index) + self.addText( + '\t\\node[text_node]() at ({}, {}){{TOS-{}}};\n', x, pos, index, + ) - def to_string (self, emu): - PRE = """ \\documentclass{standalone} + def to_string(self, emu): + PRE = ( + """ \\documentclass{standalone} \\usepackage{tikz} \\begin{document} - \\begin{tikzpicture}["""+self.options+"]" + \\begin{tikzpicture}[""" + + self.options + + ']' + ) POST = """\\end{tikzpicture} \\end{document}""" self.drawNames(emu) - return PRE + "".join(self.text) + POST + return PRE + ''.join(self.text) + POST - def __str__ (self): + def __str__(self): return 'TikzPainter[]' - diff --git a/infernal_interpreter/__main__.py b/infernal_interpreter/__main__.py index b8128de..74af28e 100644 --- a/infernal_interpreter/__main__.py +++ b/infernal_interpreter/__main__.py @@ -1,37 +1,60 @@ #!/usr/bin/python2 +import argparse +import re import sys -import re -import argparse - -from .Emulator import Emulator, REGISTERS -from .TikzPainter import TikzPainter -from .AsciiPainter import AsciiPainter from . import Junk +from .AsciiPainter import AsciiPainter +from .Emulator import REGISTERS, Emulator +from .TikzPainter import TikzPainter -ARG_DESC = ''' +ARG_DESC = """ A silly x86-64 emulator and stack visualizer. -''' +""" -PAINTER_ID_TO_CLASS = { - 'ascii': AsciiPainter, - 'tikz': TikzPainter -} +PAINTER_ID_TO_CLASS = {'ascii': AsciiPainter, 'tikz': TikzPainter} -def parse_args (): - parser = argparse.ArgumentParser(description = ARG_DESC) - parser.add_argument('-i', '--input-file', default='-', help = 'File to use as input', dest = 'input-file') - parser.add_argument('-o', '--output-file', default='-', help = 'File to use as output', dest = 'output-file') - parser.add_argument('-p', '--painter', default='ascii', help = 'Drawer to use', dest = 'painter-name', choices=PAINTER_ID_TO_CLASS.keys()) + +def parse_args(): + parser = argparse.ArgumentParser(description=ARG_DESC) + parser.add_argument( + '-i', + '--input-file', + default='-', + help='File to use as input', + dest='input-file', + ) + parser.add_argument( + '-o', + '--output-file', + default='-', + help='File to use as output', + dest='output-file', + ) + parser.add_argument( + '-p', + '--painter', + default='ascii', + help='Drawer to use', + dest='painter-name', + choices=PAINTER_ID_TO_CLASS.keys(), + ) for register in REGISTERS: - parser.add_argument('--'+register[1:], nargs = 1, type = int, default = None, dest = register, help = 'the initial value of the register') + parser.add_argument( + '--' + register[1:], + nargs=1, + type=int, + default=None, + dest=register, + help='the initial value of the register', + ) args = vars(parser.parse_args()) # Determine args # registers_init = {} for register in REGISTERS: - registers_init[register[1:]] = Junk.Junk('old '+register[1:]) + registers_init[register[1:]] = Junk.Junk('old ' + register[1:]) # registers_init['rip'] = 0 registers_init['rsp'] = 0 @@ -42,7 +65,7 @@ def parse_args (): registers_init[register[1:]] = args[register][0] # - program = "" + program = '' if args['input-file'] == '-': program = sys.stdin.read() else: @@ -62,20 +85,20 @@ def parse_args (): ## Return return (program, registers_init, output_file, painter_class) -def main (): +def main(): (program, registers_init, output_file, painter_class) = parse_args() # Determine registers to display - registers_to_draw = ["%rip","%rbp","%rsp", ""] - for match in re.findall(r"%r[a-z0-9]{1,2}", program): + registers_to_draw = ['%rip', '%rbp', '%rsp', ''] + for match in re.findall(r'%r[a-z0-9]{1,2}', program): if match not in registers_to_draw: registers_to_draw.append(match) # Setup emulator and drawer emu = Emulator(program) emu.setRegs(**registers_init) - emu.setStack(Junk.Junk("junk..."), Junk.Junk("call eip")) + emu.setStack(Junk.Junk('junk...'), Junk.Junk('call eip')) painter = painter_class(registers_to_draw) @@ -88,6 +111,6 @@ def main (): output_file.write(painter.to_string(emu)) output_file.close() -if __name__ == "__main__": - main() +if __name__ == '__main__': + main() diff --git a/infernal_interpreter/opcodes.py b/infernal_interpreter/opcodes.py index c053a6c..608b579 100644 --- a/infernal_interpreter/opcodes.py +++ b/infernal_interpreter/opcodes.py @@ -1,88 +1,109 @@ - OPCODES = {} -def add_opcode (*opcode_names): - def wrapped (func): + +def add_opcode(*opcode_names): + def wrapped(func): for name in opcode_names: OPCODES[name] = func + return wrapped + ################################################################################ # Stack -@add_opcode("push","pushq") + +@add_opcode('push', 'pushq') def push(emu, value): emu.pushToStack(emu.getVal(value)) -@add_opcode("pop","popq") + +@add_opcode('pop', 'popq') def pop(emu, target_reg): emu.registers[target_reg] = emu.popFromStack() - emu.changedRegisters((target_reg,"change")) + emu.changedRegisters((target_reg, 'change')) + # Arithmetic -@add_opcode("mov","movq") + +@add_opcode('mov', 'movq') def mov(emu, from_reg, target_reg): emu.registers[target_reg] = emu.getVal(from_reg) - emu.changedRegisters((target_reg,"change")) + emu.changedRegisters((target_reg, 'change')) -@add_opcode("add","addq") + +@add_opcode('add', 'addq') def add(emu, add_val, target_reg): emu.registers[target_reg] += emu.getVal(add_val) - emu.changedRegisters((target_reg,"change")) + emu.changedRegisters((target_reg, 'change')) -@add_opcode("sub","subq") + +@add_opcode('sub', 'subq') def sub(emu, sub_val, target_reg): emu.registers[target_reg] -= emu.getVal(sub_val) - emu.changedRegisters((target_reg,"change")) + emu.changedRegisters((target_reg, 'change')) + # Comparison and conditional jumping -@add_opcode("cmp","cmpq") + +@add_opcode('cmp', 'cmpq') def comp(emu, val1, val2): emu.compareVal(val1, val2) -@add_opcode("jg") + +@add_opcode('jg') def jg(emu, label): emu.jump(label, g=True) -@add_opcode("jl") + +@add_opcode('jl') def jl(emu, label): emu.jump(label, l=True) -@add_opcode("je") + +@add_opcode('je') def je(emu, label): emu.jump(label, e=True) -@add_opcode("jge") + +@add_opcode('jge') def jge(emu, label): - emu.jump(label, "or", g=True, e=True) + emu.jump(label, 'or', g=True, e=True) -@add_opcode("jle") + +@add_opcode('jle') def jle(emu, label): - emu.jump(label, "or", l=True, e=True) + emu.jump(label, 'or', l=True, e=True) -@add_opcode("jne") + +@add_opcode('jne') def jne(emu, label): emu.jump(label, e=False) + # Unconditional Jumping -@add_opcode("jmp") + +@add_opcode('jmp') def jmp(emu, label): emu.registers['%rip'] = emu.labels[label] -@add_opcode("call") + +@add_opcode('call') def call(emu, label): - emu.pushToStack(emu.registers['%rip']+1) + emu.pushToStack(emu.registers['%rip'] + 1) emu.registers['%rip'] = emu.labels[label] -@add_opcode("leave") -def leave(emu): - emu.registers["%rsp"] = emu.registers["%rbp"] - emu.registers["%rbp"] = emu.popFromStack() -@add_opcode("ret") +@add_opcode('leave') +def leave(emu): + emu.registers['%rsp'] = emu.registers['%rbp'] + emu.registers['%rbp'] = emu.popFromStack() + + +@add_opcode('ret') def ret(emu): emu.registers['%rip'] = emu.popFromStack() diff --git a/setup.py b/setup.py index 04a9f56..6f1c842 100644 --- a/setup.py +++ b/setup.py @@ -41,7 +41,10 @@ this stuff is worth it, you can buy me a beer in return. Jon Michael Aanes ```""" -PACKAGE_DESCRIPTION_SHORT='Simple interpreter and stack tracer for the AMD x86-64 ABI.' +PACKAGE_DESCRIPTION_SHORT = ( + 'Simple interpreter and stack tracer for the AMD x86-64 ABI.' +) + def parse_version_file(text: str) -> str: match = re.match(r'^__version__\s*=\s*(["\'])([\d\.]+)\1$', text) diff --git a/tests/tests.py b/tests/tests.py index b07f079..af1daf5 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -1,6 +1,7 @@ import os import sys import traceback + sys.path.insert(1, os.path.join(sys.path[0], '..')) import infernal @@ -9,14 +10,17 @@ import infernal tests = [] + def add_test(name, result, register, code): tests.append((name, result, register, code)) -def printf (str, *args): + +def printf(str, *args): print(str.format(*args)) + def execute_tests(): - print("Executing tests!") + print('Executing tests!') total_tests = 0 failed_tests = 0 error_tests = 0 @@ -25,94 +29,152 @@ def execute_tests(): line_nr = None try: emu = infernal.Emulator(code) - emu.setStack("junk...", "calling eip") - emu.setRegs( rip = 0, rbp = 'old bp') + emu.setStack('junk...', 'calling eip') + emu.setRegs(rip=0, rbp='old bp') except infernal.CodeParseException as e: error_tests += 1 - printf("Encountered error when parsing {}, at line {}: {}", - name, e.line_nr, e.str) + printf( + 'Encountered error when parsing {}, at line {}: {}', + name, + e.line_nr, + e.str, + ) try: for line_nr in emu: pass if isinstance(result, BaseException): - printf("Error should have happened in {}, but did not", name) + printf('Error should have happened in {}, but did not', name) failed_tests += 1 output = emu.getVal(register) if output != result: failed_tests += 1 - printf("Failed in {}. {} was {}, should be {}", - name, register, output, result) + printf( + 'Failed in {}. {} was {}, should be {}', + name, + register, + output, + result, + ) except BaseException as e: print(e) if not isinstance(result, BaseException) or not isinstance(e, result): error_tests += 1 - printf("Encountered error in {}, at operation {}: {}", - name, emu.getVal('%rip'), e) + printf( + 'Encountered error in {}, at operation {}: {}', + name, + emu.getVal('%rip'), + e, + ) traceback.print_exc() - printf("Tests done! {}/{}.", - total_tests - failed_tests - error_tests, total_tests) - printf("{} failed, and {} encountered a python error", - failed_tests, error_tests) + printf('Tests done! {}/{}.', total_tests - failed_tests - error_tests, total_tests) + printf('{} failed, and {} encountered a python error', failed_tests, error_tests) + ################################################################################ # Arithmetic Operations -add_test("constant $255", 255, "%rsi", """ +add_test( + 'constant $255', + 255, + '%rsi', + """ movq $255, %rsi -""") +""", +) -add_test("static addition 10+$20", 30, "%rsi", """ +add_test( + 'static addition 10+$20', + 30, + '%rsi', + """ movq $10, %rsi addq $20, %rsi -""") +""", +) -add_test("register addition 10+20", 30, "%rsi", """ +add_test( + 'register addition 10+20', + 30, + '%rsi', + """ movq $10, %rsi movq $20, %rax addq %rax, %rsi -""") +""", +) -add_test("register subtraction 10-20", -10, "%rsi", """ +add_test( + 'register subtraction 10-20', + -10, + '%rsi', + """ movq $10, %rsi movq $20, %rax subq %rax, %rsi -""") +""", +) ################################################################################ # Branching branch_tests = [ - ("jg",10,">",5,0), ("jg",5,">",5,1), ("jg",5,">",10,1), - ("jl",10,"<",5,1), ("jl",5,"<",5,1), ("jl",5,"<",10,0), - ("je",10,"==",5,1),("je",5,"==",5,0),("je",5,"==",10,1), - ("jge",10,">=",5,0),("jge",5,">=",5,0),("jge",5,">=",10,1), - ("jle",10,"<=",5,1),("jle",5,"<=",5,0),("jle",5,"<=",10,0), - ("jne",10,"!=",5,0),("jne",5,"!=",5,1),("jne",5,"!=",10,0), + ('jg', 10, '>', 5, 0), + ('jg', 5, '>', 5, 1), + ('jg', 5, '>', 10, 1), + ('jl', 10, '<', 5, 1), + ('jl', 5, '<', 5, 1), + ('jl', 5, '<', 10, 0), + ('je', 10, '==', 5, 1), + ('je', 5, '==', 5, 0), + ('je', 5, '==', 10, 1), + ('jge', 10, '>=', 5, 0), + ('jge', 5, '>=', 5, 0), + ('jge', 5, '>=', 10, 1), + ('jle', 10, '<=', 5, 1), + ('jle', 5, '<=', 5, 0), + ('jle', 5, '<=', 10, 0), + ('jne', 10, '!=', 5, 0), + ('jne', 5, '!=', 5, 1), + ('jne', 5, '!=', 10, 0), ] for jump_instruct, a, comp, b, result in branch_tests: - add_test("branch {a} {comp} {b}={result}".format(a=a,comp=comp,b=b,result=not result), result, "%rsi", """ + add_test( + f'branch {a} {comp} {b}={not result}', + result, + '%rsi', + f""" start: cmpq ${b}, ${a} {jump_instruct} true movq $1, %rsi jmp return true: movq $0, %rsi return: ret - """.format(a=a,b=b,jump_instruct=jump_instruct)) + """, + ) ################################################################################ # Junk Comparisons -add_test("invalid comparison 1", infernal.JunkComparisonException, "None", """ +add_test( + 'invalid comparison 1', + infernal.JunkComparisonException, + 'None', + """ start: movq $100, %rsp # Set stack pointer to a random position. popq %rsi # Move a Junk value into %rsi cmpq $10, %rsi # Do a Junk comparison, which triggers the `i` comp # virtual register. jg start # Attempt to do a jump to the start, if (Junk-10)>0, # which makes no sense, and thus throws an error. -""") +""", +) -add_test("invalid addition 1", infernal.JunkComparisonException, "None", """ +add_test( + 'invalid addition 1', + infernal.JunkComparisonException, + 'None', + """ start: movq $100, %rsp # Set stack pointer to a random position. popq %rsi # Move a Junk value into %rsi addq %rsi, %rsp # Adds Junk to 101, which produces Junk. @@ -120,9 +182,14 @@ start: movq $100, %rsp # Set stack pointer to a random position. # virtual register. jg start # Attempt to do a jump to the start, if (Junk-10)>0, # which makes no sense, and thus throws an error. -""") +""", +) -add_test("invalid addition 2", infernal.JunkComparisonException, "None", """ +add_test( + 'invalid addition 2', + infernal.JunkComparisonException, + 'None', + """ start: movq $100, %rsp # Set stack pointer to a random position. popq %rsi # Move a Junk value into %rsi addq %rsp, %rsi # Adds 101 to Junk, which produces Junk. @@ -130,9 +197,14 @@ start: movq $100, %rsp # Set stack pointer to a random position. # virtual register. jg start # Attempt to do a jump to the start, if (Junk-10)>0, # which makes no sense, and thus throws an error. -""") +""", +) -add_test("invalid subtraction 1", infernal.JunkComparisonException, "None", """ +add_test( + 'invalid subtraction 1', + infernal.JunkComparisonException, + 'None', + """ start: movq $100, %rsp # Set stack pointer to a random position. popq %rsi # Move a Junk value into %rsi subq %rsi, %rsp # Adds Junk to 101, which produces Junk. @@ -140,9 +212,14 @@ start: movq $100, %rsp # Set stack pointer to a random position. # virtual register. jg start # Attempt to do a jump to the start, if (Junk-10)>0, # which makes no sense, and thus throws an error. -""") +""", +) -add_test("invalid subtraction 2", infernal.JunkComparisonException, "None", """ +add_test( + 'invalid subtraction 2', + infernal.JunkComparisonException, + 'None', + """ start: movq $100, %rsp # Set stack pointer to a random position. popq %rsi # Move a Junk value into %rsi subq %rsp, %rsi # Adds 101 to Junk, which produces Junk. @@ -150,9 +227,10 @@ start: movq $100, %rsp # Set stack pointer to a random position. # virtual register. jg start # Attempt to do a jump to the start, if (Junk-10)>0, # which makes no sense, and thus throws an error. -""") +""", +) ################################################################################ -if __name__ == "__main__": +if __name__ == '__main__': execute_tests()