Merge "fix(memmap): reintroduce support for GNU map files" into integration
diff --git a/tools/memory/memory/buildparser.py b/tools/memory/memory/buildparser.py
index c128c36..dedff79 100755
--- a/tools/memory/memory/buildparser.py
+++ b/tools/memory/memory/buildparser.py
@@ -8,14 +8,16 @@
 from pathlib import Path
 
 from memory.elfparser import TfaElfParser
+from memory.mapparser import TfaMapParser
 
 
 class TfaBuildParser:
     """A class for performing analysis on the memory layout of a TF-A build."""
 
-    def __init__(self, path: Path):
+    def __init__(self, path: Path, map_backend=False):
         self._modules = dict()
         self._path = path
+        self.map_backend = map_backend
         self._parse_modules()
 
     def __getitem__(self, module: str):
@@ -23,15 +25,24 @@
         return self._modules[module]
 
     def _parse_modules(self):
-        """Parse ELF files in the build path."""
-        for elf_file in self._path.glob("**/*.elf"):
-            module_name = elf_file.name.split("/")[-1].split(".")[0]
-            with open(elf_file, "rb") as file:
-                self._modules[module_name] = TfaElfParser(file)
+        """Parse the build files using the selected backend."""
+        backend = TfaElfParser
+        files = list(self._path.glob("**/*.elf"))
+        io_perms = "rb"
+
+        if self.map_backend or len(files) == 0:
+            backend = TfaMapParser
+            files = self._path.glob("**/*.map")
+            io_perms = "r"
+
+        for file in files:
+            module_name = file.name.split("/")[-1].split(".")[0]
+            with open(file, io_perms) as f:
+                self._modules[module_name] = backend(f)
 
         if not len(self._modules):
             raise FileNotFoundError(
-                f"failed to find ELF files in path {self._path}!"
+                f"failed to find files to analyse in path {self._path}!"
             )
 
     @property
@@ -54,7 +65,7 @@
         """Returns map of memory usage per memory type for each module."""
         mem_map = {}
         for k, v in self._modules.items():
-            mod_mem_map = v.get_elf_memory_layout()
+            mod_mem_map = v.get_memory_layout()
             if len(mod_mem_map):
                 mem_map[k] = mod_mem_map
         return mem_map
diff --git a/tools/memory/memory/elfparser.py b/tools/memory/memory/elfparser.py
index 1bd68b1..2dd2513 100644
--- a/tools/memory/memory/elfparser.py
+++ b/tools/memory/memory/elfparser.py
@@ -131,7 +131,7 @@
         """Get a dictionary of segments and their section mappings."""
         return [asdict(v) for k, v in self._segments.items()]
 
-    def get_elf_memory_layout(self):
+    def get_memory_layout(self):
         """Get the total memory consumed by this module from the memory
         configuration.
             {"rom": {"start": 0x0, "end": 0xFF, "length": ... }
diff --git a/tools/memory/memory/mapparser.py b/tools/memory/memory/mapparser.py
new file mode 100644
index 0000000..b1a4b4c
--- /dev/null
+++ b/tools/memory/memory/mapparser.py
@@ -0,0 +1,75 @@
+#
+# Copyright (c) 2023, Arm Limited. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+from re import match, search
+from typing import TextIO
+
+
+class TfaMapParser:
+    """A class representing a map file built for TF-A.
+
+    Provides a basic interface for reading the symbol table. The constructor
+    accepts a file-like object with the contents a Map file. Only GNU map files
+    are supported at this stage.
+    """
+
+    def __init__(self, map_file: TextIO):
+        self._symbols = self.read_symbols(map_file)
+
+    @property
+    def symbols(self):
+        return self._symbols.items()
+
+    @staticmethod
+    def read_symbols(file: TextIO, pattern: str = None) -> dict:
+        pattern = r"\b(0x\w*)\s*(\w*)\s=" if not pattern else pattern
+        symbols = {}
+
+        for line in file.readlines():
+            match = search(pattern, line)
+
+            if match is not None:
+                value, name = match.groups()
+                symbols[name] = int(value, 16)
+
+        return symbols
+
+    def get_memory_layout(self) -> dict:
+        """Get the total memory consumed by this module from the memory
+        configuration.
+            {"rom": {"start": 0x0, "end": 0xFF, "length": ... }
+        """
+        assert len(self._symbols), "Symbol table is empty!"
+        expr = r".*(.?R.M)_REGION.*(START|END|LENGTH)"
+        memory_layout = {}
+
+        region_symbols = filter(lambda s: match(expr, s), self._symbols)
+
+        for symbol in region_symbols:
+            region, _, attr = tuple(symbol.lower().strip("__").split("_"))
+            if region not in memory_layout:
+                memory_layout[region] = {}
+
+            memory_layout[region][attr] = self._symbols[symbol]
+
+            if "start" and "length" and "end" in memory_layout[region]:
+                memory_layout[region]["limit"] = (
+                    memory_layout[region]["end"]
+                    + memory_layout[region]["length"]
+                )
+                memory_layout[region]["free"] = (
+                    memory_layout[region]["limit"]
+                    - memory_layout[region]["end"]
+                )
+                memory_layout[region]["total"] = memory_layout[region][
+                    "length"
+                ]
+                memory_layout[region]["size"] = (
+                    memory_layout[region]["end"]
+                    - memory_layout[region]["start"]
+                )
+
+        return memory_layout
diff --git a/tools/memory/memory/memmap.py b/tools/memory/memory/memmap.py
index 6d6f39d..dda104a 100755
--- a/tools/memory/memory/memmap.py
+++ b/tools/memory/memory/memmap.py
@@ -66,6 +66,11 @@
     default=False,
     help="Display numbers in decimal base.",
 )
+@click.option(
+    "--no-elf-images",
+    is_flag=True,
+    help="Analyse the build's map files instead of ELF images.",
+)
 def main(
     root: Path,
     platform: str,
@@ -76,11 +81,12 @@
     depth: int,
     width: int,
     d: bool,
+    no_elf_images: bool,
 ):
     build_path = root if root else Path("build/", platform, build_type)
     click.echo(f"build-path: {build_path.resolve()}")
 
-    parser = TfaBuildParser(build_path)
+    parser = TfaBuildParser(build_path, map_backend=no_elf_images)
     printer = TfaPrettyPrinter(columns=width, as_decimal=d)
 
     if footprint or not (tree or symbols):
diff --git a/tools/memory/memory/printer.py b/tools/memory/memory/printer.py
index 6bc6bff..4b18560 100755
--- a/tools/memory/memory/printer.py
+++ b/tools/memory/memory/printer.py
@@ -95,13 +95,16 @@
         self,
         symbols: list,
         modules: list,
-        start: int = 11,
+        start: int = 12,
     ):
         assert len(symbols), "Empty symbol list!"
         modules = sorted(modules)
         col_width = int((self.term_size - start) / len(modules))
+        address_fixed_width = 11
 
-        num_fmt = "0=#010x" if not self.as_decimal else ">10"
+        num_fmt = (
+            f"0=#0{address_fixed_width}x" if not self.as_decimal else ">10"
+        )
 
         _symbol_map = [
             " " * start