| 1 | #!/usr/bin/env python3 |
| 2 | # |
| 3 | # validate-memory-counts.py: check we instrumented memory properly |
| 4 | # |
| 5 | # This program takes two inputs: |
| 6 | # - the mem plugin output |
| 7 | # - the memory binary output |
| 8 | # |
| 9 | # Copyright (C) 2024 Linaro Ltd |
| 10 | # |
| 11 | # SPDX-License-Identifier: GPL-2.0-or-later |
| 12 | |
| 13 | import sys |
| 14 | from argparse import ArgumentParser |
| 15 | |
| 16 | def extract_counts(path): |
| 17 | """ |
| 18 | Load the output from path and extract the lines containing: |
| 19 | |
| 20 | Test data start: 0x40214000 |
| 21 | Test data end: 0x40218001 |
| 22 | Test data read: 2522280 |
| 23 | Test data write: 262111 |
| 24 | |
| 25 | From the stream of data. Extract the values for use in the |
| 26 | validation function. |
| 27 | """ |
| 28 | start_address = None |
| 29 | end_address = None |
| 30 | read_count = 0 |
| 31 | write_count = 0 |
| 32 | with open(path, 'r') as f: |
| 33 | for line in f: |
| 34 | if line.startswith("Test data start:"): |
| 35 | start_address = int(line.split(':')[1].strip(), 16) |
| 36 | elif line.startswith("Test data end:"): |
| 37 | end_address = int(line.split(':')[1].strip(), 16) |
| 38 | elif line.startswith("Test data read:"): |
| 39 | read_count = int(line.split(':')[1].strip()) |
| 40 | elif line.startswith("Test data write:"): |
| 41 | write_count = int(line.split(':')[1].strip()) |
| 42 | return start_address, end_address, read_count, write_count |
| 43 | |
| 44 | |
| 45 | def parse_plugin_output(path, start, end): |
| 46 | """ |
| 47 | Load the plugin output from path in the form of: |
| 48 | |
| 49 | Region Base, Reads, Writes, Seen all |
| 50 | 0x0000000040004000, 31093, 0, false |
| 51 | 0x0000000040214000, 2522280, 278579, true |
| 52 | 0x0000000040000000, 137398, 0, false |
| 53 | 0x0000000040210000, 54727397, 33721956, false |
| 54 | |
| 55 | And extract the ranges that match test data start and end and |
| 56 | return the results. |
| 57 | """ |
| 58 | total_reads = 0 |
| 59 | total_writes = 0 |
| 60 | seen_all = False |
| 61 | |
| 62 | with open(path, 'r') as f: |
| 63 | next(f) # Skip the header |
| 64 | for line in f: |
| 65 | |
| 66 | if line.startswith("Region Base"): |
| 67 | continue |
| 68 | |
| 69 | parts = line.strip().split(', ') |
| 70 | if len(parts) != 4: |
| 71 | continue |
| 72 | |
| 73 | region_base = int(parts[0], 16) |
| 74 | reads = int(parts[1]) |
| 75 | writes = int(parts[2]) |
| 76 | |
| 77 | if start <= region_base < end: # Checking if within range |
| 78 | total_reads += reads |
| 79 | total_writes += writes |
| 80 | seen_all = parts[3] == "true" |
| 81 | |
| 82 | return total_reads, total_writes, seen_all |
| 83 | |
| 84 | def main() -> None: |
| 85 | """ |
| 86 | Process the arguments, injest the program and plugin out and |
| 87 | verify they match up and report if they do not. |
| 88 | """ |
| 89 | parser = ArgumentParser(description="Validate memory instrumentation") |
| 90 | parser.add_argument('test_output', |
| 91 | help="The output from the test itself") |
| 92 | parser.add_argument('plugin_output', |
| 93 | help="The output from memory plugin") |
| 94 | parser.add_argument('--bss-cleared', |
| 95 | action='store_true', |
| 96 | help='Assume bss was cleared (and adjusts counts).') |
| 97 | |
| 98 | args = parser.parse_args() |
| 99 | |
| 100 | # Extract counts from memory binary |
| 101 | start, end, exp_reads, exp_writes = extract_counts(args.test_output) |
| 102 | |
| 103 | # Some targets clear BSS before running but the test doesn't know |
| 104 | # that so we adjust it by the size of the test region. |
| 105 | if args.bss_cleared: |
| 106 | exp_writes += 16384 |
| 107 | |
| 108 | if start is None or end is None: |
| 109 | print("Failed to test_data boundaries from output.") |
| 110 | sys.exit(1) |
| 111 | |
| 112 | # Parse plugin output |
| 113 | preads, pwrites, seen_all = parse_plugin_output(args.plugin_output, |
| 114 | start, end) |
| 115 | |
| 116 | if not seen_all: |
| 117 | print("Fail: didn't instrument all accesses to test_data.") |
| 118 | sys.exit(1) |
| 119 | |
| 120 | # Compare and report |
| 121 | if preads == exp_reads and pwrites == exp_writes: |
| 122 | sys.exit(0) |
| 123 | else: |
| 124 | print("Fail: The memory reads and writes count does not match.") |
| 125 | print(f"Expected Reads: {exp_reads}, Actual Reads: {preads}") |
| 126 | print(f"Expected Writes: {exp_writes}, Actual Writes: {pwrites}") |
| 127 | sys.exit(1) |
| 128 | |
| 129 | if __name__ == "__main__": |
| 130 | main() |