mirror of https://github.com/ARMmbed/mbed-os.git
Allow memap.py to process memory bank information (#345)
* Begin modernizing memap, add parsing of start address and symbol names * Memap can now process and print memory bank info! * Rename symbols back to modules, it was correct before * Remove trailing whitespace * Add type hints to modernized functions * Apply suggestions from code review Co-authored-by: VictorWTang <33789988+VictorWTang@users.noreply.github.com> * Remove else Co-authored-by: VictorWTang <33789988+VictorWTang@users.noreply.github.com> * Clarify statement --------- Co-authored-by: Victor Tang <me@victorwtang.dev> Co-authored-by: VictorWTang <33789988+VictorWTang@users.noreply.github.com>pull/15530/head
parent
037fe9b619
commit
0a7652033c
|
@ -30,20 +30,6 @@
|
|||
aborting compilation, it is not the run time limit:
|
||||
Heap_Size + Stack_Size = 0x80 + 0x80 = 0x100
|
||||
*/
|
||||
.section .stack
|
||||
.align 3
|
||||
#ifdef __STACK_SIZE
|
||||
.equ Stack_Size, __STACK_SIZE
|
||||
#else
|
||||
.equ Stack_Size, 0xc00
|
||||
#endif
|
||||
.globl __StackTop
|
||||
.globl __StackLimit
|
||||
__StackLimit:
|
||||
.space Stack_Size
|
||||
.size __StackLimit, . - __StackLimit
|
||||
__StackTop:
|
||||
.size __StackTop, . - __StackTop
|
||||
|
||||
.section .heap
|
||||
.align 3
|
||||
|
|
|
@ -48,6 +48,9 @@ function(mbed_generate_map_file target)
|
|||
set(MBED_MEMAP_CREATE_JSON FALSE CACHE BOOL "create report in json file")
|
||||
set(MBED_MEMAP_CREATE_HTML FALSE CACHE BOOL "create report in html file")
|
||||
|
||||
# Config process saves the JSON file here
|
||||
set(MEMORY_BANKS_JSON_PATH ${CMAKE_BINARY_DIR}/memory_banks.json)
|
||||
|
||||
# generate table for screen
|
||||
add_custom_command(
|
||||
TARGET
|
||||
|
@ -55,7 +58,7 @@ function(mbed_generate_map_file target)
|
|||
POST_BUILD
|
||||
COMMAND ${Python3_EXECUTABLE} -m memap.memap
|
||||
-t ${MBED_TOOLCHAIN} ${CMAKE_CURRENT_BINARY_DIR}/${target}${CMAKE_EXECUTABLE_SUFFIX}.map
|
||||
--depth ${MBED_MEMAP_DEPTH}
|
||||
--depth ${MBED_MEMAP_DEPTH} --memory-banks-json ${MEMORY_BANKS_JSON_PATH}
|
||||
WORKING_DIRECTORY
|
||||
${mbed-os_SOURCE_DIR}/tools/python
|
||||
)
|
||||
|
@ -71,6 +74,7 @@ function(mbed_generate_map_file target)
|
|||
--depth ${MBED_MEMAP_DEPTH}
|
||||
-e json
|
||||
-o ${CMAKE_CURRENT_BINARY_DIR}/${target}${CMAKE_EXECUTABLE_SUFFIX}.memmap.json
|
||||
--memory-banks-json ${MEMORY_BANKS_JSON_PATH}
|
||||
WORKING_DIRECTORY
|
||||
${mbed-os_SOURCE_DIR}/tools/python
|
||||
)
|
||||
|
@ -87,6 +91,7 @@ function(mbed_generate_map_file target)
|
|||
--depth ${MBED_MEMAP_DEPTH}
|
||||
-e html
|
||||
-o ${CMAKE_CURRENT_BINARY_DIR}/${target}${CMAKE_EXECUTABLE_SUFFIX}.memmap.html
|
||||
--memory-banks-json ${MEMORY_BANKS_JSON_PATH}
|
||||
WORKING_DIRECTORY
|
||||
${mbed-os_SOURCE_DIR}/tools/python
|
||||
)
|
||||
|
|
|
@ -17,9 +17,39 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
"""
|
||||
from __future__ import print_function, division, absolute_import
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import abstractmethod, ABCMeta
|
||||
"""
|
||||
memap term glossary, for code reviewers and for developers working on this script
|
||||
--------------------------------------------------------------------------------------------
|
||||
|
||||
- Module: In this script, a module refers to the code library (i.e. the .o file) where an object came from.
|
||||
- Symbol: Any entity declared in the program that has a global address. Generally this means any global
|
||||
variables and all functions. Note that symbol names have to be alphanumeric, so C++ implemented
|
||||
"mangling" to encode class and function names as valid symbol names. This means that C++ symbols will look
|
||||
like "_ZN4mbed8AnalogIn6_mutexE" to the linker. You can use the "c++filt" tool to convert those back
|
||||
into a human readable name like "mbed::AnalogIn::_mutex."
|
||||
- Section: A logical region of an elf of object file. Each section has a specific name and occupies contiguous memory.
|
||||
It's a vague term.
|
||||
- Input section: The section in the object (.o/.obj) file that a symbol comes from. It generally has a specific name,
|
||||
e.g. a function could be from the .text.my_function input section.
|
||||
- Output section: The section in the linked application file (.elf) that a symbol has been put into. The output
|
||||
section *might* match the input section, but not always! A linker script can happily put stuff from
|
||||
any input section into any output section if so desired.
|
||||
- VMA (Virtual Memory Address): The address that an output section will have when the application runs.
|
||||
Note that this name is something of a misnomer as it is inherited from desktop Linux. There is no virtual
|
||||
memory on microcontrollers!
|
||||
- LMA (Load Memory Address): The address that an output section is loaded from in flash when the program boots.
|
||||
- .bss: Output section for global variables which have zero values at boot. This region of RAM is zeroed at boot.
|
||||
- .data: Output section for global variables with nonzero default values. This region is copied, as a single block
|
||||
of data, from the LMA to the VMA at boot.
|
||||
- .text: Output section for code and constant data (e.g. the values of constant arrays). This region
|
||||
is mapped directly into flash and does not need to be copied at runtime.
|
||||
"""
|
||||
|
||||
import dataclasses
|
||||
from typing import Optional, TextIO
|
||||
from abc import abstractmethod, ABC
|
||||
from sys import stdout, exit, argv, path
|
||||
from os import sep
|
||||
from os.path import (basename, dirname, join, relpath, abspath, commonprefix,
|
||||
|
@ -47,7 +77,28 @@ from .utils import (
|
|||
) # noqa: E402
|
||||
|
||||
|
||||
class _Parser(with_metaclass(ABCMeta, object)):
|
||||
@dataclasses.dataclass
|
||||
class MemoryBankInfo:
|
||||
name: str
|
||||
"""Name of the bank, from cmsis_mcu_descriptions.json"""
|
||||
|
||||
start_addr: int
|
||||
"""Start address of memory bank"""
|
||||
|
||||
total_size: int
|
||||
"""Total size of the memory bank in bytes"""
|
||||
|
||||
used_size: int = 0
|
||||
"""Size used in the memory bank in bytes (sum of the sizes of all symbols)"""
|
||||
|
||||
def contains_addr(self, addr: int) -> bool:
|
||||
"""
|
||||
:return: True if the given address is contained inside this memory bank
|
||||
"""
|
||||
return addr >= self.start_addr and addr < self.start_addr + self.total_size
|
||||
|
||||
|
||||
class _Parser(ABC):
|
||||
"""Internal interface for parsing"""
|
||||
SECTIONS = ('.text', '.data', '.bss', '.heap', '.stack')
|
||||
MISC_FLASH_SECTIONS = ('.interrupts', '.flash_config')
|
||||
|
@ -57,19 +108,62 @@ class _Parser(with_metaclass(ABCMeta, object)):
|
|||
'.stabstr', '.ARM.exidx', '.ARM')
|
||||
|
||||
def __init__(self):
|
||||
self.modules = dict()
|
||||
self.modules: dict[str, dict[str, int]] = {}
|
||||
"""Dict of object name to {section name, size}"""
|
||||
|
||||
def module_add(self, object_name, size, section):
|
||||
""" Adds a module or section to the list
|
||||
self.memory_banks: dict[str, list[MemoryBankInfo]] = {"RAM": [], "ROM": []}
|
||||
"""Memory bank info, by type (RAM/ROM)"""
|
||||
|
||||
def _add_symbol_to_memory_banks(self, symbol_name: str, symbol_start_addr: int, size: int) -> None:
|
||||
"""
|
||||
Update the memory banks structure to add the space used by a symbol.
|
||||
"""
|
||||
|
||||
if len(self.memory_banks["RAM"]) == 0 and len(self.memory_banks["ROM"]) == 0:
|
||||
# No memory banks loaded, skip
|
||||
return
|
||||
|
||||
end_addr = symbol_start_addr + size
|
||||
for banks in self.memory_banks.values():
|
||||
for bank_info in banks:
|
||||
if bank_info.contains_addr(symbol_start_addr):
|
||||
if bank_info.contains_addr(end_addr):
|
||||
# Symbol fully inside this memory bank
|
||||
bank_info.used_size += size
|
||||
|
||||
# Uncomment to show debug info about each symbol
|
||||
# print(f"Symbol {symbol_name} uses {size} bytes in {bank_info.name}")
|
||||
|
||||
return
|
||||
print(f"Warning: Symbol {symbol_name} is only partially contained by memory bank {bank_info.name}")
|
||||
first_addr_after_bank = bank_info.start_addr + bank_info.total_size
|
||||
bank_info.used_size += first_addr_after_bank - symbol_start_addr
|
||||
|
||||
print(f"Warning: Symbol {symbol_name} (at address 0x{symbol_start_addr:x}, size {size}) is not inside a "
|
||||
f"defined memory bank for this target.")
|
||||
|
||||
def add_symbol(self, symbol_name: str, object_name: str, start_addr: int, size: int, section: str, vma_lma_offset: int) -> None:
|
||||
""" Adds information about a symbol (e.g. a function or global variable) to the data structures.
|
||||
|
||||
Positional arguments:
|
||||
object_name - name of the entry to add
|
||||
size - the size of the module being added
|
||||
section - the section the module contributes to
|
||||
symbol_name - Descriptive name of the symbol, e.g. ".text.some_function"
|
||||
object_name - name of the object file containing the symbol
|
||||
start addr - start address of symbol
|
||||
size - the size of the symbol being added
|
||||
section - Name of the output section, e.g. ".text". Can also be "unknown".
|
||||
vma_lma_offset - Offset from where the output section exists in memory to where it's loaded from. If nonzero,
|
||||
the initializer for this section will be considered too
|
||||
"""
|
||||
if not object_name or not size or not section:
|
||||
if not object_name or not size:
|
||||
return
|
||||
|
||||
# Don't count the heap output section for memory bank size tracking, because the linker scripts (almost always?)
|
||||
# configure that section to expand to fill the remaining amount of space
|
||||
if section not in {".heap"}:
|
||||
self._add_symbol_to_memory_banks(symbol_name, start_addr, size)
|
||||
if vma_lma_offset != 0:
|
||||
self._add_symbol_to_memory_banks(f"<initializer for {symbol_name}>", start_addr + vma_lma_offset, size)
|
||||
|
||||
if object_name in self.modules:
|
||||
self.modules[object_name].setdefault(section, 0)
|
||||
self.modules[object_name][section] += size
|
||||
|
@ -82,19 +176,25 @@ class _Parser(with_metaclass(ABCMeta, object)):
|
|||
contents[section] += size
|
||||
return
|
||||
|
||||
new_module = defaultdict(int)
|
||||
new_module[section] = size
|
||||
self.modules[object_name] = new_module
|
||||
new_symbol = defaultdict(int)
|
||||
new_symbol[section] = size
|
||||
self.modules[object_name] = new_symbol
|
||||
|
||||
def module_replace(self, old_object, new_object):
|
||||
""" Replaces an object name with a new one
|
||||
def load_memory_banks_info(self, memory_banks_json_file: TextIO) -> None:
|
||||
"""
|
||||
if old_object in self.modules:
|
||||
self.modules[new_object] = self.modules[old_object]
|
||||
del self.modules[old_object]
|
||||
Load the memory bank information from a memory_banks.json file
|
||||
"""
|
||||
memory_banks_json = json.load(memory_banks_json_file)
|
||||
for bank_type, banks in memory_banks_json["configured_memory_banks"].items():
|
||||
for bank_name, bank_data in banks.items():
|
||||
self.memory_banks[bank_type].append(MemoryBankInfo(
|
||||
name=bank_name,
|
||||
start_addr=bank_data["start"],
|
||||
total_size=bank_data["size"]
|
||||
))
|
||||
|
||||
@abstractmethod
|
||||
def parse_mapfile(self, mapfile):
|
||||
def parse_mapfile(self, file_desc: TextIO) -> dict[str, dict[str, int]]:
|
||||
"""Parse a given file object pointing to a map file
|
||||
|
||||
Positional arguments:
|
||||
|
@ -116,32 +216,84 @@ class _GccParser(_Parser):
|
|||
RE_TRANS_FILE = re.compile(r'^(.+\/|.+\.ltrans.o(bj)?)$')
|
||||
OBJECT_EXTENSIONS = (".o", ".obj")
|
||||
|
||||
# Parses a line beginning a new output section in the map file that has a load address
|
||||
# Groups:
|
||||
# 1 = section name, including dot
|
||||
# 2 = in-memory address, hex, no 0x
|
||||
# 3 = section size
|
||||
# 4 = load address, i.e. where is the data for this section stored in flash
|
||||
RE_OUTPUT_SECTION_WITH_LOAD_ADDRESS = re.compile(r'^(.\w+) +0x([0-9a-f]+) +0x([0-9a-f]+) +load address +0x([0-9a-f]+)')
|
||||
|
||||
# Parses a line beginning a new output section in the map file does not have a load address
|
||||
# Groups:
|
||||
# 1 = section name, including dot
|
||||
# 2 = in-memory address, hex, no 0x
|
||||
# 3 = section size
|
||||
# 4 = load address, i.e. where is the data for this section stored in flash
|
||||
RE_OUTPUT_SECTION_NO_LOAD_ADDRESS = re.compile(r'^(.\w+) +0x([0-9a-f]+) +0x([0-9a-f]+)')
|
||||
|
||||
# Gets the input section name from the line, if it exists.
|
||||
# Input section names are always indented 1 space.
|
||||
RE_INPUT_SECTION_NAME = re.compile(r'^ (\.\w+\.?\w*\.?\w*)') # Note: This allows up to 3 dots... hopefully that's enough...
|
||||
|
||||
ALL_SECTIONS = (
|
||||
_Parser.SECTIONS
|
||||
+ _Parser.OTHER_SECTIONS
|
||||
+ _Parser.MISC_FLASH_SECTIONS
|
||||
+ ('unknown', 'OUTPUT')
|
||||
+ ('unknown', )
|
||||
)
|
||||
|
||||
def check_new_section(self, line):
|
||||
""" Check whether a new section in a map file has been detected
|
||||
def check_new_output_section(self, line: str) -> tuple[str, int] | None:
|
||||
""" Check whether a new output section in a map file has been detected
|
||||
|
||||
Positional arguments:
|
||||
line - the line to check for a new section
|
||||
|
||||
return value - A section name, if a new section was found, None
|
||||
return value - Tuple of (name, vma to lma offset), if a new section was found, None
|
||||
otherwise
|
||||
The vma to lma offset is the offset to be added to a memory address to get the
|
||||
address where it's loaded from. If this is zero, the section is not loaded from flash to RAM at startup.
|
||||
"""
|
||||
line_s = line.strip()
|
||||
for i in self.ALL_SECTIONS:
|
||||
if line_s.startswith(i):
|
||||
return i
|
||||
if line.startswith('.'):
|
||||
return 'unknown'
|
||||
|
||||
match = re.match(self.RE_OUTPUT_SECTION_WITH_LOAD_ADDRESS, line)
|
||||
if match:
|
||||
section_name = match.group(1)
|
||||
memory_address = int(match.group(2), 16)
|
||||
load_address = int(match.group(4), 16)
|
||||
load_addr_offset = load_address - memory_address
|
||||
else:
|
||||
match = re.match(self.RE_OUTPUT_SECTION_NO_LOAD_ADDRESS, line)
|
||||
if not match:
|
||||
return None
|
||||
section_name = match.group(1)
|
||||
load_addr_offset = 0
|
||||
|
||||
# Ensure that this is a known section name, remove if not
|
||||
if section_name not in self.ALL_SECTIONS:
|
||||
section_name = "unknown"
|
||||
|
||||
# Strangely, GCC still generates load address info for sections that are not loaded, such as .bss.
|
||||
# For now, suppress this for all sections other than .data.
|
||||
if section_name != ".data":
|
||||
load_addr_offset = 0
|
||||
|
||||
return section_name, load_addr_offset
|
||||
|
||||
def check_input_section(self, line) -> Optional[str]:
|
||||
""" Check whether a new input section in a map file has been detected.
|
||||
|
||||
Positional arguments:
|
||||
line - the line to check for a new section
|
||||
|
||||
return value - Input section name if found, None otherwise
|
||||
"""
|
||||
match = re.match(self.RE_INPUT_SECTION_NAME, line)
|
||||
if not match:
|
||||
return None
|
||||
|
||||
def parse_object_name(self, line):
|
||||
return match.group(1)
|
||||
|
||||
def parse_object_name(self, line: str) -> str:
|
||||
""" Parse a path to object file
|
||||
|
||||
Positional arguments:
|
||||
|
@ -177,8 +329,8 @@ class _GccParser(_Parser):
|
|||
% line)
|
||||
return '[misc]'
|
||||
|
||||
def parse_section(self, line):
|
||||
""" Parse data from a section of gcc map file
|
||||
def parse_section(self, line: str) -> tuple[str, int, int]:
|
||||
""" Parse data from a section of gcc map file describing one symbol in the code.
|
||||
|
||||
examples:
|
||||
0x00004308 0x7c ./BUILD/K64F/GCC_ARM/spi_api.o
|
||||
|
@ -186,46 +338,64 @@ class _GccParser(_Parser):
|
|||
|
||||
Positional arguments:
|
||||
line - the line to parse a section from
|
||||
|
||||
Returns tuple of (name, start addr, size)
|
||||
"""
|
||||
is_fill = re.match(self.RE_FILL_SECTION, line)
|
||||
if is_fill:
|
||||
o_name = '[fill]'
|
||||
o_start_addr = int(is_fill.group(1), 16)
|
||||
o_size = int(is_fill.group(2), 16)
|
||||
return [o_name, o_size]
|
||||
return o_name, o_start_addr, o_size
|
||||
|
||||
is_section = re.match(self.RE_STD_SECTION, line)
|
||||
if is_section:
|
||||
o_start_addr = int(is_section.group(1), 16)
|
||||
o_size = int(is_section.group(2), 16)
|
||||
if o_size:
|
||||
o_name = self.parse_object_name(is_section.group(3))
|
||||
return [o_name, o_size]
|
||||
return o_name, o_start_addr, o_size
|
||||
|
||||
return ["", 0]
|
||||
return "", 0, 0
|
||||
|
||||
def parse_mapfile(self, file_desc):
|
||||
def parse_mapfile(self, file_desc: TextIO) -> dict[str, dict[str, int]]:
|
||||
""" Main logic to decode gcc map files
|
||||
|
||||
Positional arguments:
|
||||
file_desc - a stream object to parse as a gcc map file
|
||||
"""
|
||||
current_section = 'unknown'
|
||||
|
||||
# GCC can put the section/symbol info on its own line or on the same line as the size and address.
|
||||
# So since this is a line oriented parser, we have to remember the most recently seen input & output
|
||||
# section name for later.
|
||||
current_output_section = 'unknown'
|
||||
current_output_section_addr_offset = 0
|
||||
current_input_section = 'unknown'
|
||||
|
||||
with file_desc as infile:
|
||||
for line in infile:
|
||||
if line.startswith('Linker script and memory map'):
|
||||
current_section = "unknown"
|
||||
break
|
||||
|
||||
for line in infile:
|
||||
next_section = self.check_new_section(line)
|
||||
|
||||
if next_section == "OUTPUT":
|
||||
if line.startswith("OUTPUT("):
|
||||
# Done with memory map part of the map file
|
||||
break
|
||||
elif next_section:
|
||||
current_section = next_section
|
||||
|
||||
object_name, object_size = self.parse_section(line)
|
||||
self.module_add(object_name, object_size, current_section)
|
||||
next_section = self.check_new_output_section(line)
|
||||
if next_section is not None:
|
||||
current_output_section, current_output_section_addr_offset = next_section
|
||||
|
||||
next_input_section = self.check_input_section(line)
|
||||
if next_input_section is not None:
|
||||
current_input_section = next_input_section
|
||||
|
||||
symbol_name, symbol_start_addr, symbol_size = self.parse_section(line)
|
||||
|
||||
# With GCC at least, the closest we can get to a descriptive symbol name is the input section
|
||||
# name. Thanks to the -ffunction-sections and -fdata-sections options, the section names should
|
||||
# be unique for each symbol.
|
||||
self.add_symbol(current_input_section, symbol_name, symbol_start_addr, symbol_size, current_output_section, current_output_section_addr_offset)
|
||||
|
||||
common_prefix = dirname(commonprefix([
|
||||
o for o in self.modules.keys()
|
||||
|
@ -244,280 +414,6 @@ class _GccParser(_Parser):
|
|||
return new_modules
|
||||
|
||||
|
||||
class _ArmccParser(_Parser):
|
||||
RE = re.compile(
|
||||
r'^\s+0x(\w{8})\s+0x(\w{8})\s+(\w+)\s+(\w+)\s+(\d+)\s+[*]?.+\s+(.+)$')
|
||||
RE_OBJECT = re.compile(r'(.+\.(l|a|ar))\((.+\.o(bj)?)\)')
|
||||
OBJECT_EXTENSIONS = (".o", ".obj")
|
||||
|
||||
def parse_object_name(self, line):
|
||||
""" Parse object file
|
||||
|
||||
Positional arguments:
|
||||
line - the line containing the object or library
|
||||
"""
|
||||
if line.endswith(self.OBJECT_EXTENSIONS):
|
||||
return line
|
||||
|
||||
else:
|
||||
is_obj = re.match(self.RE_OBJECT, line)
|
||||
if is_obj:
|
||||
return join(
|
||||
'[lib]', basename(is_obj.group(1)), is_obj.group(3)
|
||||
)
|
||||
else:
|
||||
print(
|
||||
"Malformed input found when parsing ARMCC map: %s" % line
|
||||
)
|
||||
return '[misc]'
|
||||
|
||||
def parse_section(self, line):
|
||||
""" Parse data from an armcc map file
|
||||
|
||||
Examples of armcc map file:
|
||||
Base_Addr Size Type Attr Idx E Section Name Object
|
||||
0x00000000 0x00000400 Data RO 11222 self.RESET startup_MK64F12.o
|
||||
0x00000410 0x00000008 Code RO 49364 * !!!main c_w.l(__main.o)
|
||||
|
||||
Positional arguments:
|
||||
line - the line to parse the section data from
|
||||
""" # noqa: E501
|
||||
test_re = re.match(self.RE, line)
|
||||
|
||||
if (
|
||||
test_re
|
||||
and "ARM_LIB_HEAP" not in line
|
||||
):
|
||||
size = int(test_re.group(2), 16)
|
||||
|
||||
if test_re.group(4) == 'RO':
|
||||
section = '.text'
|
||||
else:
|
||||
if test_re.group(3) == 'Data':
|
||||
section = '.data'
|
||||
elif test_re.group(3) == 'Zero':
|
||||
section = '.bss'
|
||||
elif test_re.group(3) == 'Code':
|
||||
section = '.text'
|
||||
else:
|
||||
print(
|
||||
"Malformed input found when parsing armcc map: %s, %r"
|
||||
% (line, test_re.groups())
|
||||
)
|
||||
|
||||
return ["", 0, ""]
|
||||
|
||||
# check name of object or library
|
||||
object_name = self.parse_object_name(
|
||||
test_re.group(6))
|
||||
|
||||
return [object_name, size, section]
|
||||
|
||||
else:
|
||||
return ["", 0, ""]
|
||||
|
||||
def parse_mapfile(self, file_desc):
|
||||
""" Main logic to decode armc5 map files
|
||||
|
||||
Positional arguments:
|
||||
file_desc - a file like object to parse as an armc5 map file
|
||||
"""
|
||||
with file_desc as infile:
|
||||
# Search area to parse
|
||||
for line in infile:
|
||||
if line.startswith(' Base Addr Size'):
|
||||
break
|
||||
|
||||
# Start decoding the map file
|
||||
for line in infile:
|
||||
self.module_add(*self.parse_section(line))
|
||||
|
||||
common_prefix = dirname(commonprefix([
|
||||
o for o in self.modules.keys()
|
||||
if (
|
||||
o.endswith(self.OBJECT_EXTENSIONS)
|
||||
and o != "anon$$obj.o"
|
||||
and o != "anon$$obj.obj"
|
||||
and not o.startswith("[lib]")
|
||||
)]))
|
||||
new_modules = {}
|
||||
for name, stats in self.modules.items():
|
||||
if (
|
||||
name == "anon$$obj.o"
|
||||
or name == "anon$$obj.obj"
|
||||
or name.startswith("[lib]")
|
||||
):
|
||||
new_modules[name] = stats
|
||||
elif name.endswith(self.OBJECT_EXTENSIONS):
|
||||
new_modules[relpath(name, common_prefix)] = stats
|
||||
else:
|
||||
new_modules[name] = stats
|
||||
return new_modules
|
||||
|
||||
|
||||
class _IarParser(_Parser):
|
||||
RE = re.compile(
|
||||
r'^\s+(.+)\s+(zero|const|ro code|inited|uninit)\s'
|
||||
r'+0x([\'\w]+)\s+0x(\w+)\s+(.+)\s.+$')
|
||||
|
||||
RE_CMDLINE_FILE = re.compile(r'^#\s+(.+\.o(bj)?)')
|
||||
RE_LIBRARY = re.compile(r'^(.+\.a)\:.+$')
|
||||
RE_OBJECT_LIBRARY = re.compile(r'^\s+(.+\.o(bj)?)\s.*')
|
||||
OBJECT_EXTENSIONS = (".o", ".obj")
|
||||
|
||||
def __init__(self):
|
||||
_Parser.__init__(self)
|
||||
# Modules passed to the linker on the command line
|
||||
# this is a dict because modules are looked up by their basename
|
||||
self.cmd_modules = {}
|
||||
|
||||
def parse_object_name(self, object_name):
|
||||
""" Parse object file
|
||||
|
||||
Positional arguments:
|
||||
line - the line containing the object or library
|
||||
"""
|
||||
if object_name.endswith(self.OBJECT_EXTENSIONS):
|
||||
try:
|
||||
return self.cmd_modules[object_name]
|
||||
except KeyError:
|
||||
return object_name
|
||||
else:
|
||||
return '[misc]'
|
||||
|
||||
def parse_section(self, line):
|
||||
""" Parse data from an IAR map file
|
||||
|
||||
Examples of IAR map file:
|
||||
Section Kind Address Size Object
|
||||
.intvec ro code 0x00000000 0x198 startup_MK64F12.o [15]
|
||||
.rodata const 0x00000198 0x0 zero_init3.o [133]
|
||||
.iar.init_table const 0x00008384 0x2c - Linker created -
|
||||
Initializer bytes const 0x00000198 0xb2 <for P3 s0>
|
||||
.data inited 0x20000000 0xd4 driverAtmelRFInterface.o [70]
|
||||
.bss zero 0x20000598 0x318 RTX_Conf_CM.o [4]
|
||||
.iar.dynexit uninit 0x20001448 0x204 <Block tail>
|
||||
HEAP uninit 0x20001650 0x10000 <Block tail>
|
||||
|
||||
Positional_arguments:
|
||||
line - the line to parse section data from
|
||||
""" # noqa: E501
|
||||
test_re = re.match(self.RE, line)
|
||||
if test_re:
|
||||
if (
|
||||
test_re.group(2) == 'const' or
|
||||
test_re.group(2) == 'ro code'
|
||||
):
|
||||
section = '.text'
|
||||
elif (test_re.group(2) == 'zero' or
|
||||
test_re.group(2) == 'uninit'):
|
||||
if test_re.group(1)[0:4] == 'HEAP':
|
||||
section = '.heap'
|
||||
elif test_re.group(1)[0:6] == 'CSTACK':
|
||||
section = '.stack'
|
||||
else:
|
||||
section = '.bss' # default section
|
||||
|
||||
elif test_re.group(2) == 'inited':
|
||||
section = '.data'
|
||||
else:
|
||||
print("Malformed input found when parsing IAR map: %s" % line)
|
||||
return ["", 0, ""]
|
||||
|
||||
# lookup object in dictionary and return module name
|
||||
object_name = self.parse_object_name(test_re.group(5))
|
||||
|
||||
size = int(test_re.group(4), 16)
|
||||
return [object_name, size, section]
|
||||
|
||||
else:
|
||||
return ["", 0, ""]
|
||||
|
||||
def check_new_library(self, line):
|
||||
"""
|
||||
Searches for libraries and returns name. Example:
|
||||
m7M_tls.a: [43]
|
||||
|
||||
"""
|
||||
test_address_line = re.match(self.RE_LIBRARY, line)
|
||||
if test_address_line:
|
||||
return test_address_line.group(1)
|
||||
else:
|
||||
return ""
|
||||
|
||||
def check_new_object_lib(self, line):
|
||||
"""
|
||||
Searches for objects within a library section and returns name.
|
||||
Example:
|
||||
rt7M_tl.a: [44]
|
||||
ABImemclr4.o 6
|
||||
ABImemcpy_unaligned.o 118
|
||||
ABImemset48.o 50
|
||||
I64DivMod.o 238
|
||||
I64DivZer.o 2
|
||||
|
||||
"""
|
||||
test_address_line = re.match(self.RE_OBJECT_LIBRARY, line)
|
||||
if test_address_line:
|
||||
return test_address_line.group(1)
|
||||
else:
|
||||
return ""
|
||||
|
||||
def parse_command_line(self, lines):
|
||||
"""Parse the files passed on the command line to the iar linker
|
||||
|
||||
Positional arguments:
|
||||
lines -- an iterator over the lines within a file
|
||||
"""
|
||||
for line in lines:
|
||||
if line.startswith("*"):
|
||||
break
|
||||
for arg in line.split(" "):
|
||||
arg = arg.rstrip(" \n")
|
||||
if (
|
||||
not arg.startswith("-")
|
||||
and arg.endswith(self.OBJECT_EXTENSIONS)
|
||||
):
|
||||
self.cmd_modules[basename(arg)] = arg
|
||||
|
||||
common_prefix = dirname(commonprefix(list(self.cmd_modules.values())))
|
||||
self.cmd_modules = {s: relpath(f, common_prefix)
|
||||
for s, f in self.cmd_modules.items()}
|
||||
|
||||
def parse_mapfile(self, file_desc):
|
||||
""" Main logic to decode IAR map files
|
||||
|
||||
Positional arguments:
|
||||
file_desc - a file like object to parse as an IAR map file
|
||||
"""
|
||||
with file_desc as infile:
|
||||
self.parse_command_line(infile)
|
||||
|
||||
for line in infile:
|
||||
if line.startswith(' Section '):
|
||||
break
|
||||
|
||||
for line in infile:
|
||||
self.module_add(*self.parse_section(line))
|
||||
|
||||
if line.startswith('*** MODULE SUMMARY'): # finish section
|
||||
break
|
||||
|
||||
current_library = ""
|
||||
for line in infile:
|
||||
library = self.check_new_library(line)
|
||||
|
||||
if library:
|
||||
current_library = library
|
||||
|
||||
object_name = self.check_new_object_lib(line)
|
||||
|
||||
if object_name and current_library:
|
||||
temp = join('[lib]', current_library, object_name)
|
||||
self.module_replace(object_name, temp)
|
||||
return self.modules
|
||||
|
||||
|
||||
class MemapParser(object):
|
||||
"""An object that represents parsed results, parses the memory map files,
|
||||
and writes out different file types of memory results
|
||||
|
@ -757,7 +653,7 @@ class MemapParser(object):
|
|||
"Total Flash memory (text + data): {}({:+}) bytes\n"
|
||||
)
|
||||
|
||||
def generate_csv(self, file_desc):
|
||||
def generate_csv(self, file_desc: TextIO) -> None:
|
||||
"""Generate a CSV file from a memoy map
|
||||
|
||||
Positional arguments:
|
||||
|
@ -830,12 +726,22 @@ class MemapParser(object):
|
|||
self.mem_summary['total_flash_delta']
|
||||
)
|
||||
|
||||
output += '\n'
|
||||
for bank_type, banks in self.memory_banks.items():
|
||||
for bank_info in banks:
|
||||
this_bank_deltas = self.memory_bank_summary[bank_type][bank_info.name]
|
||||
output += (f"{bank_type} Bank {bank_info.name}: {bank_info.used_size}({this_bank_deltas['delta_bytes_used']:+})/"
|
||||
f"{bank_info.total_size} bytes used, "
|
||||
f"{this_bank_deltas['percent_used']:.01f}% ({this_bank_deltas['delta_percent_used']:+.01f}%) used\n")
|
||||
|
||||
return output
|
||||
|
||||
toolchains = ["ARM", "ARM_STD", "ARM_MICRO", "GCC_ARM", "IAR"]
|
||||
|
||||
def compute_report(self):
|
||||
""" Generates summary of memory usage for main areas
|
||||
"""
|
||||
Generates summary of memory usage for main areas. Result is put into the 'self.mem_report'
|
||||
dict, which is processed by tests and also dumped as JSON for the JSON output format.
|
||||
"""
|
||||
self.subtotal = defaultdict(int)
|
||||
|
||||
|
@ -857,22 +763,49 @@ class MemapParser(object):
|
|||
self.subtotal['.text-delta'] + self.subtotal['.data-delta'],
|
||||
}
|
||||
|
||||
self.mem_report = []
|
||||
self.mem_report = {}
|
||||
modules = []
|
||||
if self.short_modules:
|
||||
for name, sizes in sorted(self.short_modules.items()):
|
||||
self.mem_report.append({
|
||||
modules.append({
|
||||
"module": name,
|
||||
"size": {
|
||||
k: sizes.get(k, 0) for k in (self.print_sections +
|
||||
self.delta_sections)
|
||||
}
|
||||
})
|
||||
self.mem_report["modules"] = modules
|
||||
|
||||
self.mem_report.append({
|
||||
'summary': self.mem_summary
|
||||
})
|
||||
self.mem_report["summary"] = self.mem_summary
|
||||
|
||||
def parse(self, mapfile, toolchain):
|
||||
# Calculate the delta sizes for each memory bank in a couple different formats
|
||||
self.memory_bank_summary: dict[str, dict[str, dict[str, float|int]]] = {}
|
||||
for bank_type, banks in self.memory_banks.items():
|
||||
self.memory_bank_summary[bank_type] = {}
|
||||
for bank_info in banks:
|
||||
|
||||
this_bank_info = {}
|
||||
|
||||
# Find matching memory bank in old memory banks. Compare by name as it would be possible
|
||||
# for the indices to change between builds if someone edited the memory bank definition
|
||||
old_bank_info = None
|
||||
if self.old_memory_banks is not None and bank_type in self.old_memory_banks:
|
||||
for curr_old_bank_info in self.old_memory_banks[bank_type]:
|
||||
if curr_old_bank_info.name == bank_info.name:
|
||||
old_bank_info = curr_old_bank_info
|
||||
break
|
||||
|
||||
this_bank_info["bytes_used"] = bank_info.used_size
|
||||
this_bank_info["total_size"] = bank_info.total_size
|
||||
this_bank_info["delta_bytes_used"] = 0 if old_bank_info is None else bank_info.used_size - old_bank_info.used_size
|
||||
this_bank_info["percent_used"] = 100 * bank_info.used_size/bank_info.total_size
|
||||
this_bank_info["delta_percent_used"] = 100 * this_bank_info["delta_bytes_used"]/bank_info.total_size
|
||||
|
||||
self.memory_bank_summary[bank_type][bank_info.name] = this_bank_info
|
||||
|
||||
self.mem_report["memory_bank_usage"] = self.memory_bank_summary
|
||||
|
||||
def parse(self, mapfile: str, toolchain: str, memory_banks_json_path: str | None) -> bool:
|
||||
""" Parse and decode map file depending on the toolchain
|
||||
|
||||
Positional arguments:
|
||||
|
@ -880,22 +813,28 @@ class MemapParser(object):
|
|||
toolchain - the toolchain used to create the file
|
||||
"""
|
||||
self.tc_name = toolchain.title()
|
||||
if toolchain in ("ARM", "ARM_STD", "ARM_MICRO", "ARMC6"):
|
||||
parser = _ArmccParser
|
||||
elif toolchain == "GCC_ARM":
|
||||
parser = _GccParser
|
||||
elif toolchain == "IAR":
|
||||
parser = _IarParser
|
||||
if toolchain == "GCC_ARM":
|
||||
parser_class = _GccParser
|
||||
else:
|
||||
return False
|
||||
parser = parser_class()
|
||||
old_map_parser = parser_class()
|
||||
|
||||
if memory_banks_json_path is not None:
|
||||
with open(memory_banks_json_path, 'r') as memory_banks_json_file:
|
||||
parser.load_memory_banks_info(memory_banks_json_file)
|
||||
|
||||
try:
|
||||
with open(mapfile, 'r') as file_input:
|
||||
self.modules = parser().parse_mapfile(file_input)
|
||||
self.modules = parser.parse_mapfile(file_input)
|
||||
self.memory_banks = parser.memory_banks
|
||||
try:
|
||||
with open("%s.old" % mapfile, 'r') as old_input:
|
||||
self.old_modules = parser().parse_mapfile(old_input)
|
||||
self.old_modules = old_map_parser.parse_mapfile(old_input)
|
||||
self.old_memory_banks = old_map_parser.memory_banks
|
||||
except IOError:
|
||||
self.old_modules = None
|
||||
self.old_memory_banks = None
|
||||
return True
|
||||
|
||||
except IOError as error:
|
||||
|
@ -938,6 +877,11 @@ def main():
|
|||
|
||||
parser.add_argument('-v', '--version', action='version', version=version)
|
||||
|
||||
parser.add_argument(
|
||||
'-m', '--memory-banks-json',
|
||||
type=argparse_filestring_type,
|
||||
help='Path to memory bank JSON file. If passed, memap will track the used space in each memory bank.')
|
||||
|
||||
# Parse/run command
|
||||
if len(argv) <= 1:
|
||||
parser.print_help()
|
||||
|
@ -950,7 +894,7 @@ def main():
|
|||
|
||||
# Parse and decode a map file
|
||||
if args.file and args.toolchain:
|
||||
if memap.parse(args.file, args.toolchain) is False:
|
||||
if memap.parse(args.file, args.toolchain, args.memory_banks_json) is False:
|
||||
exit(0)
|
||||
|
||||
if args.depth is None:
|
||||
|
|
|
@ -33,10 +33,6 @@ import logging
|
|||
from intelhex import IntelHex
|
||||
import io
|
||||
|
||||
try:
|
||||
unicode
|
||||
except NameError:
|
||||
unicode = str
|
||||
|
||||
def remove_if_in(lst, thing):
|
||||
if thing in lst:
|
||||
|
@ -427,7 +423,7 @@ def argparse_type(casedness, prefer_hyphen=False):
|
|||
the string, or the hyphens/underscores do not match the expected
|
||||
style of the argument.
|
||||
"""
|
||||
if not isinstance(string, unicode):
|
||||
if not isinstance(string, str):
|
||||
string = string.decode()
|
||||
if prefer_hyphen:
|
||||
newstring = casedness(string).replace("_", "-")
|
||||
|
@ -447,10 +443,10 @@ def argparse_type(casedness, prefer_hyphen=False):
|
|||
return middle
|
||||
|
||||
# short cuts for the argparse_type versions
|
||||
argparse_uppercase_type = argparse_type(unicode.upper, False)
|
||||
argparse_lowercase_type = argparse_type(unicode.lower, False)
|
||||
argparse_uppercase_hyphen_type = argparse_type(unicode.upper, True)
|
||||
argparse_lowercase_hyphen_type = argparse_type(unicode.lower, True)
|
||||
argparse_uppercase_type = argparse_type(str.upper, False)
|
||||
argparse_lowercase_type = argparse_type(str.lower, False)
|
||||
argparse_uppercase_hyphen_type = argparse_type(str.upper, True)
|
||||
argparse_lowercase_hyphen_type = argparse_type(str.lower, True)
|
||||
|
||||
def argparse_force_type(case):
|
||||
""" validate that an argument passed in (as string) is a member of the list
|
||||
|
@ -458,11 +454,11 @@ def argparse_force_type(case):
|
|||
"""
|
||||
def middle(lst, type_name):
|
||||
""" The parser type generator"""
|
||||
if not isinstance(lst[0], unicode):
|
||||
if not isinstance(lst[0], str):
|
||||
lst = [o.decode() for o in lst]
|
||||
def parse_type(string):
|
||||
""" The parser type"""
|
||||
if not isinstance(string, unicode):
|
||||
if not isinstance(string, str):
|
||||
string = string.decode()
|
||||
for option in lst:
|
||||
if case(string) == case(option):
|
||||
|
@ -474,8 +470,8 @@ def argparse_force_type(case):
|
|||
return middle
|
||||
|
||||
# these two types convert the case of their arguments _before_ validation
|
||||
argparse_force_uppercase_type = argparse_force_type(unicode.upper)
|
||||
argparse_force_lowercase_type = argparse_force_type(unicode.lower)
|
||||
argparse_force_uppercase_type = argparse_force_type(str.upper)
|
||||
argparse_force_lowercase_type = argparse_force_type(str.lower)
|
||||
|
||||
def argparse_many(func):
|
||||
""" An argument parser combinator that takes in an argument parser and
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
Component: ARM Compiler 5.06 update 5 (build 528) Tool: armlink [4d35e2]
|
||||
|
||||
==============================================================================
|
||||
|
||||
Memory Map of the image
|
||||
|
||||
Image Entry point : 0x0001b0c1
|
||||
|
||||
Load Region LR_IROM1 (Base: 0x0001b000, Size: 0x0000ed04, Max: 0x00025000, ABSOLUTE, COMPRESSED[0x0000e23c])
|
||||
|
||||
Execution Region ER_IROM1 (Base: 0x0001b000, Size: 0x0000e1c4, Max: 0x00025000, ABSOLUTE)
|
||||
|
||||
Base Addr Size Type Attr Idx E Section Name Object
|
||||
|
||||
0x0001b000 0x000000c0 Data RO 7002 RESET /common/path/startup/startup.o
|
||||
0x0001b0c0 0x00000008 Code RO 8820 * !!!main /installed/libs/../lib/armlib/c_p.l(__main.o)
|
||||
0x0001b26c 0x00000098 Code RO 6076 .text /common/path/irqs/irqs.o
|
||||
0x000206a0 0x00000036 Code RO 27 i._Z9time_funcPN4mbed5TimerEi /common/path/main.o
|
||||
0x200039b4 0x00000018 Data RW 8092 .data /common/path/data/data.o
|
||||
0x20003af8 0x00000198 Zero RW 57 .bss /common/path/data/data.o
|
||||
|
||||
==============================================================================
|
||||
|
||||
Image component sizes
|
||||
|
||||
|
||||
Code (inc. data) RO Data RW Data ZI Data Debug
|
||||
|
||||
344 368 0 24 408 36188 Object Totals
|
||||
8 0 0 0 0 7596 Library Totals
|
||||
|
||||
==============================================================================
|
||||
|
||||
|
||||
Code (inc. data) RO Data RW Data ZI Data Debug
|
||||
|
||||
352 376 0 24 408 17208 Grand Totals
|
||||
352 376 0 24 408 17208 ELF Image Totals (compressed)
|
||||
352 376 0 24 0 0 ROM Totals
|
||||
|
||||
==============================================================================
|
||||
|
||||
Total RO Size (Code + RO Data) 352 ( 0.35kB)
|
||||
Total RW Size (RW Data + ZI Data) 432 ( 0.43kB)
|
||||
Total ROM Size (Code + RO Data + RW Data) 376 ( 0.37kB)
|
||||
|
||||
==============================================================================
|
|
@ -1,83 +0,0 @@
|
|||
###############################################################################
|
||||
#
|
||||
# IAR ELF Linker V7.80.1.28/LNX for ARM 18/Sep/2017 14:26:09
|
||||
# Copyright 2007-2016 IAR Systems AB.
|
||||
#
|
||||
# Output file =
|
||||
# /common/path/project.elf
|
||||
# Map file =
|
||||
# /common/path/project.map
|
||||
# Command line =
|
||||
# -f
|
||||
# /common/path/.link_files.txt
|
||||
# (-o /common/path/project.elf
|
||||
# --map=/common/path/project.map /common/path/main.o
|
||||
# /common/path/startup/startup.o /common/path/irqs/irqs.o
|
||||
# /common/path/data/data.o
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
*******************************************************************************
|
||||
*** RUNTIME MODEL ATTRIBUTES
|
||||
***
|
||||
|
||||
CppFlavor = *
|
||||
__CPP_Exceptions = Disabled
|
||||
__CPP_Language = C++
|
||||
__Heap_Handler = DLMalloc
|
||||
__SystemLibrary = DLib
|
||||
__dlib_dynamic_initialization = postponed
|
||||
__dlib_has_iterator_debugging = 0
|
||||
__dlib_jmp_buf_num_elements = 8
|
||||
|
||||
|
||||
*******************************************************************************
|
||||
*** PLACEMENT SUMMARY
|
||||
***
|
||||
|
||||
"A0": place at 0x0001b000 { ro section .intvec };
|
||||
"P1": place in [from 0x0001b0c0 to 0x0003ffff] { ro };
|
||||
"P2": place in [from 0x20002ef8 to 0x20007fff] { rw, block HEAP, block CSTACK };
|
||||
do not initialize { section .noinit };
|
||||
initialize by copy { rw };
|
||||
{ section .intvec };
|
||||
|
||||
Section Kind Address Size Object
|
||||
------- ---- ------- ---- ------
|
||||
"A0": 0xc0
|
||||
.intvec ro code 0x0001b000 0xc0 startup.o [4]
|
||||
- 0x0001b0c0 0xc0
|
||||
|
||||
"P1": 0x
|
||||
.text ro code 0x0001c753 0x36 main.o [3]
|
||||
.text ro code 0x0001cfff 0x98 irqs.o [5]
|
||||
.text ro code 0x0001c778 0x8 __main.o [67]
|
||||
|
||||
"P2", part 1 of 2: 0x18
|
||||
P2-1 0x20002ef8 0x18 <Init block>
|
||||
.data inited 0x20002fa8 0x18 data.o [6]
|
||||
|
||||
"P2", part 2 of 2: 0x198
|
||||
P2-2 0x20005388 0x198 <Init block>
|
||||
.bss zero 0x20002fa8 0x198 data.o [6]
|
||||
|
||||
*******************************************************************************
|
||||
*** INIT TABLE
|
||||
***
|
||||
|
||||
*******************************************************************************
|
||||
*** MODULE SUMMARY
|
||||
***
|
||||
|
||||
d16M_tlf.a: [67]
|
||||
__main.o 8
|
||||
------------------------------------------------
|
||||
Total: 8
|
||||
|
||||
Linker created
|
||||
---------------------------------------------------
|
||||
Grand Total:
|
||||
|
||||
*******************************************************************************
|
||||
*** ENTRY LIST
|
||||
***
|
|
@ -22,7 +22,7 @@ import json
|
|||
import pytest
|
||||
|
||||
from memap import memap
|
||||
from memap.memap import MemapParser
|
||||
from memap.memap import MemapParser, MemoryBankInfo
|
||||
from copy import deepcopy
|
||||
|
||||
"""
|
||||
|
@ -38,7 +38,7 @@ def memap_parser():
|
|||
"""
|
||||
memap_parser = MemapParser()
|
||||
|
||||
memap_parser.modules = {
|
||||
memap_parser.symbols = {
|
||||
"mbed-os/targets/TARGET/TARGET_MCUS/api/pinmap.o": {
|
||||
".text": 1,
|
||||
".data": 2,
|
||||
|
@ -136,6 +136,24 @@ def memap_parser():
|
|||
"OUTPUT":0,
|
||||
},
|
||||
}
|
||||
|
||||
memap_parser.memory_banks = {
|
||||
"RAM": [
|
||||
MemoryBankInfo(name="IRAM1", start_addr=0x20000000, total_size=32768, used_size=2000)
|
||||
],
|
||||
"ROM": [
|
||||
MemoryBankInfo(name="IROM1", start_addr=0x20000000, total_size=65536, used_size=10000)
|
||||
]
|
||||
}
|
||||
memap_parser.old_memory_banks = {
|
||||
"RAM": [
|
||||
MemoryBankInfo(name="IRAM1", start_addr=0x20000000, total_size=32768, used_size=2014)
|
||||
],
|
||||
"ROM": [
|
||||
MemoryBankInfo(name="IROM1", start_addr=0x20000000, total_size=65536, used_size=9000)
|
||||
]
|
||||
}
|
||||
|
||||
return memap_parser
|
||||
|
||||
|
||||
|
@ -217,3 +235,21 @@ def test_generate_output_csv_ci(memap_parser, tmpdir, depth, sep):
|
|||
file_name = str(tmpdir.join('output.csv').realpath())
|
||||
generate_test_helper(memap_parser, 'csv-ci', depth, sep, file_name)
|
||||
assert isfile(file_name), "Failed to create csv-ci file"
|
||||
|
||||
|
||||
def test_memory_bank_summary(memap_parser: MemapParser):
|
||||
"""
|
||||
Test that the memory bank summary has the expected information in it.
|
||||
"""
|
||||
|
||||
memap_parser.generate_output('table', 1, "/")
|
||||
|
||||
assert memap_parser.memory_bank_summary["RAM"].keys() == {"IRAM1"}
|
||||
assert memap_parser.memory_bank_summary["ROM"].keys() == {"IROM1"}
|
||||
|
||||
# Check details of the ROM bank more closely
|
||||
assert memap_parser.memory_bank_summary["ROM"]["IROM1"]["bytes_used"] == 10000
|
||||
assert memap_parser.memory_bank_summary["ROM"]["IROM1"]["total_size"] == 65536
|
||||
assert memap_parser.memory_bank_summary["ROM"]["IROM1"]["delta_bytes_used"] == 1000
|
||||
assert memap_parser.memory_bank_summary["ROM"]["IROM1"]["percent_used"] == pytest.approx(15.3, abs=0.1)
|
||||
assert memap_parser.memory_bank_summary["ROM"]["IROM1"]["delta_percent_used"] == pytest.approx(1.5, abs=0.1)
|
|
@ -20,84 +20,55 @@ import sys
|
|||
from io import open
|
||||
from os import sep
|
||||
from os.path import isfile, join, dirname
|
||||
import json
|
||||
from collections import defaultdict
|
||||
|
||||
import pytest
|
||||
|
||||
from memap.memap import MemapParser, _ArmccParser
|
||||
from memap.memap import MemapParser, _GccParser
|
||||
from copy import deepcopy
|
||||
|
||||
|
||||
PARSED_ARM_DATA = {
|
||||
"startup/startup.o": {".text": 0xc0},
|
||||
"[lib]/c_p.l/__main.o": {".text": 8},
|
||||
"irqs/irqs.o": {".text": 0x98},
|
||||
"data/data.o": {".data": 0x18, ".bss": 0x198},
|
||||
"main.o": {".text": 0x36},
|
||||
}
|
||||
|
||||
def test_parse_armcc():
|
||||
memap = MemapParser()
|
||||
memap.parse(join(dirname(__file__), "arm.map"), "ARM")
|
||||
|
||||
parsed_data_os_agnostic = dict()
|
||||
for k in PARSED_ARM_DATA:
|
||||
parsed_data_os_agnostic[k.replace('/', sep)] = PARSED_ARM_DATA[k]
|
||||
|
||||
assert memap.modules == parsed_data_os_agnostic
|
||||
|
||||
PARSED_IAR_DATA = {
|
||||
"startup/startup.o": {".text": 0xc0},
|
||||
"[lib]/d16M_tlf.a/__main.o": {".text": 8},
|
||||
"irqs/irqs.o": {".text": 0x98},
|
||||
"data/data.o": {".data": 0x18, ".bss": 0x198},
|
||||
"main.o": {".text": 0x36},
|
||||
}
|
||||
|
||||
def test_parse_iar():
|
||||
memap = MemapParser()
|
||||
memap.parse(join(dirname(__file__), "iar.map"), "IAR")
|
||||
|
||||
parsed_data_os_agnostic = dict()
|
||||
for k in PARSED_IAR_DATA:
|
||||
parsed_data_os_agnostic[k.replace('/', sep)] = PARSED_IAR_DATA[k]
|
||||
|
||||
assert memap.modules == parsed_data_os_agnostic
|
||||
|
||||
PARSED_GCC_DATA = {
|
||||
"startup/startup.o": {".text": 0xc0},
|
||||
"[lib]/d16M_tlf.a/__main.o": {".text": 8},
|
||||
"[lib]/misc/foo.o": {".text": 8},
|
||||
"irqs/irqs.o": {".text": 0x98},
|
||||
"data/data.o": {".data": 0x18, ".bss": 0x198},
|
||||
"main.o": {".text": 0x36},
|
||||
"startup/startup.o": defaultdict(int, {".text": 0xc0}),
|
||||
"[lib]/d16M_tlf.a/__main.o": defaultdict(int, {".text": 8}),
|
||||
"[lib]/misc/foo.o": defaultdict(int, {".text": 8}),
|
||||
"irqs/irqs.o": defaultdict(int, {".text": 0x98}),
|
||||
"data/data.o":defaultdict(int, {".data": 0x18, ".bss": 0x198}),
|
||||
"main.o": defaultdict(int, {".text": 0x36}),
|
||||
}
|
||||
|
||||
def test_parse_gcc():
|
||||
memap = MemapParser()
|
||||
memap.parse(join(dirname(__file__), "gcc.map"), "GCC_ARM")
|
||||
|
||||
this_script_dir = dirname(__file__)
|
||||
memap.parse(join(this_script_dir, "gcc.map"), "GCC_ARM", join(this_script_dir, "test_memory_banks.json"))
|
||||
|
||||
parsed_data_os_agnostic = dict()
|
||||
for k in PARSED_GCC_DATA:
|
||||
parsed_data_os_agnostic[k.replace('/', sep)] = PARSED_GCC_DATA[k]
|
||||
|
||||
# Sum of everything in .text and .data
|
||||
assert memap.memory_banks["ROM"][0].used_size == 0x1B6
|
||||
|
||||
# Sum of everything in .bss and .data
|
||||
assert memap.memory_banks["RAM"][0].used_size == 0x1B0
|
||||
|
||||
assert memap.modules == parsed_data_os_agnostic
|
||||
|
||||
|
||||
def test_add_empty_module():
|
||||
memap = _ArmccParser()
|
||||
old_modules = deepcopy(memap.modules)
|
||||
memap.module_add("", 8, ".data")
|
||||
assert(old_modules == memap.modules)
|
||||
memap.module_add("main.o", 0, ".text")
|
||||
assert(old_modules == memap.modules)
|
||||
memap.module_add("main.o", 8, "")
|
||||
assert(old_modules == memap.modules)
|
||||
def test_add_symbol_missing_info():
|
||||
memap = _GccParser()
|
||||
old_symbols = deepcopy(memap.modules)
|
||||
memap.add_symbol(".data.some_func", "", 8, 10, ".data", 1000)
|
||||
assert(old_symbols == memap.modules)
|
||||
memap.add_symbol(".data.some_func", "foo.o", 8, 0, ".data", 1000)
|
||||
assert(old_symbols == memap.modules)
|
||||
|
||||
|
||||
def test_add_full_module():
|
||||
memap = _ArmccParser()
|
||||
memap = _GccParser()
|
||||
old_modules = deepcopy(memap.modules)
|
||||
memap.module_add("main.o", 8, ".data")
|
||||
memap.add_symbol(".data.foo", "main.o", 5, 8, ".data", 1000)
|
||||
assert(old_modules != memap.modules)
|
||||
assert("main.o" in memap.modules)
|
||||
assert(".data" in memap.modules["main.o"])
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"configured_memory_banks": {
|
||||
"ROM": {
|
||||
"IROM1": {
|
||||
"access": {
|
||||
"execute": true,
|
||||
"non_secure": false,
|
||||
"non_secure_callable": false,
|
||||
"peripheral": false,
|
||||
"read": true,
|
||||
"secure": false,
|
||||
"write": false
|
||||
},
|
||||
"default": true,
|
||||
"size": 524288,
|
||||
"start": 0,
|
||||
"startup": true
|
||||
}
|
||||
},
|
||||
"RAM": {
|
||||
"IRAM1": {
|
||||
"access": {
|
||||
"execute": false,
|
||||
"non_secure": false,
|
||||
"non_secure_callable": false,
|
||||
"peripheral": false,
|
||||
"read": true,
|
||||
"secure": false,
|
||||
"write": true
|
||||
},
|
||||
"default": true,
|
||||
"size": 32768,
|
||||
"start": 536870912,
|
||||
"startup": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue