feat(memmap): add topological memory view

Present memory usage in hierarchical view. This view maps modules to
their respective segments and sections.

Change-Id: I5c374b46738edbc83133441ff3f4268f08cb011d
Signed-off-by: Harrison Mutai <harrison.mutai@arm.com>
diff --git a/tools/memory/memory/elfparser.py b/tools/memory/memory/elfparser.py
index b61f328..1bd68b1 100644
--- a/tools/memory/memory/elfparser.py
+++ b/tools/memory/memory/elfparser.py
@@ -5,11 +5,21 @@
 #
 
 import re
+from dataclasses import asdict, dataclass
 from typing import BinaryIO
 
 from elftools.elf.elffile import ELFFile
 
 
+@dataclass(frozen=True)
+class TfaMemObject:
+    name: str
+    start: int
+    end: int
+    size: int
+    children: list
+
+
 class TfaElfParser:
     """A class representing an ELF file built for TF-A.
 
@@ -29,12 +39,74 @@
             for sym in elf.get_section_by_name(".symtab").iter_symbols()
         }
 
+        self.set_segment_section_map(elf.iter_segments(), elf.iter_sections())
         self._memory_layout = self.get_memory_layout_from_symbols()
+        self._start = elf["e_entry"]
+        self._size, self._free = self._get_mem_usage()
+        self._end = self._start + self._size
 
     @property
     def symbols(self):
         return self._symbols.items()
 
+    @staticmethod
+    def tfa_mem_obj_factory(elf_obj, name=None, children=None, segment=False):
+        """Converts a pyelfparser Segment or Section to a TfaMemObject."""
+        # Ensure each segment is provided a name since they aren't in the
+        # program header.
+        assert not (
+            segment and name is None
+        ), "Attempting to make segment without a name"
+
+        if children is None:
+            children = list()
+
+        # Segment and sections header keys have different prefixes.
+        vaddr = "p_vaddr" if segment else "sh_addr"
+        size = "p_memsz" if segment else "sh_size"
+
+        # TODO figure out how to handle free space for sections and segments
+        return TfaMemObject(
+            name if segment else elf_obj.name,
+            elf_obj[vaddr],
+            elf_obj[vaddr] + elf_obj[size],
+            elf_obj[size],
+            [] if not children else children,
+        )
+
+    def _get_mem_usage(self) -> (int, int):
+        """Get total size and free space for this component."""
+        size = free = 0
+
+        # Use information encoded in the segment header if we can't get a
+        # memory configuration.
+        if not self._memory_layout:
+            return sum(s.size for s in self._segments.values()), 0
+
+        for v in self._memory_layout.values():
+            size += v["length"]
+            free += v["start"] + v["length"] - v["end"]
+
+        return size, free
+
+    def set_segment_section_map(self, segments, sections):
+        """Set segment to section mappings."""
+        segments = list(
+            filter(lambda seg: seg["p_type"] == "PT_LOAD", segments)
+        )
+
+        for sec in sections:
+            for n, seg in enumerate(segments):
+                if seg.section_in_segment(sec):
+                    if n not in self._segments.keys():
+                        self._segments[n] = self.tfa_mem_obj_factory(
+                            seg, name=f"{n:#02}", segment=True
+                        )
+
+                    self._segments[n].children.append(
+                        self.tfa_mem_obj_factory(sec)
+                    )
+
     def get_memory_layout_from_symbols(self, expr=None) -> dict:
         """Retrieve information about the memory configuration from the symbol
         table.
@@ -55,6 +127,10 @@
 
         return memory_layout
 
+    def get_seg_map_as_dict(self):
+        """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):
         """Get the total memory consumed by this module from the memory
         configuration.
@@ -72,3 +148,14 @@
                 "total": attrs["length"],
             }
         return mem_dict
+
+    def get_mod_mem_usage_dict(self):
+        """Get the total memory consumed by the module, this combines the
+        information in the memory configuration.
+        """
+        return {
+            "start": self._start,
+            "end": self._end,
+            "size": self._size,
+            "free": self._free,
+        }