import os
import math
import struct
from unicorn import *
from unicorn.x86_const import *
filename = "./fibonacci"
file_size = int(math.ceil(os.path.getsize(filename) / 1024) + 1) * 1024
base_adr = 0x400000
stack_adr = 0x0
stack_size = 1024 * 1024
main_start_adr = 0x004004e0
main_end_adr = 0x00400582
instructions_skip_list = [0x004004EF, 0x004004f6, 0x00400502, 0x0040054F]
instructions_IO_putc_list = [0x00400560, 0x00400575]
fibonacci_start = [0x00400670]
fibonacci_end = [0x004006f1, 0x00400709]
ret_instr = 0x004005e9
stack_buf = []
dict_fibonacci_result = {}
def read(name):
    with open(name, "rb") as f:
        return f.read()
        
def u32(data):
    return struct.unpack("I", data)[0]
    
def p32(num):
    return struct.pack("I", num)
def hook_code(mu, address, size, user_data):  
    # print('>>> Tracing instruction at 0x%x, instruction size = 0x%x' %(address, size))
    if address in instructions_skip_list:
        mu.reg_write(UC_X86_REG_RIP, address + size)
    elif address in instructions_IO_putc_list:
        print(chr(mu.reg_read(UC_X86_REG_RDI)), end="")
        mu.reg_write(UC_X86_REG_RIP, address + size)
    elif address in fibonacci_start:
        number, ptrNumber = mu.reg_read(UC_X86_REG_RDI), mu.reg_read(UC_X86_REG_RSI)
        ptrNumber_value = u32(mu.mem_read(ptrNumber, 4))
        args = (number, ptrNumber_value)
        if args in dict_fibonacci_result:
            ret_val, ret_ptrNumber_value = dict_fibonacci_result[args]
            mu.reg_write(UC_X86_REG_RAX, ret_val)
            mu.mem_write(ptrNumber, p32(ret_ptrNumber_value))
            mu.reg_write(UC_X86_REG_RIP, ret_instr)
        else:
            stack_buf.append((number, ptrNumber, ptrNumber_value))
    elif address in fibonacci_end:
        number, ptrNumber, ptrNumber_value = stack_buf.pop()
        ret_val = mu.reg_read(UC_X86_REG_RAX)
        ret_ptrNumber_value = u32(mu.mem_read(ptrNumber, 4))
        args = (number, ptrNumber_value)
        dict_fibonacci_result[args] = (ret_val, ret_ptrNumber_value)
try:
    mu = Uc(UC_ARCH_X86, UC_MODE_64)
    mu.mem_map(base_adr, file_size)
    mu.mem_map(stack_adr, stack_size)
    mu.mem_write(base_adr, read(filename))
    mu.reg_write(UC_X86_REG_RSP, stack_adr + stack_size - 1)
    mu.hook_add(UC_HOOK_CODE, hook_code)
    mu.emu_start(main_start_adr, main_end_adr)
except UcError as e:
    print("ERROR: %s" % e)