def pick_operand_value(self, op_type: OperandType) -> Optional[int]: '''Pick a random value for an operand The result will always be non-negative: if the operand is a signed immediate, this is encoded as 2s complement. ''' if isinstance(op_type, RegOperandType): return self.pick_reg_operand_value(op_type) op_rng = op_type.get_op_val_range(self.pc) if op_rng is None: # If we don't know the width, the only immediate that we *know* # is going to be valid is 0. return 0 if isinstance(op_type, ImmOperandType): shift = op_type.shift else: shift = 0 align = 1 << shift lo, hi = op_rng sh_lo = (lo + align - 1) // align sh_hi = hi // align op_val = random.randint(sh_lo, sh_hi) << shift return op_type.op_val_to_enc_val(op_val, self.pc)
def _pick_loopi_iterations(self, max_iters: int, op_type: OperandType, model: Model) -> Optional[IterCount]: '''Pick the number of iterations for a LOOPI loop max_iters is the maximum number of iterations possible, given how much fuel we have left. Returns the encoded and decoded number of iterations. ''' assert isinstance(op_type, ImmOperandType) iters_range = op_type.get_op_val_range(model.pc) assert iters_range is not None iters_lo, iters_hi = iters_range # Constrain iters_hi if the max-loop-iters configuration value was set. if self.cfg_max_iters is not None: iters_hi = min(iters_hi, self.cfg_max_iters) if iters_hi < iters_lo: return None # Very occasionally, generate iters_hi iterations (the maximum number # representable) if we've got fuel for it. We don't do this often, # because the instruction sequence will end up just testing loop # handling and be very inefficient for testing anything else. if max_iters >= iters_hi and random.random() < 0.01: enc_val = op_type.op_val_to_enc_val(iters_hi, model.pc) # This should never fail, because iters_hi was encodable. assert enc_val is not None return (enc_val, iters_hi, None) # The rest of the time, we don't usually (95%) generate more than 3 # iterations (because the instruction sequences are rather # repetitive!). Also, don't generate 0 iterations here, even though # it's encodable. That causes an error, so we'll do that in a separate # generator. if random.random() < 0.95: tgt_max_iters = min(max_iters, 3) else: tgt_max_iters = 10000 ub = min(iters_hi, max_iters, tgt_max_iters) lb = max(iters_lo, 1) if ub < lb: return None # Otherwise, pick a value uniformly in [iters_lo, iters_hi]. No need # for clever weighting: in the usual case, there are just 3 # possibilities! num_iters = random.randint(lb, ub) enc_val = op_type.op_val_to_enc_val(num_iters, model.pc) # This should never fail: the choice should have been in the encodable # range. assert enc_val is not None return (enc_val, num_iters, None)
def _pick_loopi_iterations(self, op_type: OperandType, pc: int) -> int: # Like Loop._pick_loopi_iterations but simpler because it doesn't try # to weight towards small counts. assert isinstance(op_type, ImmOperandType) iters_range = op_type.get_op_val_range(pc) assert iters_range is not None iters_lo, iters_hi = iters_range assert 1 <= iters_hi num_iters = random.randint(max(iters_lo, 1), iters_hi) enc_val = op_type.op_val_to_enc_val(num_iters, pc) assert enc_val is not None return enc_val
def pick_operand_value(self, op_type: OperandType) -> Optional[int]: '''Pick a random value for an operand The result will always be non-negative: if the operand is a signed immediate, this is encoded as 2s complement. ''' if isinstance(op_type, RegOperandType): return self.pick_reg_operand_value(op_type) op_rng = op_type.get_op_val_range(self.pc) if op_rng is None: # If we don't know the width, the only immediate that we *know* # is going to be valid is 0. return 0 lo, hi = op_rng op_val = random.randrange(lo, hi + 1) return op_type.op_val_to_enc_val(op_val, self.pc)
def pick_operand_value(self, op_type: OperandType) -> Optional[int]: '''Pick a random value for an operand The result will always be non-negative: if the operand is a signed immediate, this is encoded as 2s complement. ''' if isinstance(op_type, RegOperandType): return self.pick_reg_operand_value(op_type) if isinstance(op_type, ImmOperandType): rng = op_type.get_range() if rng is None: # If we don't know the width, the only immediate that we *know* # is valid is 0. return 0 lo, hi = rng value = random.randrange(lo, hi + 1) return op_type.encode_val(value) raise NotImplementedError('Unknown operand type in ' 'Model.pick_operand_value')
def _pick_iterations(self, op_type: OperandType, bodysize: int, model: Model) -> Optional[Tuple[int, int]]: '''Pick the number of iterations for the loop If this is a LOOP instruction, op_type will be a RegOperandType. In this case, we pick a register whose value we know and which doesn't give us a ridiculous number of iterations (checking model.fuel). Otherwise, op_type is an ImmOperandType and we pick a reasonable iteration count. ''' assert bodysize > 0 min_fuel_per_iter = 1 if bodysize == 1 else 2 # model.fuel - 2 is the fuel after executing the LOOP/LOOPI instruction # and before executing the minimum-length single-instruction # continuation. max_iters = (model.fuel - 2) // min_fuel_per_iter # Never generate more than 10 iterations (because the instruction # sequences would be booooring). Obviously, we'll need to come back to # this when filling coverage holes. # # In general, we'll weight by 1/(1 + abs(iters - 2)). This makes 2 the # most likely iteration count (which is good, because 1 iteration is # boring). max_iters = min(max_iters, 10) if isinstance(op_type, RegOperandType): assert op_type.reg_type == 'gpr' # Iterate over the known registers, trying to pick a weight poss_pairs = [] # type: List[Tuple[int, int]] weights = [] # type: List[float] for idx, value in model.regs_with_known_vals('gpr'): if 0 < value <= max_iters: poss_pairs.append((idx, value)) # Weight higher iteration counts smaller (1 / count) weights.append(1 / (1 + abs(value - 2))) if not poss_pairs: return None return random.choices(poss_pairs, weights=weights)[0] assert isinstance(op_type, ImmOperandType) iters_range = op_type.get_op_val_range(model.pc) assert iters_range is not None iters_lo, iters_hi = iters_range if max_iters < max(1, iters_lo): return None iters_lo = max(iters_lo, 1) iters_hi = min(iters_hi, max_iters) # Pick a value in [iters_lo, iters_hi], weighting lower values more # heavily (1 / count). Since we've made sure that iters_hi <= max_iters # <= 10, we don't need to do any clever maths: we can just use # random.choices with some weights. values = range(iters_lo, 1 + iters_hi) weights = [] for value in values: weights.append(1 / (1 + abs(value - 2))) num_iters = random.choices(values, weights=weights)[0] return (num_iters, num_iters)