class JitCore(object): "JiT management. This is an abstract class" jitted_block_delete_cb = None jitted_block_max_size = 10000 def __init__(self, ir_arch, bs=None): """Initialise a JitCore instance. @ir_arch: ir instance for current architecture @bs: bitstream """ self.ir_arch = ir_arch self.arch_name = "%s%s" % (self.ir_arch.arch.name, self.ir_arch.attrib) self.bs = bs self.known_blocs = {} self.loc_key_to_jit_block = BoundedDict(self.jitted_block_max_size, delete_cb=self.jitted_block_delete_cb) self.lbl2bloc = {} self.log_mn = False self.log_regs = False self.log_newbloc = False self.segm_to_do = set() self.jitcount = 0 self.addr2obj = {} self.addr2objref = {} self.blocs_mem_interval = interval() self.disasm_cb = None self.split_dis = set() self.options = {"jit_maxline": 50, # Maximum number of line jitted "max_exec_per_call": 0 # 0 means no limit } self.mdis = disasmEngine( ir_arch.arch, ir_arch.attrib, bs, lines_wd=self.options["jit_maxline"], symbol_pool=ir_arch.symbol_pool, follow_call=False, dontdis_retcall=False, split_dis=self.split_dis, dis_block_callback=self.disasm_cb ) def set_options(self, **kwargs): "Set options relative to the backend" self.options.update(kwargs) def clear_jitted_blocks(self): "Reset all jitted blocks" self.loc_key_to_jit_block.clear() self.lbl2bloc.clear() self.blocs_mem_interval = interval() def add_disassembly_splits(self, *args): """The disassembly engine will stop on address in args if they are not at the block beginning""" self.split_dis.update(set(args)) def remove_disassembly_splits(self, *args): """The disassembly engine will no longer stop on address in args""" self.split_dis.difference_update(set(args)) def load(self): "Initialise the Jitter" raise NotImplementedError("Abstract class") def get_bloc_min_max(self, cur_block): "Update cur_block to set min/max address" if cur_block.lines: cur_block.ad_min = cur_block.lines[0].offset cur_block.ad_max = cur_block.lines[-1].offset + cur_block.lines[-1].l else: # 1 byte block for unknown mnemonic offset = ir_arch.symbol_pool.loc_key_to_offset(cur_block.loc_key) cur_block.ad_min = offset cur_block.ad_max = offset+1 def add_bloc_to_mem_interval(self, vm, block): "Update vm to include block addresses in its memory range" self.blocs_mem_interval += interval([(block.ad_min, block.ad_max - 1)]) vm.reset_code_bloc_pool() for a, b in self.blocs_mem_interval: vm.add_code_bloc(a, b + 1) def jitirblocs(self, label, irblocks): """JiT a group of irblocks. @label: the label of the irblocks @irblocks: a gorup of irblocks """ raise NotImplementedError("Abstract class") def add_bloc(self, block): """Add a block to JiT and JiT it. @block: asm_bloc to add """ irblocks = self.ir_arch.add_block(block, gen_pc_updt = True) block.blocks = irblocks self.jitirblocs(block.loc_key, irblocks) def disbloc(self, addr, vm): """Disassemble a new block and JiT it @addr: address of the block to disassemble (LocKey or int) @vm: VmMngr instance """ # Get the block if isinstance(addr, LocKey): addr = self.ir_arch.symbol_pool.loc_key_to_offset(addr) if addr is None: raise RuntimeError("Unknown offset for LocKey") # Prepare disassembler self.mdis.lines_wd = self.options["jit_maxline"] self.mdis.dis_block_callback = self.disasm_cb # Disassemble it cur_block = self.mdis.dis_block(addr) if isinstance(cur_block, AsmBlockBad): return cur_block # Logging if self.log_newbloc: print cur_block.to_string(self.mdis.symbol_pool) # Update label -> block self.lbl2bloc[cur_block.loc_key] = cur_block # Store min/max block address needed in jit automod code self.get_bloc_min_max(cur_block) # JiT it self.add_bloc(cur_block) # Update jitcode mem range self.add_bloc_to_mem_interval(vm, cur_block) return cur_block def runbloc(self, cpu, lbl, breakpoints): """Run the block starting at lbl. @cpu: JitCpu instance @lbl: target label """ if lbl is None: lbl = getattr(cpu, self.ir_arch.pc.name) if not lbl in self.loc_key_to_jit_block: # Need to JiT the block cur_block = self.disbloc(lbl, cpu.vmmngr) if isinstance(cur_block, AsmBlockBad): errno = cur_block.errno if errno == AsmBlockBad.ERROR_IO: cpu.vmmngr.set_exception(EXCEPT_ACCESS_VIOL) elif errno == AsmBlockBad.ERROR_CANNOT_DISASM: cpu.set_exception(EXCEPT_UNK_MNEMO) else: raise RuntimeError("Unhandled disasm result %r" % errno) return lbl # Run the block and update cpu/vmmngr state return self.exec_wrapper(lbl, cpu, self.loc_key_to_jit_block.data, breakpoints, self.options["max_exec_per_call"]) def blocs2memrange(self, blocks): """Return an interval instance standing for blocks addresses @blocks: list of asm_bloc instances """ mem_range = interval() for block in blocks: mem_range += interval([(block.ad_min, block.ad_max - 1)]) return mem_range def __updt_jitcode_mem_range(self, vm): """Rebuild the VM blocks address memory range @vm: VmMngr instance """ # Reset the current pool vm.reset_code_bloc_pool() # Add blocks in the pool for start, stop in self.blocs_mem_interval: vm.add_code_bloc(start, stop + 1) def del_bloc_in_range(self, ad1, ad2): """Find and remove jitted block in range [ad1, ad2]. Return the list of block removed. @ad1: First address @ad2: Last address """ # Find concerned blocks modified_blocks = set() for block in self.lbl2bloc.values(): if not block.lines: continue if block.ad_max <= ad1 or block.ad_min >= ad2: # Block not modified pass else: # Modified blocks modified_blocks.add(block) # Generate interval to delete del_interval = self.blocs2memrange(modified_blocks) # Remove interval from monitored interval list self.blocs_mem_interval -= del_interval # Remove modified blocks for block in modified_blocks: try: for irblock in block.blocks: # Remove offset -> jitted block link offset = self.ir_arch.symbol_pool.loc_key_to_offset(irblock.loc_key) if offset in self.loc_key_to_jit_block: del(self.loc_key_to_jit_block[offset]) except AttributeError: # The block has never been translated in IR offset = self.ir_arch.symbol_pool.loc_key_to_offset(block.loc_key) if offset in self.loc_key_to_jit_block: del(self.loc_key_to_jit_block[offset]) # Remove label -> block link del(self.lbl2bloc[block.loc_key]) return modified_blocks def updt_automod_code_range(self, vm, mem_range): """Remove jitted code in range @mem_range @vm: VmMngr instance @mem_range: list of start/stop addresses """ for addr_start, addr_stop in mem_range: self.del_bloc_in_range(addr_start, addr_stop) self.__updt_jitcode_mem_range(vm) vm.reset_memory_access() def updt_automod_code(self, vm): """Remove jitted code updated by memory write @vm: VmMngr instance """ mem_range = [] for addr_start, addr_stop in vm.get_memory_write(): mem_range.append((addr_start, addr_stop)) self.updt_automod_code_range(vm, mem_range) def hash_block(self, block): """ Build a hash of the block @block @block: asmblock """ block_raw = "".join(line.b for line in block.lines) offset = self.ir_arch.symbol_pool.loc_key_to_offset(block.loc_key) block_hash = md5("%X_%s_%s_%s_%s" % (offset, self.arch_name, self.log_mn, self.log_regs, block_raw)).hexdigest() return block_hash
class JitCore(object): "JiT management. This is an abstract class" jitted_block_delete_cb = None jitted_block_max_size = 10000 def __init__(self, ir_arch, bs=None): """Initialise a JitCore instance. @ir_arch: ir instance for current architecture @bs: bitstream """ self.ir_arch = ir_arch self.bs = bs self.known_blocs = {} self.lbl2jitbloc = BoundedDict(self.jitted_block_max_size, delete_cb=self.jitted_block_delete_cb) self.lbl2bloc = {} self.log_mn = False self.log_regs = False self.log_newbloc = False self.segm_to_do = set() self.job_done = set() self.jitcount = 0 self.addr2obj = {} self.addr2objref = {} self.blocs_mem_interval = interval() self.disasm_cb = None self.split_dis = set() self.options = {"jit_maxline": 50, # Maximum number of line jitted "max_exec_per_call": 0 # 0 means no limit } self.mdis = asmbloc.disasmEngine(ir_arch.arch, ir_arch.attrib, bs, lines_wd=self.options["jit_maxline"], symbol_pool=ir_arch.symbol_pool, follow_call=False, dontdis_retcall=False, split_dis=self.split_dis, dis_bloc_callback=self.disasm_cb) def set_options(self, **kwargs): "Set options relative to the backend" self.options.update(kwargs) def clear_jitted_blocks(self): "Reset all jitted blocks" self.lbl2jitbloc.clear() self.lbl2bloc.clear() self.blocs_mem_interval = interval() def add_disassembly_splits(self, *args): """The disassembly engine will stop on address in args if they are not at the block beginning""" self.split_dis.update(set(args)) def remove_disassembly_splits(self, *args): """The disassembly engine will no longer stop on address in args""" self.split_dis.difference_update(set(args)) def load(self): "Initialise the Jitter" raise NotImplementedError("Abstract class") def get_bloc_min_max(self, cur_bloc): "Update cur_bloc to set min/max address" if cur_bloc.lines: cur_bloc.ad_min = cur_bloc.lines[0].offset cur_bloc.ad_max = cur_bloc.lines[-1].offset + cur_bloc.lines[-1].l else: # 1 byte block for unknown mnemonic cur_bloc.ad_min = cur_bloc.label.offset cur_bloc.ad_max = cur_bloc.label.offset+1 def add_bloc_to_mem_interval(self, vm, bloc): "Update vm to include bloc addresses in its memory range" self.blocs_mem_interval += interval([(bloc.ad_min, bloc.ad_max - 1)]) vm.reset_code_bloc_pool() for a, b in self.blocs_mem_interval: vm.add_code_bloc(a, b + 1) def jitirblocs(self, label, irblocs): """JiT a group of irblocs. @label: the label of the irblocs @irblocs: a gorup of irblocs """ raise NotImplementedError("Abstract class") def add_bloc(self, b): """Add a bloc to JiT and JiT it. @b: the bloc to add """ irblocs = self.ir_arch.add_bloc(b, gen_pc_updt = True) b.irblocs = irblocs self.jitirblocs(b.label, irblocs) def disbloc(self, addr, vm): """Disassemble a new bloc and JiT it @addr: address of the block to disassemble (asm_label or int) @vm: VmMngr instance """ # Get the bloc if isinstance(addr, asmbloc.asm_label): addr = addr.offset # Prepare disassembler self.mdis.job_done.clear() self.mdis.lines_wd = self.options["jit_maxline"] self.mdis.dis_bloc_callback = self.disasm_cb # Disassemble it try: cur_bloc = self.mdis.dis_bloc(addr) except IOError: # vm_exception_flag is set label = self.ir_arch.symbol_pool.getby_offset_create(addr) cur_bloc = asmbloc.asm_block_bad(label) # Logging if self.log_newbloc: print cur_bloc # Update label -> bloc self.lbl2bloc[cur_bloc.label] = cur_bloc # Store min/max bloc address needed in jit automod code self.get_bloc_min_max(cur_bloc) # JiT it self.add_bloc(cur_bloc) # Update jitcode mem range self.add_bloc_to_mem_interval(vm, cur_bloc) def runbloc(self, cpu, lbl, breakpoints): """Run the bloc starting at lbl. @cpu: JitCpu instance @lbl: target label """ if lbl is None: lbl = getattr(cpu, self.ir_arch.pc.name) if not lbl in self.lbl2jitbloc: # Need to JiT the bloc self.disbloc(lbl, cpu.vmmngr) # Run the bloc and update cpu/vmmngr state return self.exec_wrapper(lbl, cpu, self.lbl2jitbloc.data, breakpoints, self.options["max_exec_per_call"]) def blocs2memrange(self, blocs): """Return an interval instance standing for blocs addresses @blocs: list of asm_bloc instances """ mem_range = interval() for b in blocs: mem_range += interval([(b.ad_min, b.ad_max - 1)]) return mem_range def __updt_jitcode_mem_range(self, vm): """Rebuild the VM blocs address memory range @vm: VmMngr instance """ # Reset the current pool vm.reset_code_bloc_pool() # Add blocs in the pool for a, b in self.blocs_mem_interval: vm.add_code_bloc(a, b + 1) def del_bloc_in_range(self, ad1, ad2): """Find and remove jitted bloc in range [ad1, ad2]. Return the list of bloc removed. @ad1: First address @ad2: Last address """ # Find concerned blocs modified_blocs = set() for b in self.lbl2bloc.values(): if not b.lines: continue if b.ad_max <= ad1 or b.ad_min >= ad2: # Bloc not modified pass else: # Modified blocs modified_blocs.add(b) # Generate interval to delete del_interval = self.blocs2memrange(modified_blocs) # Remove interval from monitored interval list self.blocs_mem_interval -= del_interval # Remove modified blocs for b in modified_blocs: try: for irbloc in b.irblocs: # Remove offset -> jitted bloc link if irbloc.label.offset in self.lbl2jitbloc: del(self.lbl2jitbloc[irbloc.label.offset]) except AttributeError: # The bloc has never been translated in IR if b.label.offset in self.lbl2jitbloc: del(self.lbl2jitbloc[b.label.offset]) # Remove label -> bloc link del(self.lbl2bloc[b.label]) return modified_blocs def updt_automod_code_range(self, vm, mem_range): """Remove jitted code in range @mem_range @vm: VmMngr instance @mem_range: list of start/stop addresses """ for addr_start, addr_stop in mem_range: self.del_bloc_in_range(addr_start, addr_stop) self.__updt_jitcode_mem_range(vm) vm.reset_memory_access() def updt_automod_code(self, vm): """Remove jitted code updated by memory write @vm: VmMngr instance """ mem_range = [] for addr_start, addr_stop in vm.get_memory_write(): mem_range.append((addr_start, addr_stop)) self.updt_automod_code_range(vm, mem_range)
class JitCore(object): "JiT management. This is an abstract class" jitted_block_delete_cb = None jitted_block_max_size = 10000 def __init__(self, ir_arch, bs=None): """Initialise a JitCore instance. @ir_arch: ir instance for current architecture @bs: bitstream """ self.ir_arch = ir_arch self.arch_name = "%s%s" % (self.ir_arch.arch.name, self.ir_arch.attrib) self.bs = bs self.known_blocs = {} self.loc_key_to_jit_block = BoundedDict( self.jitted_block_max_size, delete_cb=self.jitted_block_delete_cb) self.lbl2bloc = {} self.log_mn = False self.log_regs = False self.log_newbloc = False self.segm_to_do = set() self.jitcount = 0 self.addr2obj = {} self.addr2objref = {} self.blocs_mem_interval = interval() self.disasm_cb = None self.split_dis = set() self.options = { "jit_maxline": 50, # Maximum number of line jitted "max_exec_per_call": 0 # 0 means no limit } self.mdis = disasmEngine(ir_arch.arch, ir_arch.attrib, bs, lines_wd=self.options["jit_maxline"], symbol_pool=ir_arch.symbol_pool, follow_call=False, dontdis_retcall=False, split_dis=self.split_dis, dis_block_callback=self.disasm_cb) def set_options(self, **kwargs): "Set options relative to the backend" self.options.update(kwargs) def clear_jitted_blocks(self): "Reset all jitted blocks" self.loc_key_to_jit_block.clear() self.lbl2bloc.clear() self.blocs_mem_interval = interval() def add_disassembly_splits(self, *args): """The disassembly engine will stop on address in args if they are not at the block beginning""" self.split_dis.update(set(args)) def remove_disassembly_splits(self, *args): """The disassembly engine will no longer stop on address in args""" self.split_dis.difference_update(set(args)) def load(self): "Initialise the Jitter" raise NotImplementedError("Abstract class") def get_bloc_min_max(self, cur_block): "Update cur_block to set min/max address" if cur_block.lines: cur_block.ad_min = cur_block.lines[0].offset cur_block.ad_max = cur_block.lines[-1].offset + cur_block.lines[ -1].l else: # 1 byte block for unknown mnemonic offset = ir_arch.symbol_pool.loc_key_to_offset(cur_block.loc_key) cur_block.ad_min = offset cur_block.ad_max = offset + 1 def add_bloc_to_mem_interval(self, vm, block): "Update vm to include block addresses in its memory range" self.blocs_mem_interval += interval([(block.ad_min, block.ad_max - 1)]) vm.reset_code_bloc_pool() for a, b in self.blocs_mem_interval: vm.add_code_bloc(a, b + 1) def jitirblocs(self, label, irblocks): """JiT a group of irblocks. @label: the label of the irblocks @irblocks: a gorup of irblocks """ raise NotImplementedError("Abstract class") def add_bloc(self, block): """Add a block to JiT and JiT it. @block: asm_bloc to add """ irblocks = self.ir_arch.add_block(block, gen_pc_updt=True) block.blocks = irblocks self.jitirblocs(block.loc_key, irblocks) def disbloc(self, addr, vm): """Disassemble a new block and JiT it @addr: address of the block to disassemble (LocKey or int) @vm: VmMngr instance """ # Get the block if isinstance(addr, LocKey): addr = self.ir_arch.symbol_pool.loc_key_to_offset(addr) if addr is None: raise RuntimeError("Unknown offset for LocKey") # Prepare disassembler self.mdis.lines_wd = self.options["jit_maxline"] self.mdis.dis_block_callback = self.disasm_cb # Disassemble it cur_block = self.mdis.dis_block(addr) if isinstance(cur_block, AsmBlockBad): return cur_block # Logging if self.log_newbloc: print cur_block.to_string(self.mdis.symbol_pool) # Update label -> block self.lbl2bloc[cur_block.loc_key] = cur_block # Store min/max block address needed in jit automod code self.get_bloc_min_max(cur_block) # JiT it self.add_bloc(cur_block) # Update jitcode mem range self.add_bloc_to_mem_interval(vm, cur_block) return cur_block def runbloc(self, cpu, lbl, breakpoints): """Run the block starting at lbl. @cpu: JitCpu instance @lbl: target label """ if lbl is None: lbl = getattr(cpu, self.ir_arch.pc.name) if not lbl in self.loc_key_to_jit_block: # Need to JiT the block cur_block = self.disbloc(lbl, cpu.vmmngr) if isinstance(cur_block, AsmBlockBad): errno = cur_block.errno if errno == AsmBlockBad.ERROR_IO: cpu.vmmngr.set_exception(EXCEPT_ACCESS_VIOL) elif errno == AsmBlockBad.ERROR_CANNOT_DISASM: cpu.set_exception(EXCEPT_UNK_MNEMO) else: raise RuntimeError("Unhandled disasm result %r" % errno) return lbl # Run the block and update cpu/vmmngr state return self.exec_wrapper(lbl, cpu, self.loc_key_to_jit_block.data, breakpoints, self.options["max_exec_per_call"]) def blocs2memrange(self, blocks): """Return an interval instance standing for blocks addresses @blocks: list of asm_bloc instances """ mem_range = interval() for block in blocks: mem_range += interval([(block.ad_min, block.ad_max - 1)]) return mem_range def __updt_jitcode_mem_range(self, vm): """Rebuild the VM blocks address memory range @vm: VmMngr instance """ # Reset the current pool vm.reset_code_bloc_pool() # Add blocks in the pool for start, stop in self.blocs_mem_interval: vm.add_code_bloc(start, stop + 1) def del_bloc_in_range(self, ad1, ad2): """Find and remove jitted block in range [ad1, ad2]. Return the list of block removed. @ad1: First address @ad2: Last address """ # Find concerned blocks modified_blocks = set() for block in self.lbl2bloc.values(): if not block.lines: continue if block.ad_max <= ad1 or block.ad_min >= ad2: # Block not modified pass else: # Modified blocks modified_blocks.add(block) # Generate interval to delete del_interval = self.blocs2memrange(modified_blocks) # Remove interval from monitored interval list self.blocs_mem_interval -= del_interval # Remove modified blocks for block in modified_blocks: try: for irblock in block.blocks: # Remove offset -> jitted block link offset = self.ir_arch.symbol_pool.loc_key_to_offset( irblock.loc_key) if offset in self.loc_key_to_jit_block: del (self.loc_key_to_jit_block[offset]) except AttributeError: # The block has never been translated in IR offset = self.ir_arch.symbol_pool.loc_key_to_offset( block.loc_key) if offset in self.loc_key_to_jit_block: del (self.loc_key_to_jit_block[offset]) # Remove label -> block link del (self.lbl2bloc[block.loc_key]) return modified_blocks def updt_automod_code_range(self, vm, mem_range): """Remove jitted code in range @mem_range @vm: VmMngr instance @mem_range: list of start/stop addresses """ for addr_start, addr_stop in mem_range: self.del_bloc_in_range(addr_start, addr_stop) self.__updt_jitcode_mem_range(vm) vm.reset_memory_access() def updt_automod_code(self, vm): """Remove jitted code updated by memory write @vm: VmMngr instance """ mem_range = [] for addr_start, addr_stop in vm.get_memory_write(): mem_range.append((addr_start, addr_stop)) self.updt_automod_code_range(vm, mem_range) def hash_block(self, block): """ Build a hash of the block @block @block: asmblock """ block_raw = "".join(line.b for line in block.lines) offset = self.ir_arch.symbol_pool.loc_key_to_offset(block.loc_key) block_hash = md5("%X_%s_%s_%s_%s" % (offset, self.arch_name, self.log_mn, self.log_regs, block_raw)).hexdigest() return block_hash
class JitCore(object): "JiT management. This is an abstract class" jitted_block_delete_cb = None jitted_block_max_size = 10000 def __init__(self, ir_arch, bs=None): """Initialise a JitCore instance. @ir_arch: ir instance for current architecture @bs: bitstream """ self.ir_arch = ir_arch self.bs = bs self.known_blocs = {} self.lbl2jitbloc = BoundedDict(self.jitted_block_max_size, delete_cb=self.jitted_block_delete_cb) self.lbl2bloc = {} self.log_mn = False self.log_regs = False self.log_newbloc = False self.segm_to_do = set() self.job_done = set() self.jitcount = 0 self.addr2obj = {} self.addr2objref = {} self.blocs_mem_interval = interval() self.disasm_cb = None self.split_dis = set() self.addr_mod = interval() self.options = {"jit_maxline": 50 # Maximum number of line jitted } def set_options(self, **kwargs): "Set options relative to the backend" self.options.update(kwargs) def clear_jitted_blocks(self): "Reset all jitted blocks" self.lbl2jitbloc.clear() self.lbl2bloc.clear() self.blocs_mem_interval = interval() def add_disassembly_splits(self, *args): """The disassembly engine will stop on address in args if they are not at the block beginning""" self.split_dis.update(set(args)) def remove_disassembly_splits(self, *args): """The disassembly engine will no longer stop on address in args""" self.split_dis.difference_update(set(args)) def load(self, arch, attrib): "Initialise the Jitter according to arch and attrib" raise NotImplementedError("Abstract class") def get_bloc_min_max(self, cur_bloc): "Update cur_bloc to set min/max address" if cur_bloc.lines: cur_bloc.ad_min = cur_bloc.lines[0].offset cur_bloc.ad_max = cur_bloc.lines[-1].offset + cur_bloc.lines[-1].l def add_bloc_to_mem_interval(self, vm, bloc): "Update vm to include bloc addresses in its memory range" self.blocs_mem_interval += interval([(bloc.ad_min, bloc.ad_max - 1)]) vm.reset_code_bloc_pool() for a, b in self.blocs_mem_interval: vm.add_code_bloc(a, b + 1) def jitirblocs(self, label, irblocs): """JiT a group of irblocs. @label: the label of the irblocs @irblocs: a gorup of irblocs """ raise NotImplementedError("Abstract class") def add_bloc(self, b): """Add a bloc to JiT and JiT it. @b: the bloc to add """ irblocs = self.ir_arch.add_bloc(b, gen_pc_updt = True) b.irblocs = irblocs self.jitirblocs(b.label, irblocs) def disbloc(self, addr, cpu, vm): "Disassemble a new bloc and JiT it" # Get the bloc if isinstance(addr, asmbloc.asm_label): addr = addr.offset label = self.ir_arch.symbol_pool.getby_offset_create(addr) # Disassemble it try: cur_bloc, _ = asmbloc.dis_bloc(self.ir_arch.arch, self.bs, label, addr, set(), self.ir_arch.symbol_pool, [], follow_call=False, dontdis_retcall=False, lines_wd=self.options["jit_maxline"], # max 10 asm lines attrib=self.ir_arch.attrib, split_dis=self.split_dis) except IOError: # vm_exception_flag is set cur_bloc = asmbloc.asm_bloc(label) # Logging if self.log_newbloc: print cur_bloc if self.disasm_cb is not None: self.disasm_cb(cur_bloc) # Check for empty blocks if not cur_bloc.lines: raise ValueError("Cannot JIT a block without any assembly line") # Update label -> bloc self.lbl2bloc[label] = cur_bloc # Store min/max bloc address needed in jit automod code self.get_bloc_min_max(cur_bloc) # JiT it self.add_bloc(cur_bloc) # Update jitcode mem range self.add_bloc_to_mem_interval(vm, cur_bloc) def jit_call(self, label, cpu, _vmmngr, breakpoints): """Call the function label with cpu and vmmngr states @label: function's label @cpu: JitCpu instance @breakpoints: Dict instance of used breakpoints """ return self.exec_wrapper(label, cpu, self.lbl2jitbloc.data, breakpoints) def runbloc(self, cpu, vm, lbl, breakpoints): """Run the bloc starting at lbl. @cpu: JitCpu instance @vm: VmMngr instance @lbl: target label """ if lbl is None: lbl = cpu.get_gpreg()[self.ir_arch.pc.name] if not lbl in self.lbl2jitbloc: # Need to JiT the bloc self.disbloc(lbl, cpu, vm) # Run the bloc and update cpu/vmmngr state ret = self.jit_call(lbl, cpu, vm, breakpoints) return ret def blocs2memrange(self, blocs): """Return an interval instance standing for blocs addresses @blocs: list of asm_bloc instances """ mem_range = interval() for b in blocs: mem_range += interval([(b.ad_min, b.ad_max - 1)]) return mem_range def __updt_jitcode_mem_range(self, vm): """Rebuild the VM blocs address memory range @vm: VmMngr instance """ # Reset the current pool vm.reset_code_bloc_pool() # Add blocs in the pool for a, b in self.blocs_mem_interval: vm.add_code_bloc(a, b + 1) def del_bloc_in_range(self, ad1, ad2): """Find and remove jitted bloc in range [ad1, ad2]. Return the list of bloc removed. @ad1: First address @ad2: Last address """ # Find concerned blocs modified_blocs = set() for b in self.lbl2bloc.values(): if not b.lines: continue if b.ad_max <= ad1 or b.ad_min >= ad2: # Bloc not modified pass else: # Modified blocs modified_blocs.add(b) # Generate interval to delete del_interval = self.blocs2memrange(modified_blocs) # Remove interval from monitored interval list self.blocs_mem_interval -= del_interval # Remove modified blocs for b in modified_blocs: try: for irbloc in b.irblocs: # Remove offset -> jitted bloc link if irbloc.label.offset in self.lbl2jitbloc: del(self.lbl2jitbloc[irbloc.label.offset]) except AttributeError: # The bloc has never been translated in IR if b.label.offset in self.lbl2jitbloc: del(self.lbl2jitbloc[b.label.offset]) # Remove label -> bloc link del(self.lbl2bloc[b.label]) return modified_blocs def updt_automod_code(self, vm): """Remove code jitted in range self.addr_mod @vm: VmMngr instance """ for addr_start, addr_stop in self.addr_mod: self.del_bloc_in_range(addr_start, addr_stop + 1) self.__updt_jitcode_mem_range(vm) self.addr_mod = interval() def automod_cb(self, addr=0, size=0): self.addr_mod += interval([(addr, addr + size / 8 - 1)]) return None
class JitCore(object): "JiT management. This is an abstract class" jitted_block_delete_cb = None jitted_block_max_size = 10000 def __init__(self, ir_arch, bs=None): """Initialise a JitCore instance. @ir_arch: ir instance for current architecture @bs: bitstream """ self.ir_arch = ir_arch self.bs = bs self.known_blocs = {} self.lbl2jitbloc = BoundedDict(self.jitted_block_max_size, delete_cb=self.jitted_block_delete_cb) self.lbl2bloc = {} self.log_mn = False self.log_regs = False self.log_newbloc = False self.segm_to_do = set() self.job_done = set() self.jitcount = 0 self.addr2obj = {} self.addr2objref = {} self.blocs_mem_interval = interval() self.disasm_cb = None self.split_dis = set() self.options = { "jit_maxline": 50, # Maximum number of line jitted "max_exec_per_call": 0 # 0 means no limit } self.mdis = asmbloc.disasmEngine(ir_arch.arch, ir_arch.attrib, bs, lines_wd=self.options["jit_maxline"], symbol_pool=ir_arch.symbol_pool, follow_call=False, dontdis_retcall=False, split_dis=self.split_dis, dis_bloc_callback=self.disasm_cb) def set_options(self, **kwargs): "Set options relative to the backend" self.options.update(kwargs) def clear_jitted_blocks(self): "Reset all jitted blocks" self.lbl2jitbloc.clear() self.lbl2bloc.clear() self.blocs_mem_interval = interval() def add_disassembly_splits(self, *args): """The disassembly engine will stop on address in args if they are not at the block beginning""" self.split_dis.update(set(args)) def remove_disassembly_splits(self, *args): """The disassembly engine will no longer stop on address in args""" self.split_dis.difference_update(set(args)) def load(self): "Initialise the Jitter" raise NotImplementedError("Abstract class") def get_bloc_min_max(self, cur_bloc): "Update cur_bloc to set min/max address" if cur_bloc.lines: cur_bloc.ad_min = cur_bloc.lines[0].offset cur_bloc.ad_max = cur_bloc.lines[-1].offset + cur_bloc.lines[-1].l else: # 1 byte block for unknown mnemonic cur_bloc.ad_min = cur_bloc.label.offset cur_bloc.ad_max = cur_bloc.label.offset + 1 def add_bloc_to_mem_interval(self, vm, bloc): "Update vm to include bloc addresses in its memory range" self.blocs_mem_interval += interval([(bloc.ad_min, bloc.ad_max - 1)]) vm.reset_code_bloc_pool() for a, b in self.blocs_mem_interval: vm.add_code_bloc(a, b + 1) def jitirblocs(self, label, irblocs): """JiT a group of irblocs. @label: the label of the irblocs @irblocs: a gorup of irblocs """ raise NotImplementedError("Abstract class") def add_bloc(self, b): """Add a bloc to JiT and JiT it. @b: the bloc to add """ irblocs = self.ir_arch.add_bloc(b, gen_pc_updt=True) b.irblocs = irblocs self.jitirblocs(b.label, irblocs) def disbloc(self, addr, vm): """Disassemble a new bloc and JiT it @addr: address of the block to disassemble (asm_label or int) @vm: VmMngr instance """ # Get the bloc if isinstance(addr, asmbloc.asm_label): addr = addr.offset # Prepare disassembler self.mdis.job_done.clear() self.mdis.lines_wd = self.options["jit_maxline"] self.mdis.dis_bloc_callback = self.disasm_cb # Disassemble it try: cur_bloc = self.mdis.dis_bloc(addr) except IOError: # vm_exception_flag is set label = self.ir_arch.symbol_pool.getby_offset_create(addr) cur_bloc = asmbloc.asm_block_bad(label) # Logging if self.log_newbloc: print cur_bloc # Update label -> bloc self.lbl2bloc[cur_bloc.label] = cur_bloc # Store min/max bloc address needed in jit automod code self.get_bloc_min_max(cur_bloc) # JiT it self.add_bloc(cur_bloc) # Update jitcode mem range self.add_bloc_to_mem_interval(vm, cur_bloc) def runbloc(self, cpu, lbl, breakpoints): """Run the bloc starting at lbl. @cpu: JitCpu instance @lbl: target label """ if lbl is None: lbl = getattr(cpu, self.ir_arch.pc.name) if not lbl in self.lbl2jitbloc: # Need to JiT the bloc self.disbloc(lbl, cpu.vmmngr) # Run the bloc and update cpu/vmmngr state return self.exec_wrapper(lbl, cpu, self.lbl2jitbloc.data, breakpoints, self.options["max_exec_per_call"]) def blocs2memrange(self, blocs): """Return an interval instance standing for blocs addresses @blocs: list of asm_bloc instances """ mem_range = interval() for b in blocs: mem_range += interval([(b.ad_min, b.ad_max - 1)]) return mem_range def __updt_jitcode_mem_range(self, vm): """Rebuild the VM blocs address memory range @vm: VmMngr instance """ # Reset the current pool vm.reset_code_bloc_pool() # Add blocs in the pool for a, b in self.blocs_mem_interval: vm.add_code_bloc(a, b + 1) def del_bloc_in_range(self, ad1, ad2): """Find and remove jitted bloc in range [ad1, ad2]. Return the list of bloc removed. @ad1: First address @ad2: Last address """ # Find concerned blocs modified_blocs = set() for b in self.lbl2bloc.values(): if not b.lines: continue if b.ad_max <= ad1 or b.ad_min >= ad2: # Bloc not modified pass else: # Modified blocs modified_blocs.add(b) # Generate interval to delete del_interval = self.blocs2memrange(modified_blocs) # Remove interval from monitored interval list self.blocs_mem_interval -= del_interval # Remove modified blocs for b in modified_blocs: try: for irbloc in b.irblocs: # Remove offset -> jitted bloc link if irbloc.label.offset in self.lbl2jitbloc: del (self.lbl2jitbloc[irbloc.label.offset]) except AttributeError: # The bloc has never been translated in IR if b.label.offset in self.lbl2jitbloc: del (self.lbl2jitbloc[b.label.offset]) # Remove label -> bloc link del (self.lbl2bloc[b.label]) return modified_blocs def updt_automod_code_range(self, vm, mem_range): """Remove jitted code in range @mem_range @vm: VmMngr instance @mem_range: list of start/stop addresses """ for addr_start, addr_stop in mem_range: self.del_bloc_in_range(addr_start, addr_stop) self.__updt_jitcode_mem_range(vm) vm.reset_memory_access() def updt_automod_code(self, vm): """Remove jitted code updated by memory write @vm: VmMngr instance """ mem_range = [] for addr_start, addr_stop in vm.get_memory_write(): mem_range.append((addr_start, addr_stop)) self.updt_automod_code_range(vm, mem_range)
class JitCore(object): "JiT management. This is an abstract class" jitted_block_delete_cb = None jitted_block_max_size = 10000 def __init__(self, ir_arch, bs=None): """Initialise a JitCore instance. @ir_arch: ir instance for current architecture @bs: bitstream """ self.ir_arch = ir_arch self.bs = bs self.known_blocs = {} self.lbl2jitbloc = BoundedDict(self.jitted_block_max_size, delete_cb=self.jitted_block_delete_cb) self.lbl2bloc = {} self.log_mn = False self.log_regs = False self.log_newbloc = False self.segm_to_do = set() self.job_done = set() self.jitcount = 0 self.addr2obj = {} self.addr2objref = {} self.blocs_mem_interval = interval() self.disasm_cb = None self.split_dis = set() self.addr_mod = interval() self.options = { "jit_maxline": 50 # Maximum number of line jitted } def set_options(self, **kwargs): "Set options relative to the backend" self.options.update(kwargs) def clear_jitted_blocks(self): "Reset all jitted blocks" self.lbl2jitbloc.clear() self.lbl2bloc.clear() self.blocs_mem_interval = interval() def add_disassembly_splits(self, *args): """The disassembly engine will stop on address in args if they are not at the block beginning""" self.split_dis.update(set(args)) def remove_disassembly_splits(self, *args): """The disassembly engine will no longer stop on address in args""" self.split_dis.difference_update(set(args)) def load(self, arch, attrib): "Initialise the Jitter according to arch and attrib" raise NotImplementedError("Abstract class") def get_bloc_min_max(self, cur_bloc): "Update cur_bloc to set min/max address" if cur_bloc.lines: cur_bloc.ad_min = cur_bloc.lines[0].offset cur_bloc.ad_max = cur_bloc.lines[-1].offset + cur_bloc.lines[-1].l def add_bloc_to_mem_interval(self, vm, bloc): "Update vm to include bloc addresses in its memory range" self.blocs_mem_interval += interval([(bloc.ad_min, bloc.ad_max - 1)]) vm.reset_code_bloc_pool() for a, b in self.blocs_mem_interval: vm.add_code_bloc(a, b + 1) def jitirblocs(self, label, irblocs): """JiT a group of irblocs. @label: the label of the irblocs @irblocs: a gorup of irblocs """ raise NotImplementedError("Abstract class") def add_bloc(self, b): """Add a bloc to JiT and JiT it. @b: the bloc to add """ irblocs = self.ir_arch.add_bloc(b, gen_pc_updt=True) b.irblocs = irblocs self.jitirblocs(b.label, irblocs) def disbloc(self, addr, cpu, vm): "Disassemble a new bloc and JiT it" # Get the bloc if isinstance(addr, asmbloc.asm_label): addr = addr.offset label = self.ir_arch.symbol_pool.getby_offset_create(addr) # Disassemble it try: cur_bloc, _ = asmbloc.dis_bloc( self.ir_arch.arch, self.bs, label, addr, set(), self.ir_arch.symbol_pool, [], follow_call=False, dontdis_retcall=False, lines_wd=self.options["jit_maxline"], # max 10 asm lines attrib=self.ir_arch.attrib, split_dis=self.split_dis) except IOError: # vm_exception_flag is set cur_bloc = asmbloc.asm_bloc(label) # Logging if self.log_newbloc: print cur_bloc if self.disasm_cb is not None: self.disasm_cb(cur_bloc) # Check for empty blocks if not cur_bloc.lines: raise ValueError("Cannot JIT a block without any assembly line") # Update label -> bloc self.lbl2bloc[label] = cur_bloc # Store min/max bloc address needed in jit automod code self.get_bloc_min_max(cur_bloc) # JiT it self.add_bloc(cur_bloc) # Update jitcode mem range self.add_bloc_to_mem_interval(vm, cur_bloc) def jit_call(self, label, cpu, _vmmngr, breakpoints): """Call the function label with cpu and vmmngr states @label: function's label @cpu: JitCpu instance @breakpoints: Dict instance of used breakpoints """ return self.exec_wrapper(label, cpu, self.lbl2jitbloc.data, breakpoints) def runbloc(self, cpu, vm, lbl, breakpoints): """Run the bloc starting at lbl. @cpu: JitCpu instance @vm: VmMngr instance @lbl: target label """ if lbl is None: lbl = cpu.get_gpreg()[self.ir_arch.pc.name] if not lbl in self.lbl2jitbloc: # Need to JiT the bloc self.disbloc(lbl, cpu, vm) # Run the bloc and update cpu/vmmngr state ret = self.jit_call(lbl, cpu, vm, breakpoints) return ret def blocs2memrange(self, blocs): """Return an interval instance standing for blocs addresses @blocs: list of asm_bloc instances """ mem_range = interval() for b in blocs: mem_range += interval([(b.ad_min, b.ad_max - 1)]) return mem_range def __updt_jitcode_mem_range(self, vm): """Rebuild the VM blocs address memory range @vm: VmMngr instance """ # Reset the current pool vm.reset_code_bloc_pool() # Add blocs in the pool for a, b in self.blocs_mem_interval: vm.add_code_bloc(a, b + 1) def del_bloc_in_range(self, ad1, ad2): """Find and remove jitted bloc in range [ad1, ad2]. Return the list of bloc removed. @ad1: First address @ad2: Last address """ # Find concerned blocs modified_blocs = set() for b in self.lbl2bloc.values(): if not b.lines: continue if b.ad_max <= ad1 or b.ad_min >= ad2: # Bloc not modified pass else: # Modified blocs modified_blocs.add(b) # Generate interval to delete del_interval = self.blocs2memrange(modified_blocs) # Remove interval from monitored interval list self.blocs_mem_interval -= del_interval # Remove modified blocs for b in modified_blocs: try: for irbloc in b.irblocs: # Remove offset -> jitted bloc link if irbloc.label.offset in self.lbl2jitbloc: del (self.lbl2jitbloc[irbloc.label.offset]) except AttributeError: # The bloc has never been translated in IR if b.label.offset in self.lbl2jitbloc: del (self.lbl2jitbloc[b.label.offset]) # Remove label -> bloc link del (self.lbl2bloc[b.label]) return modified_blocs def updt_automod_code(self, vm): """Remove code jitted in range self.addr_mod @vm: VmMngr instance """ for addr_start, addr_stop in self.addr_mod: self.del_bloc_in_range(addr_start, addr_stop + 1) self.__updt_jitcode_mem_range(vm) self.addr_mod = interval() def automod_cb(self, addr=0, size=0): self.addr_mod += interval([(addr, addr + size / 8 - 1)]) return None
class JitCore(object): "JiT management. This is an abstract class" # Jitted function's name FUNCNAME = "block_entry" jitted_block_delete_cb = None jitted_block_max_size = 10000 def __init__(self, ir_arch, bin_stream): """Initialise a JitCore instance. @ir_arch: ir instance for current architecture @bin_stream: bin_stream instance """ # Arch related self.ir_arch = ir_arch self.ircfg = self.ir_arch.new_ircfg() self.arch_name = "%s%s" % (self.ir_arch.arch.name, self.ir_arch.attrib) # Structures for block tracking self.offset_to_jitted_func = BoundedDict( self.jitted_block_max_size, delete_cb=self.jitted_block_delete_cb) self.loc_key_to_block = {} self.blocks_mem_interval = interval() # Logging & options self.log_mn = False self.log_regs = False self.log_newbloc = False self.options = { "jit_maxline": 50, # Maximum number of line jitted "max_exec_per_call": 0 # 0 means no limit } # Disassembly Engine self.split_dis = set() self.mdis = disasmEngine( ir_arch.arch, ir_arch.attrib, bin_stream, lines_wd=self.options["jit_maxline"], loc_db=ir_arch.loc_db, follow_call=False, dontdis_retcall=False, split_dis=self.split_dis, ) def set_options(self, **kwargs): "Set options relative to the backend" self.options.update(kwargs) def clear_jitted_blocks(self): "Reset all jitted blocks" self.offset_to_jitted_func.clear() self.loc_key_to_block.clear() self.blocks_mem_interval = interval() def add_disassembly_splits(self, *args): """The disassembly engine will stop on address in args if they are not at the block beginning""" self.split_dis.update(set(args)) def remove_disassembly_splits(self, *args): """The disassembly engine will no longer stop on address in args""" self.split_dis.difference_update(set(args)) def load(self): "Initialise the Jitter" raise NotImplementedError("Abstract class") def set_block_min_max(self, cur_block): "Update cur_block to set min/max address" if cur_block.lines: cur_block.ad_min = cur_block.lines[0].offset cur_block.ad_max = cur_block.lines[-1].offset + cur_block.lines[ -1].l else: # 1 byte block for unknown mnemonic offset = ir_arch.loc_db.get_location_offset(cur_block.loc_key) cur_block.ad_min = offset cur_block.ad_max = offset + 1 def add_block_to_mem_interval(self, vm, block): "Update vm to include block addresses in its memory range" self.blocks_mem_interval += interval([(block.ad_min, block.ad_max - 1) ]) vm.reset_code_bloc_pool() for a, b in self.blocks_mem_interval: vm.add_code_bloc(a, b + 1) def add_block(self, block): """Add a block to JiT and JiT it. @block: asm_bloc to add """ raise NotImplementedError("Abstract class") def disasm_and_jit_block(self, addr, vm): """Disassemble a new block and JiT it @addr: address of the block to disassemble (LocKey or int) @vm: VmMngr instance """ # Get the block if isinstance(addr, LocKey): addr = self.ir_arch.loc_db.get_location_offset(addr) if addr is None: raise RuntimeError("Unknown offset for LocKey") # Prepare disassembler self.mdis.lines_wd = self.options["jit_maxline"] # Disassemble it cur_block = self.mdis.dis_block(addr) if isinstance(cur_block, AsmBlockBad): return cur_block # Logging if self.log_newbloc: print cur_block.to_string(self.mdis.loc_db) # Update label -> block self.loc_key_to_block[cur_block.loc_key] = cur_block # Store min/max block address needed in jit automod code self.set_block_min_max(cur_block) # JiT it self.add_block(cur_block) # Update jitcode mem range self.add_block_to_mem_interval(vm, cur_block) return cur_block def run_at(self, cpu, offset, stop_offsets): """Run from the starting address @offset. Execution will stop if: - max_exec_per_call option is reached - a new, yet unknown, block is reached after the execution of block at address @offset - an address in @stop_offsets is reached @cpu: JitCpu instance @offset: starting address (int) @stop_offsets: set of address on which the jitter must stop """ if offset is None: offset = getattr(cpu, self.ir_arch.pc.name) if offset not in self.offset_to_jitted_func: # Need to JiT the block cur_block = self.disasm_and_jit_block(offset, cpu.vmmngr) if isinstance(cur_block, AsmBlockBad): errno = cur_block.errno if errno == AsmBlockBad.ERROR_IO: cpu.vmmngr.set_exception(EXCEPT_ACCESS_VIOL) elif errno == AsmBlockBad.ERROR_CANNOT_DISASM: cpu.set_exception(EXCEPT_UNK_MNEMO) else: raise RuntimeError("Unhandled disasm result %r" % errno) return offset # Run the block and update cpu/vmmngr state return self.exec_wrapper(offset, cpu, self.offset_to_jitted_func.data, stop_offsets, self.options["max_exec_per_call"]) def blocks_to_memrange(self, blocks): """Return an interval instance standing for blocks addresses @blocks: list of AsmBlock instances """ mem_range = interval() for block in blocks: mem_range += interval([(block.ad_min, block.ad_max - 1)]) return mem_range def __updt_jitcode_mem_range(self, vm): """Rebuild the VM blocks address memory range @vm: VmMngr instance """ # Reset the current pool vm.reset_code_bloc_pool() # Add blocks in the pool for start, stop in self.blocks_mem_interval: vm.add_code_bloc(start, stop + 1) def del_block_in_range(self, ad1, ad2): """Find and remove jitted block in range [ad1, ad2]. Return the list of block removed. @ad1: First address @ad2: Last address """ # Find concerned blocks modified_blocks = set() for block in self.loc_key_to_block.values(): if not block.lines: continue if block.ad_max <= ad1 or block.ad_min >= ad2: # Block not modified pass else: # Modified blocks modified_blocks.add(block) # Generate interval to delete del_interval = self.blocks_to_memrange(modified_blocks) # Remove interval from monitored interval list self.blocks_mem_interval -= del_interval # Remove modified blocks for block in modified_blocks: try: for irblock in block.blocks: # Remove offset -> jitted block link offset = self.ir_arch.loc_db.get_location_offset( irblock.loc_key) if offset in self.offset_to_jitted_func: del (self.offset_to_jitted_func[offset]) except AttributeError: # The block has never been translated in IR offset = self.ir_arch.loc_db.get_location_offset(block.loc_key) if offset in self.offset_to_jitted_func: del (self.offset_to_jitted_func[offset]) # Remove label -> block link del (self.loc_key_to_block[block.loc_key]) return modified_blocks def updt_automod_code_range(self, vm, mem_range): """Remove jitted code in range @mem_range @vm: VmMngr instance @mem_range: list of start/stop addresses """ for addr_start, addr_stop in mem_range: self.del_block_in_range(addr_start, addr_stop) self.__updt_jitcode_mem_range(vm) vm.reset_memory_access() def updt_automod_code(self, vm): """Remove jitted code updated by memory write @vm: VmMngr instance """ mem_range = [] for addr_start, addr_stop in vm.get_memory_write(): mem_range.append((addr_start, addr_stop)) self.updt_automod_code_range(vm, mem_range) def hash_block(self, block): """ Build a hash of the block @block @block: asmblock """ block_raw = "".join(line.b for line in block.lines) offset = self.ir_arch.loc_db.get_location_offset(block.loc_key) block_hash = md5("%X_%s_%s_%s_%s" % (offset, self.arch_name, self.log_mn, self.log_regs, block_raw)).hexdigest() return block_hash @property def disasm_cb(self): warnings.warn("Deprecated API: use .mdis.dis_block_callback") return self.mdis.dis_block_callback @disasm_cb.setter def disasm_cb(self, value): warnings.warn("Deprecated API: use .mdis.dis_block_callback") self.mdis.dis_block_callback = value