cachepc-qemu

Fork of AMDESE/qemu with changes for cachepc side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-qemu
Log | Files | Refs | Submodules | LICENSE | sfeed.txt

dissect.py (6718B)


      1#!/usr/bin/env python3
      2
      3#  Print the percentage of instructions spent in each phase of QEMU
      4#  execution.
      5#
      6#  Syntax:
      7#  dissect.py [-h] -- <qemu executable> [<qemu executable options>] \
      8#                   <target executable> [<target executable options>]
      9#
     10#  [-h] - Print the script arguments help message.
     11#
     12#  Example of usage:
     13#  dissect.py -- qemu-arm coulomb_double-arm
     14#
     15#  This file is a part of the project "TCG Continuous Benchmarking".
     16#
     17#  Copyright (C) 2020  Ahmed Karaman <ahmedkhaledkaraman@gmail.com>
     18#  Copyright (C) 2020  Aleksandar Markovic <aleksandar.qemu.devel@gmail.com>
     19#
     20#  This program is free software: you can redistribute it and/or modify
     21#  it under the terms of the GNU General Public License as published by
     22#  the Free Software Foundation, either version 2 of the License, or
     23#  (at your option) any later version.
     24#
     25#  This program is distributed in the hope that it will be useful,
     26#  but WITHOUT ANY WARRANTY; without even the implied warranty of
     27#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
     28#  GNU General Public License for more details.
     29#
     30#  You should have received a copy of the GNU General Public License
     31#  along with this program. If not, see <https://www.gnu.org/licenses/>.
     32
     33import argparse
     34import os
     35import subprocess
     36import sys
     37import tempfile
     38
     39
     40def get_JIT_line(callgrind_data):
     41    """
     42    Search for the first instance of the JIT call in
     43    the callgrind_annotate output when ran using --tree=caller
     44    This is equivalent to the self number of instructions of JIT.
     45
     46    Parameters:
     47    callgrind_data (list): callgrind_annotate output
     48
     49    Returns:
     50    (int): Line number
     51    """
     52    line = -1
     53    for i in range(len(callgrind_data)):
     54        if callgrind_data[i].strip('\n') and \
     55                callgrind_data[i].split()[-1] == "[???]":
     56            line = i
     57            break
     58    if line == -1:
     59        sys.exit("Couldn't locate the JIT call ... Exiting.")
     60    return line
     61
     62
     63def main():
     64    # Parse the command line arguments
     65    parser = argparse.ArgumentParser(
     66        usage='dissect.py [-h] -- '
     67        '<qemu executable> [<qemu executable options>] '
     68        '<target executable> [<target executable options>]')
     69
     70    parser.add_argument('command', type=str, nargs='+', help=argparse.SUPPRESS)
     71
     72    args = parser.parse_args()
     73
     74    # Extract the needed variables from the args
     75    command = args.command
     76
     77    # Insure that valgrind is installed
     78    check_valgrind = subprocess.run(
     79        ["which", "valgrind"], stdout=subprocess.DEVNULL)
     80    if check_valgrind.returncode:
     81        sys.exit("Please install valgrind before running the script.")
     82
     83    # Save all intermediate files in a temporary directory
     84    with tempfile.TemporaryDirectory() as tmpdirname:
     85        # callgrind output file path
     86        data_path = os.path.join(tmpdirname, "callgrind.data")
     87        # callgrind_annotate output file path
     88        annotate_out_path = os.path.join(tmpdirname, "callgrind_annotate.out")
     89
     90        # Run callgrind
     91        callgrind = subprocess.run((["valgrind",
     92                                     "--tool=callgrind",
     93                                     "--callgrind-out-file=" + data_path]
     94                                    + command),
     95                                   stdout=subprocess.DEVNULL,
     96                                   stderr=subprocess.PIPE)
     97        if callgrind.returncode:
     98            sys.exit(callgrind.stderr.decode("utf-8"))
     99
    100        # Save callgrind_annotate output
    101        with open(annotate_out_path, "w") as output:
    102            callgrind_annotate = subprocess.run(
    103                ["callgrind_annotate", data_path, "--tree=caller"],
    104                stdout=output,
    105                stderr=subprocess.PIPE)
    106            if callgrind_annotate.returncode:
    107                sys.exit(callgrind_annotate.stderr.decode("utf-8"))
    108
    109        # Read the callgrind_annotate output to callgrind_data[]
    110        callgrind_data = []
    111        with open(annotate_out_path, 'r') as data:
    112            callgrind_data = data.readlines()
    113
    114        # Line number with the total number of instructions
    115        total_instructions_line_number = 20
    116        # Get the total number of instructions
    117        total_instructions_line_data = \
    118            callgrind_data[total_instructions_line_number]
    119        total_instructions = total_instructions_line_data.split()[0]
    120        total_instructions = int(total_instructions.replace(',', ''))
    121
    122        # Line number with the JIT self number of instructions
    123        JIT_self_instructions_line_number = get_JIT_line(callgrind_data)
    124        # Get the JIT self number of instructions
    125        JIT_self_instructions_line_data = \
    126            callgrind_data[JIT_self_instructions_line_number]
    127        JIT_self_instructions = JIT_self_instructions_line_data.split()[0]
    128        JIT_self_instructions = int(JIT_self_instructions.replace(',', ''))
    129
    130        # Line number with the JIT self + inclusive number of instructions
    131        # It's the line above the first JIT call when running with --tree=caller
    132        JIT_total_instructions_line_number = JIT_self_instructions_line_number-1
    133        # Get the JIT self + inclusive number of instructions
    134        JIT_total_instructions_line_data = \
    135            callgrind_data[JIT_total_instructions_line_number]
    136        JIT_total_instructions = JIT_total_instructions_line_data.split()[0]
    137        JIT_total_instructions = int(JIT_total_instructions.replace(',', ''))
    138
    139        # Calculate number of instructions in helpers and code generation
    140        helpers_instructions = JIT_total_instructions-JIT_self_instructions
    141        code_generation_instructions = total_instructions-JIT_total_instructions
    142
    143        # Print results (Insert commas in large numbers)
    144        # Print total number of instructions
    145        print('{:<20}{:>20}\n'.
    146              format("Total Instructions:",
    147                     format(total_instructions, ',')))
    148        # Print code generation instructions and percentage
    149        print('{:<20}{:>20}\t{:>6.3f}%'.
    150              format("Code Generation:",
    151                     format(code_generation_instructions, ","),
    152                     (code_generation_instructions / total_instructions) * 100))
    153        # Print JIT instructions and percentage
    154        print('{:<20}{:>20}\t{:>6.3f}%'.
    155              format("JIT Execution:",
    156                     format(JIT_self_instructions, ","),
    157                     (JIT_self_instructions / total_instructions) * 100))
    158        # Print helpers instructions and percentage
    159        print('{:<20}{:>20}\t{:>6.3f}%'.
    160              format("Helpers:",
    161                     format(helpers_instructions, ","),
    162                     (helpers_instructions/total_instructions)*100))
    163
    164
    165if __name__ == "__main__":
    166    main()