| #!/usr/bin/env python3 |
| # |
| # validate-memory-counts.py: check we instrumented memory properly |
| # |
| # This program takes two inputs: |
| # - the mem plugin output |
| # - the memory binary output |
| # |
| # Copyright (C) 2024 Linaro Ltd |
| # |
| # SPDX-License-Identifier: GPL-2.0-or-later |
| |
| import sys |
| from argparse import ArgumentParser |
| |
| def extract_counts(path): |
| """ |
| Load the output from path and extract the lines containing: |
| |
| Test data start: 0x40214000 |
| Test data end: 0x40218001 |
| Test data read: 2522280 |
| Test data write: 262111 |
| |
| From the stream of data. Extract the values for use in the |
| validation function. |
| """ |
| start_address = None |
| end_address = None |
| read_count = 0 |
| write_count = 0 |
| with open(path, 'r') as f: |
| for line in f: |
| if line.startswith("Test data start:"): |
| start_address = int(line.split(':')[1].strip(), 16) |
| elif line.startswith("Test data end:"): |
| end_address = int(line.split(':')[1].strip(), 16) |
| elif line.startswith("Test data read:"): |
| read_count = int(line.split(':')[1].strip()) |
| elif line.startswith("Test data write:"): |
| write_count = int(line.split(':')[1].strip()) |
| return start_address, end_address, read_count, write_count |
| |
| |
| def parse_plugin_output(path, start, end): |
| """ |
| Load the plugin output from path in the form of: |
| |
| Region Base, Reads, Writes, Seen all |
| 0x0000000040004000, 31093, 0, false |
| 0x0000000040214000, 2522280, 278579, true |
| 0x0000000040000000, 137398, 0, false |
| 0x0000000040210000, 54727397, 33721956, false |
| |
| And extract the ranges that match test data start and end and |
| return the results. |
| """ |
| total_reads = 0 |
| total_writes = 0 |
| seen_all = False |
| |
| with open(path, 'r') as f: |
| next(f) # Skip the header |
| for line in f: |
| |
| if line.startswith("Region Base"): |
| continue |
| |
| parts = line.strip().split(', ') |
| if len(parts) != 4: |
| continue |
| |
| region_base = int(parts[0], 16) |
| reads = int(parts[1]) |
| writes = int(parts[2]) |
| |
| if start <= region_base < end: # Checking if within range |
| total_reads += reads |
| total_writes += writes |
| seen_all = parts[3] == "true" |
| |
| return total_reads, total_writes, seen_all |
| |
| def main() -> None: |
| """ |
| Process the arguments, injest the program and plugin out and |
| verify they match up and report if they do not. |
| """ |
| parser = ArgumentParser(description="Validate memory instrumentation") |
| parser.add_argument('test_output', |
| help="The output from the test itself") |
| parser.add_argument('plugin_output', |
| help="The output from memory plugin") |
| parser.add_argument('--bss-cleared', |
| action='store_true', |
| help='Assume bss was cleared (and adjusts counts).') |
| |
| args = parser.parse_args() |
| |
| # Extract counts from memory binary |
| start, end, exp_reads, exp_writes = extract_counts(args.test_output) |
| |
| # Some targets clear BSS before running but the test doesn't know |
| # that so we adjust it by the size of the test region. |
| if args.bss_cleared: |
| exp_writes += 16384 |
| |
| if start is None or end is None: |
| print("Failed to test_data boundaries from output.") |
| sys.exit(1) |
| |
| # Parse plugin output |
| preads, pwrites, seen_all = parse_plugin_output(args.plugin_output, |
| start, end) |
| |
| if not seen_all: |
| print("Fail: didn't instrument all accesses to test_data.") |
| sys.exit(1) |
| |
| # Compare and report |
| if preads == exp_reads and pwrites == exp_writes: |
| sys.exit(0) |
| else: |
| print("Fail: The memory reads and writes count does not match.") |
| print(f"Expected Reads: {exp_reads}, Actual Reads: {preads}") |
| print(f"Expected Writes: {exp_writes}, Actual Writes: {pwrites}") |
| sys.exit(1) |
| |
| if __name__ == "__main__": |
| main() |