docs: def interpret(self, startpos, args, addr=None, simfd=None):
          """
          implement scanf - extract formatted data from memory or a file according to the stored format
          specifiers and store them into the pointers extracted from `args`.
  
          :param startpos:    The index of the first argument corresponding to the first format element
          :param args:        A function which, given the index of an argument to the function, returns that argument
          :param addr:        The address in the memory to extract data from, or...
          :param simfd:       A file descriptor to use for reading data from
          :return:            The number of arguments parsed
          """
          if simfd is not None and isinstance(simfd.read_storage, SimPackets):
              argnum = startpos
              for component in self.components:
                  if type(component) is bytes:
                      sdata, _ = simfd.read_data(len(component), short_reads=False)
                      self.state.solver.add(sdata == component)
                  elif isinstance(component, claripy.Bits):
                      sdata, _ = simfd.read_data(len(component) // 8, short_reads=False)
                      self.state.solver.add(sdata == component)
                  elif component.spec_type == b's':
                      if component.length_spec is None:
                          sdata, slen = simfd.read_data(self.state.libc.buf_symbolic_bytes)
                      else:
                          sdata, slen = simfd.read_data(component.length_spec)
                      for byte in sdata.chop(8):
                          self.state.solver.add(claripy.And(*[byte != char for char in self.SCANF_DELIMITERS]))
                      self.state.memory.store(args(argnum), sdata, size=slen)
                      self.state.memory.store(args(argnum) + slen, claripy.BVV(0, 8))
                      argnum += 1
                  elif component.spec_type == b'c':
                      sdata, _ = simfd.read_data(1, short_reads=False)
                      self.state.memory.store(args(argnum), sdata)
                      argnum += 1
                  else:
                      bits = component.size * 8
                      if component.spec_type == b'x':
                          base = 16
                      elif component.spec_type == b'o':
                          base = 8
                      else:
                          base = 10
  
                      # here's the variable representing the result of the parsing
                      target_variable = self.state.solver.BVS('scanf_' + component.string.decode(), bits,
                              key=('api', 'scanf', argnum - startpos, component.string))
                      negative = claripy.SLT(target_variable, 0)
  
                      # how many digits does it take to represent this variable fully?
                      max_digits = int(math.ceil(math.log(2**bits, base)))
  
                      # how many digits does the format specify?
                      spec_digits = component.length_spec
  
                      # how many bits can we specify as input?
                      available_bits = float('inf') if spec_digits is None else spec_digits * math.log(base, 2)
                      not_enough_bits = available_bits < bits
  
                      # how many digits will we model this input as?
                      digits = max_digits if spec_digits is None else spec_digits
  
                      # constrain target variable range explicitly if it can't take on all possible values
                      if not_enough_bits:
                          self.state.solver.add(self.state.solver.And(
                              self.state.solver.SLE(target_variable, (base**digits) - 1),
                              self.state.solver.SGE(target_variable, -(base**(digits - 1) - 1))))
  
                      # perform the parsing in reverse - constrain the input digits to be the string version of the input
                      # this only works because we're reading from a packet stream and therefore nobody has the ability
                      # to add other constraints to this data!
                      # this makes z3's job EXTREMELY easy
                      sdata, _ = simfd.read_data(digits, short_reads=False)
                      for i, digit in enumerate(reversed(sdata.chop(8))):
                          digit_value = (target_variable // (base**i)) % base
                          digit_ascii = digit_value + ord('0')
                          if base > 10:
                              digit_ascii = claripy.If(digit_value >= 10, digit_value + (-10 + ord('a')), digit_ascii)
  
                          # if there aren't enough bits, we can increase the range by accounting for the possibility that
                          # the first digit is a minus sign
                          if not_enough_bits:
                              if i == digits - 1:
                                  neg_digit_ascii = ord('-')
                              else:
                                  neg_digit_value = (-target_variable // (base**i)) % base
                                  neg_digit_ascii = neg_digit_value + ord('0')
                                  if base > 10:
                                      neg_digit_ascii = claripy.If(neg_digit_value >= 10, neg_digit_value + (-10 + ord('a')), neg_digit_ascii)
  
                              digit_ascii = claripy.If(negative, neg_digit_ascii, digit_ascii)
  
                          self.state.solver.add(digit == digit_ascii[7:0])
  
                      self.state.memory.store(args(argnum), target_variable, endness=self.state.arch.memory_endness)
                      argnum += 1
  
              return argnum - startpos
  
          # TODO: we only support one format specifier in interpretation for now
  
          format_specifier_count = sum(1 for x in self.components if isinstance(x, FormatSpecifier))
          if format_specifier_count > 1:
              l.warning("We don't support more than one format specifiers in format strings.")
  
          if simfd is not None:
              region = simfd.read_storage
              addr = simfd._pos if hasattr(simfd, '_pos') else simfd._read_pos # XXX THIS IS BAD
          else:
              region = self.parser.state.memory
  
          bits = self.parser.state.arch.bits
          failed = self.parser.state.solver.BVV(0, bits)
          argpos = startpos
          position = addr
          for component in self.components:
              if isinstance(component, bytes):
                  # TODO we skip non-format-specifiers in format string interpretation for now
                  # if the region doesn't match the concrete component, we need to return immediately
                  pass
              else:
                  fmt_spec = component
                  try:
                      dest = args(argpos)
                  except SimProcedureArgumentError:
                      dest = None
                  if fmt_spec.spec_type == b's':
                      # set some limits for the find
                      max_str_len = self.parser.state.libc.max_str_len
                      max_sym_bytes = self.parser.state.libc.buf_symbolic_bytes
  
                      # has the length of the format been limited by the string itself?
                      if fmt_spec.length_spec is not None:
                          max_str_len = fmt_spec.length_spec
                          max_sym_bytes = fmt_spec.length_spec
  
                      # TODO: look for limits on other characters which scanf is sensitive to, '\x00', '\x20'
                      ohr, ohc, ohi = region.find(position, self.parser.state.solver.BVV(b'\n'), max_str_len, max_symbolic_bytes=max_sym_bytes)
  
                      # if no newline is found, mm is position + max_strlen
                      # If-branch will really only happen for format specifiers with a length
                      mm = self.parser.state.solver.If(ohr == 0, position + max_str_len, ohr)
                      # we're just going to concretize the length, load will do this anyways
                      length = self.parser.state.solver.max_int(mm - position)
                      src_str = region.load(position, length)
  
                      # TODO all of these should be delimiters we search for above
                      # add that the contents of the string cannot be any scanf %s string delimiters
                      for delimiter in set(FormatString.SCANF_DELIMITERS):
                          delim_bvv = self.parser.state.solver.BVV(delimiter)
                          for i in range(length):
                              self.parser.state.add_constraints(region.load(position + i, 1) != delim_bvv)
  
                      # write it out to the pointer
                      self.parser.state.memory.store(dest, src_str)
                      # store the terminating null byte
                      self.parser.state.memory.store(dest + length, self.parser.state.solver.BVV(0, 8))
  
                      position += length
  
                  else:
  
                      # XXX: atoi only supports strings of one byte
                      if fmt_spec.spec_type in [b'd', b'i', b'u', b'x']:
                          base = 16 if fmt_spec.spec_type == b'x' else 10
                          status, i, num_bytes = self.parser._sim_atoi_inner(position, region, base=base, read_length=fmt_spec.length_spec)
                          # increase failed count if we were unable to parse it
                          failed = self.parser.state.solver.If(status, failed, failed + 1)
                          position += num_bytes
                      elif fmt_spec.spec_type == b'c':
                          i = region.load(position, 1)
                          i = i.zero_extend(bits - 8)
                          position += 1
                      else:
                          raise SimProcedureError("unsupported format spec '%s' in interpret" % fmt_spec.spec_type)
  
                      i = self.parser.state.solver.Extract(fmt_spec.size*8-1, 0, i)
                      self.parser.state.memory.store(dest, i, size=fmt_spec.size, endness=self.parser.state.arch.memory_endness)
  
                  argpos += 1
  
          if simfd is not None:
              simfd.read_data(position - addr)
  
          return (argpos - startpos) - failed