Add optional build flag to check effective image size

Some targets will have a static-size memory carveout dedicated
to the hypervisor. In those cases, we should assert that the Hafnium
image does fit into the allocated memory. Add this as a toolchain arg
'plat_max_image_size' and add a new assertion into check_elf.py. This
way the build will fail if the image is too big.

Change-Id: Ib7311c49b185685aef167988c9aa2416822a6f28
diff --git a/build/image/check_elf.py b/build/image/check_elf.py
index 1dc0cec..a949e72 100644
--- a/build/image/check_elf.py
+++ b/build/image/check_elf.py
@@ -30,12 +30,19 @@
 HF_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
 CLANG_ROOT = os.path.join(HF_ROOT, "prebuilts", "linux-x64", "clang")
 OBJDUMP = os.path.join(CLANG_ROOT, "bin", "llvm-objdump")
+NM = os.path.join(CLANG_ROOT, "bin", "llvm-nm")
 
-def check_eret_speculation_barrier(objdump_stdout):
+def check_eret_speculation_barrier(args):
 	"""
 	Some ARM64 CPUs speculatively execute instructions after ERET.
 	Check that every ERET is followed by DSB NSH and ISB.
 	"""
+
+	objdump_stdout = subprocess\
+		.check_output([ OBJDUMP, "-d", args.input_elf ])\
+		.decode("utf-8")\
+		.splitlines()
+
 	found_eret = False
 
 	STATE_DEFAULT = 1
@@ -68,19 +75,59 @@
 	if not found_eret:
 		raise Exception("Could not find any ERET instructions")
 
+def check_max_image_size(args):
+	"""
+	Check that the ELF's effective image size does not exceed maximum
+	allowed image size, if specified in command-line arguments.
+	"""
+
+	if args.max_image_size <= 0:
+		return
+
+	nm_stdout = subprocess\
+		.check_output([ NM, args.input_elf ])\
+		.decode("utf-8")\
+		.splitlines()
+
+	COLUMN_COUNT = 3
+	COLUMN_IDX_VALUE = 0
+	COLUMN_IDX_TYPE = 1
+	COLUMN_IDX_NAME = 2
+
+	image_size = None
+	for line in nm_stdout:
+		line = line.split()
+		if len(line) != COLUMN_COUNT:
+			raise Exception(
+				"Unexpected number of columns in NM output")
+
+		if line[COLUMN_IDX_NAME] == "image_size":
+			if line[COLUMN_IDX_TYPE] != "A":
+				raise Exception(
+					"Unexpected type of image_size symbol")
+			image_size = int(line[COLUMN_IDX_VALUE], 16)
+			break
+
+	if image_size is None:
+		raise Exception("Could not find value of image_size symbol")
+	elif image_size > args.max_image_size:
+		raise Exception(
+			"Image size exceeds maximum allowed image size " +
+			"({}B > {}B)".format(image_size, args.max_image_size))
+
 def Main():
 	parser = argparse.ArgumentParser()
 	parser.add_argument("input_elf",
 		help="ELF file to analyze")
 	parser.add_argument("stamp_file",
 		help="file to be touched if successful")
+	parser.add_argument("--max-image-size",
+		required=False, type=int, default=0,
+		help="maximum allowed image size in bytes")
 	args = parser.parse_args()
 
-	objdump_stdout = subprocess.check_output([
-		OBJDUMP, "-d", args.input_elf ])
-	objdump_stdout = objdump_stdout.decode("utf-8").splitlines()
-
-	check_eret_speculation_barrier(objdump_stdout)
+	check_eret_speculation_barrier(args)
+	check_max_image_size(args)
 
 	# Touch `stamp_file`.
 	with open(args.stamp_file, "w"):
diff --git a/build/image/image.gni b/build/image/image.gni
index 7860784..1ff8777 100644
--- a/build/image/image.gni
+++ b/build/image/image.gni
@@ -13,6 +13,7 @@
 # limitations under the License.
 
 import("//build/toolchain/embedded.gni")
+import("//build/toolchain/platform.gni")
 
 # Build image, link to an ELF file then convert to plain binary.
 template("image_binary") {
@@ -73,6 +74,8 @@
     args = [
       rebase_path(elf_file),
       rebase_path(stamp_file),
+      "--max-image-size",
+      "$plat_max_image_size",
     ]
     outputs = [
       stamp_file,
diff --git a/build/toolchain/embedded.gni b/build/toolchain/embedded.gni
index fa4aeae..e824202 100644
--- a/build/toolchain/embedded.gni
+++ b/build/toolchain/embedded.gni
@@ -294,6 +294,9 @@
       plat_boot_flow = invoker.boot_flow
       plat_console = invoker.console
       plat_iommu = invoker.iommu
+      if (defined(invoker.max_image_size)) {
+        plat_max_image_size = invoker.max_image_size
+      }
       forward_variables_from(invoker.toolchain_args, "*")
     }
   }
@@ -321,6 +324,7 @@
                              "gicr_base_address",
                              "heap_pages",
                              "max_cpus",
+                             "max_image_size",
                              "max_vms",
                              "toolchain_args",
                            ])
diff --git a/build/toolchain/platform.gni b/build/toolchain/platform.gni
index 7ff4b5a..6f4df04 100644
--- a/build/toolchain/platform.gni
+++ b/build/toolchain/platform.gni
@@ -35,6 +35,9 @@
   # The maximum number of CPUs available on the platform.
   plat_max_cpus = 1
 
+  # The maximum allowed size of Hafnium's image in memory. Zero if don't care.
+  plat_max_image_size = 0
+
   # The maximum number of VMs required for the platform.
   plat_max_vms = 0
 }