refactor(memmap): create common image parser interface

This is another non-functional change, which factors the memory
footprint and symbol listing interfaces shared by the two image parsing
backends into a common `Image` interface.

Change-Id: I625a8e5ca77e38155b6ed545a33a8ae4c2cf133a
Signed-off-by: Chris Kay <chris.kay@arm.com>
diff --git a/tools/memory/src/memory/buildparser.py b/tools/memory/src/memory/buildparser.py
index f811032..eaccd19 100755
--- a/tools/memory/src/memory/buildparser.py
+++ b/tools/memory/src/memory/buildparser.py
@@ -58,7 +58,7 @@
 
     @staticmethod
     def filter_symbols(
-        symbols: Dict[str, Dict[str, int]], regex: str
+        images: Dict[str, Dict[str, int]], regex: str
     ) -> Dict[str, Dict[str, int]]:
         """Returns a map of symbols to modules."""
 
@@ -68,17 +68,12 @@
                 for symbol, symbol_value in symbols.items()
                 if re.match(regex, symbol)
             }
-            for image, symbols in symbols.items()
+            for image, symbols in images.items()
         }
 
     def get_mem_usage_dict(self) -> Dict[str, Dict[str, Region]]:
         """Returns map of memory usage per memory type for each module."""
-        mem_map: Dict[str, Dict[str, Region]] = {}
-        for k, v in self._modules.items():
-            mod_mem_map = v.get_memory_layout()
-            if len(mod_mem_map):
-                mem_map[k] = mod_mem_map
-        return mem_map
+        return {k: v.footprint for k, v in self._modules.items()}
 
     def get_mem_tree_as_dict(self) -> Dict[str, Dict[str, Any]]:
         """Returns _tree of modules, segments and segments and their total
diff --git a/tools/memory/src/memory/elfparser.py b/tools/memory/src/memory/elfparser.py
index e1b6cd3..33f36e3 100644
--- a/tools/memory/src/memory/elfparser.py
+++ b/tools/memory/src/memory/elfparser.py
@@ -20,7 +20,7 @@
 from elftools.elf.sections import Section
 from elftools.elf.segments import Segment
 
-from memory.image import Region
+from memory.image import Image, Region
 
 
 @dataclass(frozen=True)
@@ -32,7 +32,7 @@
     children: List["TfaMemObject"]
 
 
-class TfaElfParser:
+class TfaElfParser(Image):
     """A class representing an ELF file built for TF-A.
 
     Provides a basic interface for reading the symbol table and other
@@ -59,6 +59,15 @@
         self._size, self._free = self._get_mem_usage()
         self._end: int = self._start + self._size
 
+        self._footprint: Dict[str, Region] = {}
+
+        for mem, attrs in self._memory_layout.items():
+            self._footprint[mem] = Region(
+                attrs["start"],
+                attrs["end"],
+                attrs["length"],
+            )
+
     @property
     def symbols(self) -> Dict[str, int]:
         return self._symbols
@@ -164,6 +173,10 @@
 
         return mem_dict
 
+    @property
+    def footprint(self) -> Dict[str, Region]:
+        return self._footprint
+
     def get_mod_mem_usage_dict(self) -> Dict[str, int]:
         """Get the total memory consumed by the module, this combines the
         information in the memory configuration.
diff --git a/tools/memory/src/memory/image.py b/tools/memory/src/memory/image.py
index b5653c7..cda1d8a 100644
--- a/tools/memory/src/memory/image.py
+++ b/tools/memory/src/memory/image.py
@@ -4,8 +4,9 @@
 # SPDX-License-Identifier: BSD-3-Clause
 #
 
+from abc import ABC, abstractmethod
 from dataclasses import dataclass
-from typing import Optional
+from typing import Dict, Optional
 
 
 @dataclass
@@ -56,3 +57,21 @@
             return None
 
         return self.limit - self.end
+
+
+class Image(ABC):
+    """An image under analysis."""
+
+    @property
+    @abstractmethod
+    def footprint(self) -> Dict[str, Region]:
+        """Get metrics about the memory regions that this image occupies."""
+
+        pass
+
+    @property
+    @abstractmethod
+    def symbols(self) -> Dict[str, int]:
+        """Get a dictionary of the image's symbols and their corresponding addresses."""
+
+        pass
diff --git a/tools/memory/src/memory/mapparser.py b/tools/memory/src/memory/mapparser.py
index eed75f0..24ee264 100644
--- a/tools/memory/src/memory/mapparser.py
+++ b/tools/memory/src/memory/mapparser.py
@@ -3,14 +3,15 @@
 #
 # SPDX-License-Identifier: BSD-3-Clause
 #
+
 from collections import defaultdict
 from re import match, search
 from typing import Dict, TextIO
 
-from memory.image import Region
+from memory.image import Image, Region
 
 
-class TfaMapParser:
+class TfaMapParser(Image):
     """A class representing a map file built for TF-A.
 
     Provides a basic interface for reading the symbol table. The constructor
@@ -20,6 +21,20 @@
 
     def __init__(self, map_file: TextIO) -> None:
         self._symbols: Dict[str, int] = self.read_symbols(map_file)
+        assert self._symbols, "Symbol table is empty!"
+
+        self._footprint: Dict[str, Region] = defaultdict(Region)
+
+        expr = r".*(.?R.M)_REGION.*(START|END|LENGTH)"
+        for symbol in filter(lambda s: match(expr, s), self._symbols):
+            region, _, attr = symbol.lower().strip("__").split("_")
+
+            if attr == "start":
+                self._footprint[region].start = self._symbols[symbol]
+            elif attr == "end":
+                self._footprint[region].end = self._symbols[symbol]
+            if attr == "length":
+                self._footprint[region].length = self._symbols[symbol]
 
     @property
     def symbols(self) -> Dict[str, int]:
@@ -39,25 +54,6 @@
 
         return symbols
 
-    def get_memory_layout(self) -> Dict[str, Region]:
-        """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: Dict[str, Region] = defaultdict(Region)
-
-        region_symbols = filter(lambda s: match(expr, s), self._symbols)
-
-        for symbol in region_symbols:
-            region, _, attr = tuple(symbol.lower().strip("__").split("_"))
-
-            if attr == "start":
-                memory_layout[region].start = self._symbols[symbol]
-            elif attr == "end":
-                memory_layout[region].end = self._symbols[symbol]
-            if attr == "length":
-                memory_layout[region].length = self._symbols[symbol]
-
-        return memory_layout
+    @property
+    def footprint(self) -> Dict[str, Region]:
+        return self._footprint