fix(memmap): reintroduce support for GNU map files

The intial patch stack only supported ELF files, which proved
particularly problematic when dealing with incomplete builds (i.e. build
didn't complete due to linker errors). This adds support for GNU map
files. Most analysis performed by the tool should be possible with map
files alone.

Change-Id: I89f775a98efc5aef6671a17d0e6e973df555a6fa
Signed-off-by: Harrison Mutai <harrison.mutai@arm.com>
This commit is contained in:
Harrison Mutai 2023-06-07 11:28:16 +01:00
parent 1b4d99878c
commit d0e3053c4f
5 changed files with 107 additions and 12 deletions

View file

@ -8,14 +8,16 @@ import re
from pathlib import Path from pathlib import Path
from memory.elfparser import TfaElfParser from memory.elfparser import TfaElfParser
from memory.mapparser import TfaMapParser
class TfaBuildParser: class TfaBuildParser:
"""A class for performing analysis on the memory layout of a TF-A build.""" """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._modules = dict()
self._path = path self._path = path
self.map_backend = map_backend
self._parse_modules() self._parse_modules()
def __getitem__(self, module: str): def __getitem__(self, module: str):
@ -23,15 +25,24 @@ class TfaBuildParser:
return self._modules[module] return self._modules[module]
def _parse_modules(self): def _parse_modules(self):
"""Parse ELF files in the build path.""" """Parse the build files using the selected backend."""
for elf_file in self._path.glob("**/*.elf"): backend = TfaElfParser
module_name = elf_file.name.split("/")[-1].split(".")[0] files = list(self._path.glob("**/*.elf"))
with open(elf_file, "rb") as file: io_perms = "rb"
self._modules[module_name] = TfaElfParser(file)
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): if not len(self._modules):
raise FileNotFoundError( raise FileNotFoundError(
f"failed to find ELF files in path {self._path}!" f"failed to find files to analyse in path {self._path}!"
) )
@property @property
@ -54,7 +65,7 @@ class TfaBuildParser:
"""Returns map of memory usage per memory type for each module.""" """Returns map of memory usage per memory type for each module."""
mem_map = {} mem_map = {}
for k, v in self._modules.items(): 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): if len(mod_mem_map):
mem_map[k] = mod_mem_map mem_map[k] = mod_mem_map
return mem_map return mem_map

View file

@ -131,7 +131,7 @@ class TfaElfParser:
"""Get a dictionary of segments and their section mappings.""" """Get a dictionary of segments and their section mappings."""
return [asdict(v) for k, v in self._segments.items()] 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 """Get the total memory consumed by this module from the memory
configuration. configuration.
{"rom": {"start": 0x0, "end": 0xFF, "length": ... } {"rom": {"start": 0x0, "end": 0xFF, "length": ... }

View file

@ -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

View file

@ -66,6 +66,11 @@ from memory.printer import TfaPrettyPrinter
default=False, default=False,
help="Display numbers in decimal base.", 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( def main(
root: Path, root: Path,
platform: str, platform: str,
@ -76,11 +81,12 @@ def main(
depth: int, depth: int,
width: int, width: int,
d: bool, d: bool,
no_elf_images: bool,
): ):
build_path = root if root else Path("build/", platform, build_type) build_path = root if root else Path("build/", platform, build_type)
click.echo(f"build-path: {build_path.resolve()}") 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) printer = TfaPrettyPrinter(columns=width, as_decimal=d)
if footprint or not (tree or symbols): if footprint or not (tree or symbols):

View file

@ -95,13 +95,16 @@ class TfaPrettyPrinter:
self, self,
symbols: list, symbols: list,
modules: list, modules: list,
start: int = 11, start: int = 12,
): ):
assert len(symbols), "Empty symbol list!" assert len(symbols), "Empty symbol list!"
modules = sorted(modules) modules = sorted(modules)
col_width = int((self.term_size - start) / len(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 = [ _symbol_map = [
" " * start " " * start