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