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