Assembly Simulator in Python
--D. Thiebaut (talk) 17:49, 24 November 2015 (EST)
Video
Python Simulator
# assemblySimulator.py # D. Thiebaut # This program allows the display of the stack # as a recursive factorial program computes the # factorial of 3. # The program writes the stack to a file, as well # as the contents of the data registers to a file, # so the best way to display them is to "watch" the # files stack.txt and registers.txt in two separate # terminal windows. Make sure the "watch" linux # command is installed on your system. import sys, os program=""" extern _printDec extern _println ;;; ------------------------- ;;; code area ;;; ------------------------- section .text global _start ;;; ; --------------------------------------------------------- ;;; ; main program ;;; ; --------------------------------------------------------- _start: mov eax, 4 ; n = 4 push eax ; TOS <-- XX push eax ; TOS <-- n call fact pop eax ; get n in eax ;;; return to OS mov eax, 1 mov ebx, 0 int 0x80 ;;; ; --------------------------------------------------------- fact: push ebp ; TOS <-- old ebp mov ebp, esp ;;; test for stopping condition cmp dword[ebp+8], 1 ; n==1 ? jg .recurse mov dword[ebp+12], 1 ; yes, then n! = 1 pop ebp ; return to caller ret 4 ;;; recursive step .recurse: push eax ; TOS <-- old eax push ebx ; TOS <-- old ebx push edx ; TOS <-- old edx mov eax, dword[ebp+8] dec eax ; n-1 in eax push eax ; TOS <-- XX push eax ; TOS <-- n-1 call fact ; recursive call pop ebx ; get fact(n-1) in ebx mov eax, dword[ebp+8] mul ebx ; edx:eax <-- n * (n-1)! mov dword[ebp+12], eax ; that's n! ; drop edx pop edx ; restore regs pop ebx pop eax pop ebp ; return ret 4 """ instructions = [ "ret", "call", "pop", "push", "dec", "mov", "mul", "cmp", "jg", "int" ] registers = {"eax":0, "ebx":0, "ecx":0, "edx":0, "esp":0, "ebp":0 } stack = [0] * 40 flags = { "l":True, "lt":True, "e":True, "ne":True, "g":True, "ge":True } class Listing: """The listing class. Initializes itself with the lines from an assembly program. and disassembles and executes the different instructions in the program. """ def __init__( self, program ): self.lines = [] for line in program.split( "\n" ): self.lines.append( line.rstrip() ) self.stack = [] self.lastLine = len( self.lines )-1 self.ip = 0 for i,line in enumerate( self.lines ): if line.find( "_start:" ) != -1: self.ip = i break # find all labels self.labelAddress = {} for i, line in enumerate( self.lines ): if line.find( ":" ) != -1: label = line.split( ":" )[0] self.labelAddress[label] = i def display( self ): for i, line in enumerate( self.lines ): if i < max( self.ip-10, 0 ): continue if i > min( self.lastLine+1, self.ip+10 ): continue line = self.lines[i] if i == self.ip: try: line = line[0:9] + "-->" + line[12:] except: print( "Error: line = ", line ) line = line print( "{0:3}: {1:1}".format(i, line ) ) def __str__( self ): s = "Ip = " + str( self.ip ) + "\n" s += "line[Ip] = " + str( self.lines[self.ip] ) + "\n" return s def containsInstr( self, line ): for instr in instructions: if line.lower().find( instr.lower()+" " ) != -1: return True return False def getDest( self, line ): line = line.split( ";" )[0].strip() dest = line.split()[-1] return dest def nextIp( self ): ip = self.ip while True: ip += 1 line = self.lines[ip] if ip >= len( self.lines ): print( "\n\nException. IP running wild!\n\n" ) sys.exit() if self.containsInstr( line ): return ip def step( self ): # execute the current line isJump = self.execute() if isJump: return # otherwise, advance ip to the next line # that contains an instruction line = self.lines[ self.ip ] self.ip = self.nextIp() def push( self, item, comment=None ): registers[ "esp" ] -= 1 if comment==None: stack[ registers[ "esp" ] ] = item else: stack[ registers[ "esp" ] ] = \ "{0:1} {1:1}".format( item, comment ) def pop( self ): item = stack[ registers[ "esp" ] ] if type( item )==type( "string" ): item = int( item.split()[0].strip() ) registers[ "esp" ] += 1 return item def execute( self ): line = self.lines[ self.ip ] # remove comments stackComment = None if line.find( ";" ) != -1: comment = line.split( ";" )[-1] line = line.split( ";" )[0] #print( "line = ", line ) #print( "comment = ", comment ) if comment.find( "TOS <--" ) != -1: stackComment = comment.split( "TOS <--" )[-1] #print( "Stack Comment = ", stackComment ) line = line.lower() # remove label label = None if line.find( ":" ) != -1: label = line.split( ":" )[0].strip() line = line.split( ":" )[1] fields = line.split( ) instruction = fields[0] if len( fields )==3: operand1 = fields[1].replace( ',','' ).strip() operand2 = fields[2].replace( ',','' ).strip() if len( fields )==2: operand1 = fields[-1].replace( ',','' ).strip() operand2 = None if len( fields )==1: operand1 = None operand2 = None #print( "label=", label, " inst=", instruction, " op1=", operand1, # "op2=", operand2 ) # int 0x80? if instruction == "int" and operand1 =="0x80": print( "\n\nDone!\n\n" ) sys.exit() # call label? if instruction == "call": label = self.getDest( line ) #print( "call to", label ) self.push( self.nextIp(), "*" ) self.ip = self.labelAddress[ label ] return True # is a jump # push if instruction == "push": if operand1 in registers.keys(): self.push( registers[ operand1 ], stackComment ) else: print( "*** Error: trying to push ", operand1 ) return False # pop if instruction == "pop": if operand1 in registers.keys(): data = self.pop() registers[ operand1 ] = data else: print( "*** Error: trying to pop into ", operand1 ) return False # mov if instruction == "mov": #print( "mov: instr=", instruction, "op1=", operand1, # "op2=", operand2 ) if operand2 in registers.keys(): data = int( registers[operand2] ) elif operand2.find( "ebp+" )!=-1: operand2 = operand2.replace( "]", "" ) offset = int( operand2.split( "ebp+" )[1] ) data = stack[ registers["ebp"] + offset//4 ] if type(data)==type("string"): data = int( data.split()[0].strip() ) #print( "mov operand2 = ", operand2, "offset = ", offset ) else: data = int( operand2 ) #print( "mov: data = ", data ) if operand1 in registers.keys(): registers[ operand1 ] = data #print( "mov: setting", operand1, "to", data ) elif operand1.find( "ebp+" )!=-1: operand1 = operand1.replace( "]", "" ) offset = int( operand1.split( "ebp+")[1] ) stack[ registers["ebp"] + offset//4 ] = data return False # dec if instruction == "dec": if operand1 in registers.keys(): registers[operand1] -= 1 else: print( "*** Error *** trying to decrement", operand1 ) return False # mul if instruction == "mul": if operand1 in registers.keys(): registers["eax"] = registers[operand1] * registers["eax"] registers["edx"] = 0 return False # cmp if instruction == "cmp": if operand2 in registers.keys(): data = int( registers[operand2] ) elif operand2.find( "ebp+" )!=-1: operand2 = operand2.replace( "]", "" ) offset = int( operand2.split( "ebp+" )[1] ) data = stack[ registers["ebp"] + offset//4 ] if type(data)==type("string"): data = int( data.split()[0].strip() ) else: data = int( operand2 ) if operand1 in registers.keys(): #print( "cmp: operand1 = ", operand1, "value = ", # registers[ operand1 ] ) flags["l"] = registers[ operand1 ] < data flags["le"] = registers[ operand1 ] <= data flags["e"] = registers[ operand1 ] == data flags["ne"] = registers[ operand1 ] != data flags["g"] = registers[ operand1 ] > data flags["ge"] = registers[ operand1 ] >= data elif operand1.find( "ebp+" )!=-1: operand1 = operand1.replace( "]", "" ) offset = int( operand1.split( "ebp+")[1] ) operand1 = stack[ registers["ebp"] + offset//4 ] if type(operand1)==type("string"): operand1 = int( operand1.split()[0].strip() ) #print( "cmp: operand1 = ", operand1 ) flags["l"] = operand1 < data flags["le"] = operand1 <= data flags["e"] = operand1 == data flags["ne"] = operand1 != data flags["g"] = operand1 > data flags["ge"] = operand1 >= data print( "cmp: flags = ", flags ) return False # jg if instruction == "jg": if flags["g"]: self.ip = self.labelAddress[ operand1 ] return True return False # ret? if instruction == "ret": self.ip = self.pop() # get the number after ret if operand1 != None: operand1 = int( operand1 ) for i in range( 0, operand1, 4 ): self.pop() return True return False def displayStack(): file = open( "stack.txt", "w" ) file.write( "\n" ) file.write( "+" + ("-"*18) + "+ \n" ) for i in range( len(stack)-1, registers["esp"]-1, -1 ): file.write( "|" + " {0:<12} ".format( stack[i] ) + "|") if i==registers["esp"]: file.write( "<-- esp " ) if i==registers["ebp"]: file.write( "<-- ebp " ) file.write( "\n" ) file.write( "+" + ("-"*18) + "+\n" ) file.close() def displayRegs(): file = open( "registers.txt", "w" ) for reg in ["eax", "ebx", "ecx", "edx" ]: file.write( reg.upper() + ": " +str( registers[reg] ) + "\n" ) file.write( "\n" ) file.close() def main(): # initialize the registers registers["esp"] = len( stack )-1 # initialize the listing listing = Listing( program ) # display the first listing os.system('clear') listing.display() # simulation loop. Single step through the # program. while True: input( "> " ) os.system('clear') listing.step() listing.display() displayStack() displayRegs() main()
Factorial in Intel Assembly
;;; ------------------------- ;;; code area ;;; ------------------------- section .text global _start ;;; ; --------------------------------------------------------- ;;; ; main program ;;; ; --------------------------------------------------------- _start: mov eax, 4 ; n = 4 push eax ; TOS <-- XX push eax ; TOS <-- n call fact pop eax ; get n in eax ;;; return to OS mov eax, 1 mov ebx, 0 int 0x80 ;;; ; --------------------------------------------------------- fact: push ebp ; TOS <-- old ebp mov ebp, esp ;;; test for stopping condition cmp dword[ebp+8], 1 ; n==1 ? jg .recurse mov dword[ebp+12], 1 ; yes, then n! = 1 pop ebp ; return to caller ret 4 ;;; recursive step .recurse: push eax ; TOS <-- old eax push ebx ; TOS <-- old ebx push edx ; TOS <-- old edx mov eax, dword[ebp+8] dec eax ; n-1 in eax push eax ; TOS <-- XX push eax ; TOS <-- n-1 call fact ; recursive call pop ebx ; get fact(n-1) in ebx mov eax, dword[ebp+8] mul ebx ; edx:eax <-- n * (n-1)! mov dword[ebp+12], eax ; that's n! ; drop edx pop edx ; restore regs pop ebx pop eax pop ebp ; return ret 4