1#!/usr/bin/env python3 2# 3# Copyright 2019 The Hafnium Authors. 4# 5# Use of this source code is governed by a BSD-style 6# license that can be found in the LICENSE file or at 7# https://opensource.org/licenses/BSD-3-Clause. 8 9"""Check ELF file for assembly-level regressions. 10 11Objdumps the given ELF file and detects known assembly patterns, checking for 12regressions on bugs such as CPU erratas. Throws an exception if a broken pattern 13is detected. 14""" 15 16import argparse 17import os 18import re 19import subprocess 20import sys 21 22OBJDUMP = "llvm-objdump" 23NM = "llvm-nm" 24 25def check_eret_speculation_barrier(args): 26 """ 27 Some ARM64 CPUs speculatively execute instructions after ERET. 28 Check that every ERET is followed by DSB NSH and ISB. 29 """ 30 31 objdump_stdout = subprocess\ 32 .check_output([ OBJDUMP, "--no-show-raw-insn", "-d", args.input_elf ])\ 33 .decode("utf-8")\ 34 .splitlines() 35 36 found_eret = False 37 38 STATE_DEFAULT = 1 39 STATE_EXPECT_DSB_NSH = 2 40 STATE_EXPECT_ISB = 3 41 42 REGEX_ERET = re.compile(r"^\s*[0-9a-f]+:\s*eret$") 43 REGEX_DSB_NSH = re.compile(r"^\s*[0-9a-f]+:\s*dsb\s+nsh$") 44 REGEX_ISB = re.compile(r"^\s*[0-9a-f]+:\s*isb$") 45 46 state = STATE_DEFAULT 47 for line in objdump_stdout: 48 if state == STATE_DEFAULT: 49 if re.match(REGEX_ERET, line): 50 found_eret = True 51 state = STATE_EXPECT_DSB_NSH 52 elif state == STATE_EXPECT_DSB_NSH: 53 if re.match(REGEX_DSB_NSH, line): 54 state = STATE_EXPECT_ISB 55 else: 56 raise Exception("ERET not followed by DSB NSH") 57 elif state == STATE_EXPECT_ISB: 58 if re.match(REGEX_ISB, line): 59 state = STATE_DEFAULT 60 else: 61 raise Exception("ERET not followed by ISB") 62 63 # Ensure that at least one instance was found, otherwise the regexes are 64 # probably wrong. 65 if not found_eret: 66 raise Exception("Could not find any ERET instructions") 67 68def check_max_image_size(args): 69 """ 70 Check that the ELF's effective image size does not exceed maximum 71 allowed image size, if specified in command-line arguments. 72 """ 73 74 if args.max_image_size <= 0: 75 return 76 77 nm_stdout = subprocess\ 78 .check_output([ NM, args.input_elf ])\ 79 .decode("utf-8")\ 80 .splitlines() 81 82 COLUMN_COUNT = 3 83 COLUMN_IDX_VALUE = 0 84 COLUMN_IDX_TYPE = 1 85 COLUMN_IDX_NAME = 2 86 87 image_size = None 88 for line in nm_stdout: 89 line = line.split() 90 if len(line) != COLUMN_COUNT: 91 raise Exception( 92 "Unexpected number of columns in NM output") 93 94 if line[COLUMN_IDX_NAME] == "image_size": 95 if line[COLUMN_IDX_TYPE] != "A": 96 raise Exception( 97 "Unexpected type of image_size symbol") 98 image_size = int(line[COLUMN_IDX_VALUE], 16) 99 break 100 101 if image_size is None: 102 raise Exception("Could not find value of image_size symbol") 103 elif image_size > args.max_image_size: 104 raise Exception( 105 "Image size exceeds maximum allowed image size " + 106 "({}B > {}B)".format(image_size, args.max_image_size)) 107 108def Main(): 109 parser = argparse.ArgumentParser() 110 parser.add_argument("input_elf", 111 help="ELF file to analyze") 112 parser.add_argument("stamp_file", 113 help="file to be touched if successful") 114 parser.add_argument("--max-image-size", 115 required=False, type=int, default=0, 116 help="maximum allowed image size in bytes") 117 args = parser.parse_args() 118 119 check_eret_speculation_barrier(args) 120 check_max_image_size(args) 121 122 # Touch `stamp_file`. 123 with open(args.stamp_file, "w"): 124 pass 125 126 return 0 127 128if __name__ == "__main__": 129 sys.exit(Main()) 130