Checking tool for stuck bits

31.03.2021, 21:47 - Autor: Mark B.
Stuck bits may cause very difficult and not obvious to spot issues. To test for such a case I wrote this script...

The idea behind the script ist that in normal data the count of bits containing 0 and 1 should be statistaically arround the 50% mark. Data get internally transferes in many devices on so-called data lanes and if one of then is shorted or broken each bit transfered over that lane is always 0 or 1. So if we spot a 0% or 100% value on a speciffic bit we can be pretty certain that we deal with a "stuck bit".

The following script shows bit statistics for 64, 32, 16, 8 and 4 bit data-lanes:

C:\Users\DISK DOCTOR\Desktop>py.exe -3.8 test.py "Generic Storage Device 0.dsk" 100000
Reading sector no. 10000 of 10000

REPORT FOR 'Generic Storage Device 0.dsk':
Counting bits which are 1

BIT |   % |   % |   % |   % |   %
-----------------------------------
  0 | 50% | 49% | 49% | 49% | 48% |
  1 | 49% | 49% | 48% | 48% | 48% |
  2 |  0% |  0% |  0% |  0% |  0% |
  3 | 49% | 49% | 49% | 49% | 48% |
  4 | 49% | 49% | 48% | 48% |
  5 | 49% | 49% | 49% | 49% |
  6 |  0% |  0% |  0% |  0% |
  7 | 48% | 48% | 48% | 48% |
  8 | 49% | 49% | 49% |
  9 | 49% | 49% | 48% |
 10 |  0% |  0% |  0% |
 11 | 50% | 50% | 49% |
 12 | 48% | 48% | 48% |
 13 | 49% | 49% | 48% |
 14 |  0% |  0% |  0% |
 15 | 48% | 47% | 47% |
 16 | 48% | 48% |
 17 | 48% | 48% |
 18 |  0% |  0% |
 19 | 48% | 48% |
 20 | 48% | 48% |
 21 | 48% | 48% |
 22 |  0% |  0% |
 23 | 48% | 47% |
 24 | 48% | 48% |
 25 | 48% | 48% |
 26 |  0% |  0% |
 27 | 48% | 48% |
 28 | 48% | 48% |
 29 | 48% | 48% |
 30 |  0% |  0% |
 31 | 47% | 47% |
 32 | 48% |
 33 | 49% |
 34 |  0% |
 35 | 49% |
 36 | 49% |
 37 | 49% |
 38 |  0% |
 39 | 48% |
 40 | 49% |
 41 | 48% |
 42 |  0% |
 43 | 50% |
 44 | 48% |
 45 | 49% |
 46 |  0% |
 47 | 47% |
 48 | 48% |
 49 | 48% |
 50 |  0% |
 51 | 48% |
 52 | 48% |
 53 | 48% |
 54 |  0% |
 55 | 47% |
 56 | 48% |
 57 | 48% |
 58 |  0% |
 59 | 48% |
 60 | 48% |
 61 | 48% |
 62 |  0% |
 63 | 47% |

Based on that output we can conclude that we have stucked bits on data-lane 2! The pattern OK, OK, STUCK and OK from the 4 bit example repeats 2x for 8bit, 4x for 16bit, etc.

Source:

import traceback, sys, os

if __name__ == "__main__":
    ts = time.time()
    def read_file(path, max_sectors = 10000):
        sum_bits64 = [0] * 64
        sum_bits32 = [0] * 32
        sum_bits16 = [0] * 16
        sum_bits8  = [0] * 8
        sum_bits4  = [0] * 4
        ctr = 0
        
        try:
            with open(path, "rb") as f:
                while True:
                    sector = f.read(512)
                    
                    # Skip empty sectors
                    if sector == b"\x00" * 512:
                        continue

                    # End when there is no more data
                    if sector == b"" or sector == None:
                        break

                    ctr += 1
                    
                    # Read sector and sum up bits
                    for i in range(0, 512, 8):
                        byte = sector[i:(i+8)]

                        try:
                            bits = [0] * 64
                            for j in range(8):
                                for k in range(8):
                                    pos = j * 8 + k
                                    bits[pos] = ((byte[j] >> k) & 1)
                        except IndexError:
                            break
                            
                        sum_bits64 = [x + y for x, y in zip(sum_bits64, bits)]
                        sum_bits32 = [x + y for x, y in zip(sum_bits64[0:32], sum_bits64[32:])]
                        sum_bits16 = [x + y for x, y in zip(sum_bits32[0:16], sum_bits32[16:])]
                        sum_bits8  = [x + y for x, y in zip(sum_bits16[0:8], sum_bits16[8:])]
                        sum_bits4  = [x + y for x, y in zip(sum_bits8[0:4], sum_bits8[4:])]
                        
                    # Output progress message
                    if not ctr % 10:
                        print(f"Reading sector no. {ctr} of {max_sectors}", end = "\r")
                        sys.stdout.flush()

                    # End if max. number of sectors are tested
                    if ctr >= max_sectors:
                        break
                    
        except FileNotFoundError:
            print(f"FILE {path} NOT FOUND!")
            return 1
        except PermissionError:
            print(f"CAN'T ACCESS FILE {path}!")
            return 2

        # Print report
        print(f"\n\nREPORT FOR '{path}':")
        print("Counting bits which are 1\n")
        line = ""

        print("BIT |   % |   % |   % |   % |   %  ")
        print("-----------------------------------")
        for i in range(64):
            res = i 

            if i < 4:
                line =  f"{i:3} | "
                line += f"{int((sum_bits64[i] * 100) // ((ctr*512)/8)):2}% | "
                line += f"{int((sum_bits32[i] * 100) // ((ctr*512)/4)):2}% | "
                line += f"{int((sum_bits16[i] * 100) // ((ctr*512)/2)):2}% | "
                line += f"{int((sum_bits8[i] * 100) // ((ctr*512))):2}% | "
                line += f"{int((sum_bits4[i] * 100) // ((ctr*512)*2)):2}% |"

            elif i < 8:
                line =  f"{i:3} | "
                line += f"{int((sum_bits64[i] * 100) // ((ctr*512)/8)):2}% | "
                line += f"{int((sum_bits32[i] * 100) // ((ctr*512)/4)):2}% | "
                line += f"{int((sum_bits16[i] * 100) // ((ctr*512)/2)):2}% | "
                line += f"{int((sum_bits8[i] * 100) // ((ctr*512))):2}% | "

            elif i < 16:
                line =  f"{i:3} | "
                line += f"{int((sum_bits64[i] * 100) // ((ctr*512)/8)):2}% | "
                line += f"{int((sum_bits32[i] * 100) // ((ctr*512)/4)):2}% | "
                line += f"{int((sum_bits16[i] * 100) // ((ctr*512)/2)):2}% | "

            elif i < 32:
                line =  f"{i:3} | "
                line += f"{int((sum_bits64[i] * 100) // ((ctr*512)/8)):2}% | "
                line += f"{int((sum_bits32[i] * 100) // ((ctr*512)/4)):2}% | "

            else:
                line =  f"{i:3} | "
                line += f"{int((sum_bits64[i] * 100) // ((ctr*512)/8)):2}% | "

            print(line)


    def usage():
        print("\nUSAGE:\n------")
        print(f"{os.path.basename(sys.argv[0])} /path/to/file.dd [no. of sectors to test]\n")
        print(f"e.g.: \n{os.path.basename(sys.argv[0])} C:/Users/DISKDOCTOR/Desktop/file.dd 10000\n")
        a = b[4]
        quit()
        

    # Check arguments
    if "-h" in sys.argv or "--help" in sys.argv or len(sys.argv) < 2 or len(sys.argv) > 3:
        usage()

    try: 
        read_file(sys.argv[1], int(sys.argv[2]))
    except IndexError:
        read_file(sys.argv[1])
    except TypeError:
        usage()


Download

System requrements:

Windows, OSX or Linux with