def watch_scanner(d, addrs, verbose = True, block_wordcount = 4, memo_filename = None):
    """Repeatedly scan memory, in randomized order, looking for changes.

    When a change is found, we yield:
       (timestamp, address, new_value, old_value).

    Timestamps are as reported by time.time(). It's the first time we noticed
    this change, timestamped as soon as possible.

    Addresses are a list of things to watch:

       (addr,)
         Single numbers are the address of a word to watch. This guarantees
         that the address is only read as that one single word. We won't read
         anything nearby, and each read will be a single peek.

       (a, b)
         Two elements indicate a range of addresses from a to b, including
         both endpoints. These are broken into randomized sections of exactly
         block_wordcount words. To reduce unwanted statistical correlations,
         we end up scanning a little past the beginning and end, but any
         addresses outside the range is not reported.
    """

    now = time.time()
    start_timestamp = now
    output_timestamp = now
    round_number = 0

    # We can use a file on disk as memo for debugging or if we need something
    # that handles large sparse address spaces, but usually cStringIO is fine.
    if memo_filename:
        memo = open(memo_filename, 'w+b')
    else:
        memo = cStringIO.StringIO()

    # Initialize the memo with current memory contents
    for addr, fn in break_up_addresses(d, addrs, block_wordcount):
        memo.seek(addr)
        memo.write(fn())

    # Scan in an endless series of shuffled rounds
    while True:
        round_number += 1
        byte_count = 0

        for addr, fn in break_up_addresses(d, addrs, block_wordcount):

            # Timestamp as soon as the block read comes back.
            # If there's an error, keep on going. TinySCSI complains enough already.
            try:
                block = fn()
            except __HOLE__:
                continue

            timestamp = time.time()
            byte_count += len(block)

            # Keep track of differences with our memo buffer
            memo.seek(addr)
            memo_block = memo.read(len(block))
            if block == memo_block:
                continue
            
            # If the block has changed, memoize it and report word-by-word diffs
            memo.seek(addr)
            memo.write(block)

            block_wordcount = len(block) // 4
            block_words = struct.unpack('<%dI' % block_wordcount, block[:block_wordcount * 4])
            memo_words = struct.unpack('<%dI' % block_wordcount, memo_block[:block_wordcount * 4])

            for i in range(block_wordcount):
                old_value = memo_words[i]
                new_value = block_words[i]
                if new_value != old_value:
                    yield (timestamp, addr + i*4, new_value, old_value)

        # Report status
        now = time.time()
        if verbose and now > output_timestamp + 1.0:
            output_timestamp = now
            print "* scanning %d bytes at %.03f Hz" % (byte_count, round_number / (now - start_timestamp))
dataset/ETHPy150Open scanlime/coastermelt/backdoor/watch.py/watch_scanner