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
13import sys
14from argparse import ArgumentParser
15
16def 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
45def 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
84def 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
129if __name__ == "__main__":
130    main()
131