mirror of https://github.com/ARMmbed/mbed-os.git
Merge pull request #4392 from MarceloSalazar/memap_enhancements
Enhance memap, and configure depth levelpull/4786/merge
commit
878557e267
|
@ -446,7 +446,7 @@ def build_project(src_paths, build_path, target, toolchain_name,
|
||||||
macros=None, inc_dirs=None, jobs=1, silent=False,
|
macros=None, inc_dirs=None, jobs=1, silent=False,
|
||||||
report=None, properties=None, project_id=None,
|
report=None, properties=None, project_id=None,
|
||||||
project_description=None, extra_verbose=False, config=None,
|
project_description=None, extra_verbose=False, config=None,
|
||||||
app_config=None, build_profile=None):
|
app_config=None, build_profile=None, stats_depth=None):
|
||||||
""" Build a project. A project may be a test or a user program.
|
""" Build a project. A project may be a test or a user program.
|
||||||
|
|
||||||
Positional arguments:
|
Positional arguments:
|
||||||
|
@ -475,6 +475,7 @@ def build_project(src_paths, build_path, target, toolchain_name,
|
||||||
config - a Config object to use instead of creating one
|
config - a Config object to use instead of creating one
|
||||||
app_config - location of a chosen mbed_app.json file
|
app_config - location of a chosen mbed_app.json file
|
||||||
build_profile - a dict of flags that will be passed to the compiler
|
build_profile - a dict of flags that will be passed to the compiler
|
||||||
|
stats_depth - depth level for memap to display file/dirs
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Convert src_path to a list if needed
|
# Convert src_path to a list if needed
|
||||||
|
@ -553,18 +554,18 @@ def build_project(src_paths, build_path, target, toolchain_name,
|
||||||
memap_table = ''
|
memap_table = ''
|
||||||
if memap_instance:
|
if memap_instance:
|
||||||
# Write output to stdout in text (pretty table) format
|
# Write output to stdout in text (pretty table) format
|
||||||
memap_table = memap_instance.generate_output('table')
|
memap_table = memap_instance.generate_output('table', stats_depth)
|
||||||
|
|
||||||
if not silent:
|
if not silent:
|
||||||
print memap_table
|
print memap_table
|
||||||
|
|
||||||
# Write output to file in JSON format
|
# Write output to file in JSON format
|
||||||
map_out = join(build_path, name + "_map.json")
|
map_out = join(build_path, name + "_map.json")
|
||||||
memap_instance.generate_output('json', map_out)
|
memap_instance.generate_output('json', stats_depth, map_out)
|
||||||
|
|
||||||
# Write output to file in CSV format for the CI
|
# Write output to file in CSV format for the CI
|
||||||
map_csv = join(build_path, name + "_map.csv")
|
map_csv = join(build_path, name + "_map.csv")
|
||||||
memap_instance.generate_output('csv-ci', map_csv)
|
memap_instance.generate_output('csv-ci', stats_depth, map_csv)
|
||||||
|
|
||||||
resources.detect_duplicates(toolchain)
|
resources.detect_duplicates(toolchain)
|
||||||
|
|
||||||
|
@ -573,7 +574,7 @@ def build_project(src_paths, build_path, target, toolchain_name,
|
||||||
cur_result["elapsed_time"] = end - start
|
cur_result["elapsed_time"] = end - start
|
||||||
cur_result["output"] = toolchain.get_output() + memap_table
|
cur_result["output"] = toolchain.get_output() + memap_table
|
||||||
cur_result["result"] = "OK"
|
cur_result["result"] = "OK"
|
||||||
cur_result["memory_usage"] = toolchain.map_outputs
|
cur_result["memory_usage"] = memap_instance.mem_report
|
||||||
cur_result["bin"] = res
|
cur_result["bin"] = res
|
||||||
cur_result["elf"] = splitext(res)[0] + ".elf"
|
cur_result["elf"] = splitext(res)[0] + ".elf"
|
||||||
cur_result.update(toolchain.report)
|
cur_result.update(toolchain.report)
|
||||||
|
@ -1323,7 +1324,7 @@ def print_build_memory_usage(report):
|
||||||
"""
|
"""
|
||||||
from prettytable import PrettyTable
|
from prettytable import PrettyTable
|
||||||
columns_text = ['name', 'target', 'toolchain']
|
columns_text = ['name', 'target', 'toolchain']
|
||||||
columns_int = ['static_ram', 'stack', 'heap', 'total_ram', 'total_flash']
|
columns_int = ['static_ram', 'total_flash']
|
||||||
table = PrettyTable(columns_text + columns_int)
|
table = PrettyTable(columns_text + columns_int)
|
||||||
|
|
||||||
for col in columns_text:
|
for col in columns_text:
|
||||||
|
@ -1350,10 +1351,6 @@ def print_build_memory_usage(report):
|
||||||
record['toolchain_name'],
|
record['toolchain_name'],
|
||||||
record['memory_usage'][-1]['summary'][
|
record['memory_usage'][-1]['summary'][
|
||||||
'static_ram'],
|
'static_ram'],
|
||||||
record['memory_usage'][-1]['summary']['stack'],
|
|
||||||
record['memory_usage'][-1]['summary']['heap'],
|
|
||||||
record['memory_usage'][-1]['summary'][
|
|
||||||
'total_ram'],
|
|
||||||
record['memory_usage'][-1]['summary'][
|
record['memory_usage'][-1]['summary'][
|
||||||
'total_flash'],
|
'total_flash'],
|
||||||
]
|
]
|
||||||
|
|
|
@ -58,40 +58,47 @@ if __name__ == '__main__':
|
||||||
# Parse Options
|
# Parse Options
|
||||||
parser = get_default_options_parser(add_app_config=True)
|
parser = get_default_options_parser(add_app_config=True)
|
||||||
group = parser.add_mutually_exclusive_group(required=False)
|
group = parser.add_mutually_exclusive_group(required=False)
|
||||||
group.add_argument("-p",
|
group.add_argument(
|
||||||
|
"-p",
|
||||||
type=argparse_many(test_known),
|
type=argparse_many(test_known),
|
||||||
dest="program",
|
dest="program",
|
||||||
help="The index of the desired test program: [0-%d]" % (len(TESTS)-1))
|
help="The index of the desired test program: [0-%d]" % (len(TESTS)-1))
|
||||||
|
|
||||||
group.add_argument("-n",
|
group.add_argument(
|
||||||
|
"-n",
|
||||||
type=argparse_many(test_name_known),
|
type=argparse_many(test_name_known),
|
||||||
dest="program",
|
dest="program",
|
||||||
help="The name of the desired test program")
|
help="The name of the desired test program")
|
||||||
|
|
||||||
parser.add_argument("-j", "--jobs",
|
parser.add_argument(
|
||||||
|
"-j", "--jobs",
|
||||||
type=int,
|
type=int,
|
||||||
dest="jobs",
|
dest="jobs",
|
||||||
default=0,
|
default=0,
|
||||||
help="Number of concurrent jobs. Default: 0/auto (based on host machine's number of CPUs)")
|
help="Number of concurrent jobs. Default: 0/auto (based on host machine's number of CPUs)")
|
||||||
|
|
||||||
parser.add_argument("-v", "--verbose",
|
parser.add_argument(
|
||||||
|
"-v", "--verbose",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
dest="verbose",
|
dest="verbose",
|
||||||
default=False,
|
default=False,
|
||||||
help="Verbose diagnostic output")
|
help="Verbose diagnostic output")
|
||||||
|
|
||||||
parser.add_argument("--silent",
|
parser.add_argument(
|
||||||
|
"--silent",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
dest="silent",
|
dest="silent",
|
||||||
default=False,
|
default=False,
|
||||||
help="Silent diagnostic output (no copy, compile notification)")
|
help="Silent diagnostic output (no copy, compile notification)")
|
||||||
|
|
||||||
parser.add_argument("-D",
|
parser.add_argument(
|
||||||
|
"-D",
|
||||||
action="append",
|
action="append",
|
||||||
dest="macros",
|
dest="macros",
|
||||||
help="Add a macro definition")
|
help="Add a macro definition")
|
||||||
|
|
||||||
group.add_argument("-S", "--supported-toolchains",
|
group.add_argument(
|
||||||
|
"-S", "--supported-toolchains",
|
||||||
dest="supported_toolchains",
|
dest="supported_toolchains",
|
||||||
default=False,
|
default=False,
|
||||||
const="matrix",
|
const="matrix",
|
||||||
|
@ -99,11 +106,19 @@ if __name__ == '__main__':
|
||||||
nargs="?",
|
nargs="?",
|
||||||
help="Displays supported matrix of MCUs and toolchains")
|
help="Displays supported matrix of MCUs and toolchains")
|
||||||
|
|
||||||
parser.add_argument('-f', '--filter',
|
parser.add_argument(
|
||||||
|
'-f', '--filter',
|
||||||
dest='general_filter_regex',
|
dest='general_filter_regex',
|
||||||
default=None,
|
default=None,
|
||||||
help='For some commands you can use filter to filter out results')
|
help='For some commands you can use filter to filter out results')
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--stats-depth",
|
||||||
|
type=int,
|
||||||
|
dest="stats_depth",
|
||||||
|
default=2,
|
||||||
|
help="Depth level for static memory report")
|
||||||
|
|
||||||
# Local run
|
# Local run
|
||||||
parser.add_argument("--automated", action="store_true", dest="automated",
|
parser.add_argument("--automated", action="store_true", dest="automated",
|
||||||
default=False, help="Automated test")
|
default=False, help="Automated test")
|
||||||
|
@ -277,7 +292,8 @@ if __name__ == '__main__':
|
||||||
inc_dirs=[dirname(MBED_LIBRARIES)],
|
inc_dirs=[dirname(MBED_LIBRARIES)],
|
||||||
build_profile=extract_profile(parser,
|
build_profile=extract_profile(parser,
|
||||||
options,
|
options,
|
||||||
toolchain))
|
toolchain),
|
||||||
|
stats_depth=options.stats_depth)
|
||||||
print 'Image: %s'% bin_file
|
print 'Image: %s'% bin_file
|
||||||
|
|
||||||
if options.disk:
|
if options.disk:
|
||||||
|
|
550
tools/memap.py
550
tools/memap.py
|
@ -8,13 +8,12 @@ import re
|
||||||
import csv
|
import csv
|
||||||
import json
|
import json
|
||||||
import argparse
|
import argparse
|
||||||
|
from copy import deepcopy
|
||||||
from prettytable import PrettyTable
|
from prettytable import PrettyTable
|
||||||
|
|
||||||
from utils import argparse_filestring_type, \
|
from utils import argparse_filestring_type, \
|
||||||
argparse_lowercase_hyphen_type, argparse_uppercase_type
|
argparse_lowercase_hyphen_type, argparse_uppercase_type
|
||||||
|
|
||||||
DEBUG = False
|
|
||||||
|
|
||||||
RE_ARMCC = re.compile(
|
RE_ARMCC = re.compile(
|
||||||
r'^\s+0x(\w{8})\s+0x(\w{8})\s+(\w+)\s+(\w+)\s+(\d+)\s+[*]?.+\s+(.+)$')
|
r'^\s+0x(\w{8})\s+0x(\w{8})\s+(\w+)\s+(\w+)\s+(\d+)\s+[*]?.+\s+(.+)$')
|
||||||
RE_IAR = re.compile(
|
RE_IAR = re.compile(
|
||||||
|
@ -38,22 +37,18 @@ class MemapParser(object):
|
||||||
# sections to print info (generic for all toolchains)
|
# sections to print info (generic for all toolchains)
|
||||||
sections = ('.text', '.data', '.bss', '.heap', '.stack')
|
sections = ('.text', '.data', '.bss', '.heap', '.stack')
|
||||||
|
|
||||||
def __init__(self, detailed_misc=False):
|
def __init__(self):
|
||||||
""" General initialization
|
""" General initialization
|
||||||
"""
|
"""
|
||||||
#
|
|
||||||
self.detailed_misc = detailed_misc
|
|
||||||
|
|
||||||
# list of all modules and their sections
|
# list of all modules and their sections
|
||||||
self.modules = dict()
|
self.modules = dict() # full list - doesn't change with depth
|
||||||
|
self.short_modules = dict() # short version with specific depth
|
||||||
|
|
||||||
# sections must be defined in this order to take irrelevant out
|
# sections must be defined in this order to take irrelevant out
|
||||||
self.all_sections = self.sections + self.other_sections + \
|
self.all_sections = self.sections + self.other_sections + \
|
||||||
self.misc_flash_sections + ('unknown', 'OUTPUT')
|
self.misc_flash_sections + ('unknown', 'OUTPUT')
|
||||||
|
|
||||||
# list of all object files and mappting to module names
|
|
||||||
self.object_to_module = dict()
|
|
||||||
|
|
||||||
# Memory report (sections + summary)
|
# Memory report (sections + summary)
|
||||||
self.mem_report = []
|
self.mem_report = []
|
||||||
|
|
||||||
|
@ -62,23 +57,68 @@ class MemapParser(object):
|
||||||
|
|
||||||
self.subtotal = dict()
|
self.subtotal = dict()
|
||||||
|
|
||||||
def module_add(self, module_name, size, section):
|
self.misc_flash_mem = 0
|
||||||
|
|
||||||
|
|
||||||
|
def remove_unused_modules(self):
|
||||||
|
""" Removes modules/objects that were compiled but are not used
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Using keys to be able to remove entry
|
||||||
|
for i in self.modules.keys():
|
||||||
|
size = 0
|
||||||
|
for k in self.print_sections:
|
||||||
|
size += self.modules[i][k]
|
||||||
|
if size == 0:
|
||||||
|
del self.modules[i]
|
||||||
|
|
||||||
|
def module_init(self, object_name):
|
||||||
|
""" Initialize a module. Just adds the name of the module
|
||||||
|
|
||||||
|
Positional arguments:
|
||||||
|
object_name - name of the entry to add
|
||||||
|
"""
|
||||||
|
|
||||||
|
if object_name not in self.modules:
|
||||||
|
temp_dic = dict()
|
||||||
|
for section_idx in self.all_sections:
|
||||||
|
temp_dic[section_idx] = 0
|
||||||
|
self.modules[object_name] = temp_dic
|
||||||
|
|
||||||
|
def module_add(self, object_name, size, section):
|
||||||
""" Adds a module / section to the list
|
""" Adds a module / section to the list
|
||||||
|
|
||||||
Positional arguments:
|
Positional arguments:
|
||||||
module_name - name of the module to add
|
object_name - name of the entry to add
|
||||||
size - the size of the module being added
|
size - the size of the module being added
|
||||||
section - the section the module contributes to
|
section - the section the module contributes to
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if module_name in self.modules:
|
# Check if object is a sub-string of key
|
||||||
self.modules[module_name][section] += size
|
for module_path in self.modules:
|
||||||
else:
|
|
||||||
temp_dic = dict()
|
# this is required to differenciate: main.o vs xxxmain.o
|
||||||
|
module_split = os.path.basename(module_path)
|
||||||
|
obj_split = os.path.basename(object_name)
|
||||||
|
|
||||||
|
if module_split == obj_split:
|
||||||
|
self.modules[module_path][section] += size
|
||||||
|
return
|
||||||
|
|
||||||
|
new_module = dict()
|
||||||
for section_idx in self.all_sections:
|
for section_idx in self.all_sections:
|
||||||
temp_dic[section_idx] = 0
|
new_module[section_idx] = 0
|
||||||
temp_dic[section] = size
|
new_module[section] = size
|
||||||
self.modules[module_name] = temp_dic
|
self.modules[object_name] = new_module
|
||||||
|
|
||||||
|
def module_replace(self, old_object, new_object):
|
||||||
|
""" Replaces an object name with a new one
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Check if object is a sub-string of key
|
||||||
|
if old_object in self.modules:
|
||||||
|
self.modules[new_object] = self.modules[old_object]
|
||||||
|
del self.modules[old_object]
|
||||||
|
|
||||||
def check_new_section_gcc(self, line):
|
def check_new_section_gcc(self, line):
|
||||||
""" Check whether a new section in a map file has been detected (only
|
""" Check whether a new section in a map file has been detected (only
|
||||||
|
@ -99,43 +139,41 @@ class MemapParser(object):
|
||||||
return False # everything else, means no change in section
|
return False # everything else, means no change in section
|
||||||
|
|
||||||
|
|
||||||
def path_object_to_module_name(self, txt):
|
def parse_object_name_gcc(self, line):
|
||||||
""" Parse a path to object file to extract it's module and object data
|
""" Parse a path to object file
|
||||||
|
|
||||||
Positional arguments:
|
Positional arguments:
|
||||||
txt - the path to parse the object and module name from
|
txt - the path to parse the object and module name from
|
||||||
"""
|
"""
|
||||||
|
|
||||||
txt = txt.replace('\\', '/')
|
line = line.replace('\\', '/')
|
||||||
rex_mbed_os_name = r'^.+mbed-os\/(.+)\/(.+\.o)$'
|
RE_OBJECT_FILE = r'^.+\/(.+\.o)$'
|
||||||
test_rex_mbed_os_name = re.match(rex_mbed_os_name, txt)
|
test_re_mbed_os_name = re.match(RE_OBJECT_FILE, line)
|
||||||
|
|
||||||
if test_rex_mbed_os_name:
|
if test_re_mbed_os_name:
|
||||||
|
|
||||||
object_name = test_rex_mbed_os_name.group(2)
|
object_name = test_re_mbed_os_name.group(1)
|
||||||
data = test_rex_mbed_os_name.group(1).split('/')
|
|
||||||
ndata = len(data)
|
# corner case: certain objects are provided by the GCC toolchain
|
||||||
|
if 'arm-none-eabi' in line:
|
||||||
|
object_name = '[lib]/misc/' + object_name
|
||||||
|
|
||||||
|
return object_name
|
||||||
|
|
||||||
if ndata == 1:
|
|
||||||
module_name = data[0]
|
|
||||||
else:
|
else:
|
||||||
module_name = data[0] + '/' + data[1]
|
|
||||||
|
|
||||||
if self.detailed_misc:
|
RE_LIBRARY_OBJECT_FILE = r'^.+\/(lib.+\.a)\((.+\.o)\)$'
|
||||||
return [module_name + '/' + object_name, object_name]
|
test_re_obj_name = re.match(RE_LIBRARY_OBJECT_FILE, line)
|
||||||
|
|
||||||
|
if test_re_obj_name:
|
||||||
|
object_name = test_re_obj_name.group(1) + '/' + \
|
||||||
|
test_re_obj_name.group(2)
|
||||||
|
|
||||||
|
return '[lib]/' + object_name
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return [module_name, object_name]
|
print "Malformed input found when parsing GCC map: %s" % line
|
||||||
|
return '[misc]'
|
||||||
elif self.detailed_misc:
|
|
||||||
rex_obj_name = r'^.+\/(.+\.o\)*)$'
|
|
||||||
test_rex_obj_name = re.match(rex_obj_name, txt)
|
|
||||||
if test_rex_obj_name:
|
|
||||||
object_name = test_rex_obj_name.group(1)
|
|
||||||
return ['Misc/' + object_name, ""]
|
|
||||||
|
|
||||||
return ['Misc', ""]
|
|
||||||
else:
|
|
||||||
return ['Misc', ""]
|
|
||||||
|
|
||||||
def parse_section_gcc(self, line):
|
def parse_section_gcc(self, line):
|
||||||
""" Parse data from a section of gcc map file
|
""" Parse data from a section of gcc map file
|
||||||
|
@ -147,37 +185,42 @@ class MemapParser(object):
|
||||||
Positional arguments:
|
Positional arguments:
|
||||||
line - the line to parse a section from
|
line - the line to parse a section from
|
||||||
"""
|
"""
|
||||||
rex_address_len_name = re.compile(
|
|
||||||
|
RE_STD_SECTION_GCC = re.compile(
|
||||||
r'^\s+.*0x(\w{8,16})\s+0x(\w+)\s(.+)$')
|
r'^\s+.*0x(\w{8,16})\s+0x(\w+)\s(.+)$')
|
||||||
|
|
||||||
test_address_len_name = re.match(rex_address_len_name, line)
|
test_address_len_name = re.match(RE_STD_SECTION_GCC, line)
|
||||||
|
|
||||||
if test_address_len_name:
|
if test_address_len_name:
|
||||||
|
|
||||||
if int(test_address_len_name.group(2), 16) == 0: # size == 0
|
if int(test_address_len_name.group(2), 16) == 0: # size == 0
|
||||||
return ["", 0] # no valid entry
|
return ["", 0] # no valid entry
|
||||||
else:
|
else:
|
||||||
m_name, _ = self.path_object_to_module_name(
|
o_name = self.parse_object_name_gcc(\
|
||||||
test_address_len_name.group(3))
|
test_address_len_name.group(3))
|
||||||
m_size = int(test_address_len_name.group(2), 16)
|
o_size = int(test_address_len_name.group(2), 16)
|
||||||
return [m_name, m_size]
|
|
||||||
|
return [o_name, o_size]
|
||||||
|
|
||||||
else: # special corner case for *fill* sections
|
else: # special corner case for *fill* sections
|
||||||
# example
|
# example
|
||||||
# *fill* 0x0000abe4 0x4
|
# *fill* 0x0000abe4 0x4
|
||||||
rex_address_len = r'^\s+\*fill\*\s+0x(\w{8,16})\s+0x(\w+).*$'
|
|
||||||
test_address_len = re.match(rex_address_len, line)
|
RE_FILL_SECTION_GCC = r'^\s+\*fill\*\s+0x(\w{8,16})\s+0x(\w+).*$'
|
||||||
|
|
||||||
|
test_address_len = re.match(RE_FILL_SECTION_GCC, line)
|
||||||
|
|
||||||
if test_address_len:
|
if test_address_len:
|
||||||
if int(test_address_len.group(2), 16) == 0: # size == 0
|
if int(test_address_len.group(2), 16) == 0: # size == 0
|
||||||
return ["", 0] # no valid entry
|
return ["", 0] # no valid entry
|
||||||
else:
|
else:
|
||||||
m_name = 'Fill'
|
o_name = '[fill]'
|
||||||
m_size = int(test_address_len.group(2), 16)
|
o_size = int(test_address_len.group(2), 16)
|
||||||
return [m_name, m_size]
|
return [o_name, o_size]
|
||||||
else:
|
else:
|
||||||
return ["", 0] # no valid entry
|
return ["", 0] # no valid entry
|
||||||
|
|
||||||
|
|
||||||
def parse_map_file_gcc(self, file_desc):
|
def parse_map_file_gcc(self, file_desc):
|
||||||
""" Main logic to decode gcc map files
|
""" Main logic to decode gcc map files
|
||||||
|
|
||||||
|
@ -205,18 +248,40 @@ class MemapParser(object):
|
||||||
elif change_section != False:
|
elif change_section != False:
|
||||||
current_section = change_section
|
current_section = change_section
|
||||||
|
|
||||||
[module_name, module_size] = self.parse_section_gcc(line)
|
[object_name, object_size] = self.parse_section_gcc(line)
|
||||||
|
|
||||||
if module_size == 0 or module_name == "":
|
if object_size == 0 or object_name == "":
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
self.module_add(module_name, module_size, current_section)
|
self.module_add(object_name, object_size,\
|
||||||
|
current_section)
|
||||||
|
|
||||||
|
def parse_object_name_armcc(self, line):
|
||||||
|
""" Parse object file
|
||||||
|
|
||||||
|
Positional arguments:
|
||||||
|
line - the line containing the object or library
|
||||||
|
"""
|
||||||
|
|
||||||
|
# simple object (not library)
|
||||||
|
if line[-2] == '.' and line[-1] == 'o':
|
||||||
|
return line
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
RE_OBJECT_ARMCC = r'(.+\.l)\((.+\.o)\)'
|
||||||
|
test_re_obj_name = re.match(RE_OBJECT_ARMCC, line)
|
||||||
|
|
||||||
|
if test_re_obj_name:
|
||||||
|
object_name = test_re_obj_name.group(1) + '/' + \
|
||||||
|
test_re_obj_name.group(2)
|
||||||
|
|
||||||
|
return '[lib]/' + object_name
|
||||||
|
else:
|
||||||
|
print "Malformed input found when parsing ARMCC map: %s" % line
|
||||||
|
return '[misc]'
|
||||||
|
|
||||||
|
|
||||||
if DEBUG:
|
|
||||||
print "Line: %s" % line,
|
|
||||||
print "Module: %s\tSection: %s\tSize: %s" % \
|
|
||||||
(module_name, current_section, module_size)
|
|
||||||
raw_input("----------")
|
|
||||||
|
|
||||||
def parse_section_armcc(self, line):
|
def parse_section_armcc(self, line):
|
||||||
""" Parse data from an armcc map file
|
""" Parse data from an armcc map file
|
||||||
|
@ -230,34 +295,47 @@ class MemapParser(object):
|
||||||
line - the line to parse the section data from
|
line - the line to parse the section data from
|
||||||
"""
|
"""
|
||||||
|
|
||||||
test_rex_armcc = re.match(RE_ARMCC, line)
|
test_re_armcc = re.match(RE_ARMCC, line)
|
||||||
|
|
||||||
if test_rex_armcc:
|
if test_re_armcc:
|
||||||
|
|
||||||
size = int(test_rex_armcc.group(2), 16)
|
size = int(test_re_armcc.group(2), 16)
|
||||||
|
|
||||||
if test_rex_armcc.group(4) == 'RO':
|
if test_re_armcc.group(4) == 'RO':
|
||||||
section = '.text'
|
section = '.text'
|
||||||
else:
|
else:
|
||||||
if test_rex_armcc.group(3) == 'Data':
|
if test_re_armcc.group(3) == 'Data':
|
||||||
section = '.data'
|
section = '.data'
|
||||||
elif test_rex_armcc.group(3) == 'Zero':
|
elif test_re_armcc.group(3) == 'Zero':
|
||||||
section = '.bss'
|
section = '.bss'
|
||||||
else:
|
else:
|
||||||
print "BUG armcc map parser"
|
print "Malformed input found when parsing armcc map: %s" %\
|
||||||
raw_input()
|
line
|
||||||
|
|
||||||
# lookup object in dictionary and return module name
|
# check name of object or library
|
||||||
object_name = test_rex_armcc.group(6)
|
object_name = self.parse_object_name_armcc(\
|
||||||
if object_name in self.object_to_module:
|
test_re_armcc.group(6))
|
||||||
module_name = self.object_to_module[object_name]
|
|
||||||
else:
|
|
||||||
module_name = 'Misc'
|
|
||||||
|
|
||||||
return [module_name, size, section]
|
return [object_name, size, section]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return ["", 0, ""] # no valid entry
|
return ["", 0, ""]
|
||||||
|
|
||||||
|
def parse_object_name_iar(self, line):
|
||||||
|
""" Parse object file
|
||||||
|
|
||||||
|
Positional arguments:
|
||||||
|
line - the line containing the object or library
|
||||||
|
"""
|
||||||
|
|
||||||
|
# simple object (not library)
|
||||||
|
if line[-2] == '.' and line[-1] == 'o':
|
||||||
|
object_name = line
|
||||||
|
return object_name
|
||||||
|
|
||||||
|
else:
|
||||||
|
return '[misc]'
|
||||||
|
|
||||||
|
|
||||||
def parse_section_iar(self, line):
|
def parse_section_iar(self, line):
|
||||||
""" Parse data from an IAR map file
|
""" Parse data from an IAR map file
|
||||||
|
@ -277,38 +355,34 @@ class MemapParser(object):
|
||||||
line - the line to parse section data from
|
line - the line to parse section data from
|
||||||
"""
|
"""
|
||||||
|
|
||||||
test_rex_iar = re.match(RE_IAR, line)
|
test_re_iar = re.match(RE_IAR, line)
|
||||||
|
|
||||||
if test_rex_iar:
|
if test_re_iar:
|
||||||
|
|
||||||
size = int(test_rex_iar.group(4), 16)
|
size = int(test_re_iar.group(4), 16)
|
||||||
|
|
||||||
if test_rex_iar.group(2) == 'const' or \
|
if test_re_iar.group(2) == 'const' or \
|
||||||
test_rex_iar.group(2) == 'ro code':
|
test_re_iar.group(2) == 'ro code':
|
||||||
section = '.text'
|
section = '.text'
|
||||||
elif test_rex_iar.group(2) == 'zero' or \
|
elif test_re_iar.group(2) == 'zero' or \
|
||||||
test_rex_iar.group(2) == 'uninit':
|
test_re_iar.group(2) == 'uninit':
|
||||||
if test_rex_iar.group(1)[0:4] == 'HEAP':
|
if test_re_iar.group(1)[0:4] == 'HEAP':
|
||||||
section = '.heap'
|
section = '.heap'
|
||||||
elif test_rex_iar.group(1)[0:6] == 'CSTACK':
|
elif test_re_iar.group(1)[0:6] == 'CSTACK':
|
||||||
section = '.stack'
|
section = '.stack'
|
||||||
else:
|
else:
|
||||||
section = '.bss' # default section
|
section = '.bss' # default section
|
||||||
|
|
||||||
elif test_rex_iar.group(2) == 'inited':
|
elif test_re_iar.group(2) == 'inited':
|
||||||
section = '.data'
|
section = '.data'
|
||||||
else:
|
else:
|
||||||
print "BUG IAR map parser"
|
print "Malformed input found when parsing IAR map: %s" % line
|
||||||
raw_input()
|
|
||||||
|
|
||||||
# lookup object in dictionary and return module name
|
# lookup object in dictionary and return module name
|
||||||
object_name = test_rex_iar.group(5)
|
temp = test_re_iar.group(5)
|
||||||
if object_name in self.object_to_module:
|
object_name = self.parse_object_name_iar(temp)
|
||||||
module_name = self.object_to_module[object_name]
|
|
||||||
else:
|
|
||||||
module_name = 'Misc'
|
|
||||||
|
|
||||||
return [module_name, size, section]
|
return [object_name, size, section]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return ["", 0, ""] # no valid entry
|
return ["", 0, ""] # no valid entry
|
||||||
|
@ -330,12 +404,51 @@ class MemapParser(object):
|
||||||
# Start decoding the map file
|
# Start decoding the map file
|
||||||
for line in infile:
|
for line in infile:
|
||||||
|
|
||||||
[name, size, section] = self.parse_section_armcc(line)
|
[object_name, object_size, section] = \
|
||||||
|
self.parse_section_armcc(line)
|
||||||
|
|
||||||
if size == 0 or name == "" or section == "":
|
if object_size == 0 or object_name == "" or section == "":
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
self.module_add(name, size, section)
|
self.module_add(object_name, object_size, section)
|
||||||
|
|
||||||
|
|
||||||
|
def check_new_library_iar(self, line):
|
||||||
|
"""
|
||||||
|
Searches for libraries and returns name. Example:
|
||||||
|
m7M_tls.a: [43]
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
RE_LIBRARY_IAR = re.compile(r'^(.+\.a)\:.+$')
|
||||||
|
|
||||||
|
test_address_line = re.match(RE_LIBRARY_IAR, line)
|
||||||
|
|
||||||
|
if test_address_line:
|
||||||
|
return test_address_line.group(1)
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def check_new_object_lib_iar(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
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
RE_OBJECT_LIBRARY_IAR = re.compile(r'^\s+(.+\.o)\s.*')
|
||||||
|
|
||||||
|
test_address_line = re.match(RE_OBJECT_LIBRARY_IAR, line)
|
||||||
|
|
||||||
|
if test_address_line:
|
||||||
|
return test_address_line.group(1)
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
def parse_map_file_iar(self, file_desc):
|
def parse_map_file_iar(self, file_desc):
|
||||||
""" Main logic to decode IAR map files
|
""" Main logic to decode IAR map files
|
||||||
|
@ -344,8 +457,8 @@ class MemapParser(object):
|
||||||
file_desc - a file like object to parse as an IAR map file
|
file_desc - a file like object to parse as an IAR map file
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# first round, search for objects
|
||||||
with file_desc as infile:
|
with file_desc as infile:
|
||||||
|
|
||||||
# Search area to parse
|
# Search area to parse
|
||||||
for line in infile:
|
for line in infile:
|
||||||
if line.startswith(' Section '):
|
if line.startswith(' Section '):
|
||||||
|
@ -361,45 +474,142 @@ class MemapParser(object):
|
||||||
else:
|
else:
|
||||||
self.module_add(name, size, section)
|
self.module_add(name, size, section)
|
||||||
|
|
||||||
def search_objects(self, path):
|
if line.startswith('*** MODULE SUMMARY'): # finish section
|
||||||
""" Searches for object files and creates mapping: object --> module
|
break
|
||||||
|
|
||||||
|
# Start decoding the map file
|
||||||
|
current_library = ""
|
||||||
|
for line in infile:
|
||||||
|
|
||||||
|
library = self.check_new_library_iar(line)
|
||||||
|
|
||||||
|
if library != "":
|
||||||
|
current_library = library
|
||||||
|
|
||||||
|
object_name = self.check_new_object_lib_iar(line)
|
||||||
|
|
||||||
|
if object_name != "" and current_library != "":
|
||||||
|
temp = '[lib]' + '/'+ current_library + '/'+ object_name
|
||||||
|
self.module_replace(object_name, temp)
|
||||||
|
|
||||||
|
|
||||||
|
export_formats = ["json", "csv-ci", "table"]
|
||||||
|
|
||||||
|
def list_dir_obj(self, path):
|
||||||
|
""" Searches all objects in BUILD directory and creates list
|
||||||
|
|
||||||
Positional arguments:
|
Positional arguments:
|
||||||
path - the path to an object file
|
path - the path to a map file
|
||||||
"""
|
"""
|
||||||
|
|
||||||
path = path.replace('\\', '/')
|
path = path.replace('\\', '/')
|
||||||
|
|
||||||
# check location of map file
|
# check location of map file
|
||||||
rex = r'^(.+)' + r'\/(.+\.map)$'
|
RE_PATH_MAP_FILE = r'^(.+)\/.+\.map$'
|
||||||
test_rex = re.match(rex, path)
|
test_re = re.match(RE_PATH_MAP_FILE, path)
|
||||||
|
|
||||||
if test_rex:
|
if test_re:
|
||||||
search_path = test_rex.group(1) + '/mbed-os/'
|
search_path = test_re.group(1)
|
||||||
else:
|
else:
|
||||||
print "Warning: this doesn't look like an mbed project"
|
print "Warning: this doesn't look like an mbed project"
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# create empty disctionary
|
||||||
|
self.modules = dict()
|
||||||
|
|
||||||
|
# search for object files
|
||||||
for root, _, obj_files in os.walk(search_path):
|
for root, _, obj_files in os.walk(search_path):
|
||||||
for obj_file in obj_files:
|
for obj_file in obj_files:
|
||||||
if obj_file.endswith(".o"):
|
if obj_file.endswith(".o"):
|
||||||
module_name, object_name = self.path_object_to_module_name(
|
|
||||||
os.path.join(root, obj_file))
|
|
||||||
|
|
||||||
if object_name in self.object_to_module:
|
txt = os.path.join(root, obj_file)
|
||||||
if DEBUG:
|
|
||||||
print "WARNING: multiple usages of object file: %s"\
|
txt = txt.replace('\\', '/')
|
||||||
% object_name
|
|
||||||
print " Current: %s" % \
|
# add relative path + object to list
|
||||||
self.object_to_module[object_name]
|
self.module_init(txt[len(search_path)+1:])
|
||||||
print " New: %s" % module_name
|
|
||||||
print " "
|
# The code below is a special case for TESTS.
|
||||||
else:
|
# mbed-os lives in a separate location and we need to explicitly search
|
||||||
self.object_to_module.update({object_name:module_name})
|
# their object files skiping the TESTS folder (already scanned above)
|
||||||
|
|
||||||
|
# check location of mbed-os
|
||||||
|
RE_PATH_MAP_FILE = r'^(.+)\/mbed-os\/.*TESTS\/.+\.map$'
|
||||||
|
test_re = re.match(RE_PATH_MAP_FILE, path)
|
||||||
|
|
||||||
|
if test_re == None:
|
||||||
|
return
|
||||||
|
|
||||||
|
search_path = test_re.group(1)
|
||||||
|
|
||||||
|
# search for object files
|
||||||
|
for root, _, obj_files in os.walk(search_path):
|
||||||
|
for obj_file in obj_files:
|
||||||
|
if 'TESTS' not in root and obj_file.endswith(".o"):
|
||||||
|
|
||||||
|
txt = os.path.join(root, obj_file)
|
||||||
|
txt = txt.replace('\\', '/')
|
||||||
|
|
||||||
|
# add relative path + object to list
|
||||||
|
self.module_init(txt[len(search_path)+1:])
|
||||||
|
|
||||||
|
|
||||||
|
def reduce_depth(self, depth):
|
||||||
|
"""
|
||||||
|
prints list of directories and objects. Examples:
|
||||||
|
|
||||||
|
(1) depth = 1:
|
||||||
|
main.o
|
||||||
|
mbed-os
|
||||||
|
|
||||||
|
(2) depth = 2:
|
||||||
|
main.o
|
||||||
|
mbed-os/test.o
|
||||||
|
mbed-os/drivers
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# depth 0 or None shows all entries
|
||||||
|
if depth == 0 or depth == None:
|
||||||
|
self.short_modules = deepcopy(self.modules)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.short_modules = dict()
|
||||||
|
|
||||||
|
# create reduced list
|
||||||
|
for line in self.modules:
|
||||||
|
|
||||||
|
data = line.split('/')
|
||||||
|
ndir = len(data)
|
||||||
|
|
||||||
|
temp = ''
|
||||||
|
count = 0
|
||||||
|
|
||||||
|
# iterate until the max depth level
|
||||||
|
max_level = min(depth, ndir)
|
||||||
|
|
||||||
|
# rebuild the path based on depth level
|
||||||
|
while count < max_level:
|
||||||
|
if count > 0: # ignore '/' from first entry
|
||||||
|
temp = temp + '/'
|
||||||
|
|
||||||
|
temp = temp + data[count]
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
if temp not in self.short_modules:
|
||||||
|
temp_dic = dict()
|
||||||
|
for section_idx in self.all_sections:
|
||||||
|
temp_dic[section_idx] = 0
|
||||||
|
self.short_modules[temp] = temp_dic
|
||||||
|
|
||||||
|
for section_idx in self.all_sections:
|
||||||
|
self.short_modules[temp][section_idx] += \
|
||||||
|
self.modules[line][section_idx]
|
||||||
|
|
||||||
|
|
||||||
export_formats = ["json", "csv-ci", "table"]
|
export_formats = ["json", "csv-ci", "table"]
|
||||||
|
|
||||||
def generate_output(self, export_format, file_output=None):
|
def generate_output(self, export_format, depth, file_output=None):
|
||||||
""" Generates summary of memory map data
|
""" Generates summary of memory map data
|
||||||
|
|
||||||
Positional arguments:
|
Positional arguments:
|
||||||
|
@ -407,10 +617,14 @@ class MemapParser(object):
|
||||||
|
|
||||||
Keyword arguments:
|
Keyword arguments:
|
||||||
file_desc - descriptor (either stdout or file)
|
file_desc - descriptor (either stdout or file)
|
||||||
|
depth - directory depth on report
|
||||||
|
|
||||||
Returns: generated string for the 'table' format, otherwise None
|
Returns: generated string for the 'table' format, otherwise None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
self.reduce_depth(depth)
|
||||||
|
self.compute_report()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if file_output:
|
if file_output:
|
||||||
file_desc = open(file_output, 'wb')
|
file_desc = open(file_output, 'wb')
|
||||||
|
@ -452,29 +666,14 @@ class MemapParser(object):
|
||||||
|
|
||||||
csv_module_section = []
|
csv_module_section = []
|
||||||
csv_sizes = []
|
csv_sizes = []
|
||||||
for i in sorted(self.modules):
|
for i in sorted(self.short_modules):
|
||||||
for k in self.print_sections:
|
for k in self.print_sections:
|
||||||
csv_module_section += [i+k]
|
csv_module_section += [i+k]
|
||||||
csv_sizes += [self.modules[i][k]]
|
csv_sizes += [self.short_modules[i][k]]
|
||||||
|
|
||||||
csv_module_section += ['static_ram']
|
csv_module_section += ['static_ram']
|
||||||
csv_sizes += [self.mem_summary['static_ram']]
|
csv_sizes += [self.mem_summary['static_ram']]
|
||||||
|
|
||||||
csv_module_section += ['heap']
|
|
||||||
if self.mem_summary['heap'] == 0:
|
|
||||||
csv_sizes += ['unknown']
|
|
||||||
else:
|
|
||||||
csv_sizes += [self.mem_summary['heap']]
|
|
||||||
|
|
||||||
csv_module_section += ['stack']
|
|
||||||
if self.mem_summary['stack'] == 0:
|
|
||||||
csv_sizes += ['unknown']
|
|
||||||
else:
|
|
||||||
csv_sizes += [self.mem_summary['stack']]
|
|
||||||
|
|
||||||
csv_module_section += ['total_ram']
|
|
||||||
csv_sizes += [self.mem_summary['total_ram']]
|
|
||||||
|
|
||||||
csv_module_section += ['total_flash']
|
csv_module_section += ['total_flash']
|
||||||
csv_sizes += [self.mem_summary['total_flash']]
|
csv_sizes += [self.mem_summary['total_flash']]
|
||||||
|
|
||||||
|
@ -486,9 +685,6 @@ class MemapParser(object):
|
||||||
def generate_table(self, file_desc):
|
def generate_table(self, file_desc):
|
||||||
"""Generate a table from a memoy map
|
"""Generate a table from a memoy map
|
||||||
|
|
||||||
Positional arguments:
|
|
||||||
file_desc - the file to write out the final report to
|
|
||||||
|
|
||||||
Returns: string of the generated table
|
Returns: string of the generated table
|
||||||
"""
|
"""
|
||||||
# Create table
|
# Create table
|
||||||
|
@ -503,11 +699,11 @@ class MemapParser(object):
|
||||||
for i in list(self.print_sections):
|
for i in list(self.print_sections):
|
||||||
table.align[i] = 'r'
|
table.align[i] = 'r'
|
||||||
|
|
||||||
for i in sorted(self.modules):
|
for i in sorted(self.short_modules):
|
||||||
row = [i]
|
row = [i]
|
||||||
|
|
||||||
for k in self.print_sections:
|
for k in self.print_sections:
|
||||||
row.append(self.modules[i][k])
|
row.append(self.short_modules[i][k])
|
||||||
|
|
||||||
table.add_row(row)
|
table.add_row(row)
|
||||||
|
|
||||||
|
@ -520,23 +716,9 @@ class MemapParser(object):
|
||||||
output = table.get_string()
|
output = table.get_string()
|
||||||
output += '\n'
|
output += '\n'
|
||||||
|
|
||||||
if self.mem_summary['heap'] == 0:
|
|
||||||
output += "Allocated Heap: unknown\n"
|
|
||||||
else:
|
|
||||||
output += "Allocated Heap: %s bytes\n" % \
|
|
||||||
str(self.mem_summary['heap'])
|
|
||||||
|
|
||||||
if self.mem_summary['stack'] == 0:
|
|
||||||
output += "Allocated Stack: unknown\n"
|
|
||||||
else:
|
|
||||||
output += "Allocated Stack: %s bytes\n" % \
|
|
||||||
str(self.mem_summary['stack'])
|
|
||||||
|
|
||||||
output += "Total Static RAM memory (data + bss): %s bytes\n" % \
|
output += "Total Static RAM memory (data + bss): %s bytes\n" % \
|
||||||
str(self.mem_summary['static_ram'])
|
str(self.mem_summary['static_ram'])
|
||||||
output += "Total RAM memory (data + bss + heap + stack): %s bytes\n" % \
|
output += "Total Flash memory (text + data): %s bytes\n" % \
|
||||||
str(self.mem_summary['total_ram'])
|
|
||||||
output += "Total Flash memory (text + data + misc): %s bytes\n" % \
|
|
||||||
str(self.mem_summary['total_flash'])
|
str(self.mem_summary['total_flash'])
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
@ -544,36 +726,27 @@ class MemapParser(object):
|
||||||
toolchains = ["ARM", "ARM_STD", "ARM_MICRO", "GCC_ARM", "GCC_CR", "IAR"]
|
toolchains = ["ARM", "ARM_STD", "ARM_MICRO", "GCC_ARM", "GCC_CR", "IAR"]
|
||||||
|
|
||||||
def compute_report(self):
|
def compute_report(self):
|
||||||
|
""" Generates summary of memory usage for main areas
|
||||||
|
"""
|
||||||
|
|
||||||
for k in self.sections:
|
for k in self.sections:
|
||||||
self.subtotal[k] = 0
|
self.subtotal[k] = 0
|
||||||
|
|
||||||
for i in sorted(self.modules):
|
for i in sorted(self.short_modules):
|
||||||
for k in self.sections:
|
for k in self.sections:
|
||||||
self.subtotal[k] += self.modules[i][k]
|
self.subtotal[k] += self.short_modules[i][k]
|
||||||
|
|
||||||
# Calculate misc flash sections
|
|
||||||
self.misc_flash_mem = 0
|
|
||||||
for i in self.modules:
|
|
||||||
for k in self.misc_flash_sections:
|
|
||||||
if self.modules[i][k]:
|
|
||||||
self.misc_flash_mem += self.modules[i][k]
|
|
||||||
|
|
||||||
self.mem_summary = {
|
self.mem_summary = {
|
||||||
'static_ram': (self.subtotal['.data'] + self.subtotal['.bss']),
|
'static_ram': (self.subtotal['.data'] + self.subtotal['.bss']),
|
||||||
'heap': (self.subtotal['.heap']),
|
'total_flash': (self.subtotal['.text'] + self.subtotal['.data']),
|
||||||
'stack': (self.subtotal['.stack']),
|
|
||||||
'total_ram': (self.subtotal['.data'] + self.subtotal['.bss'] +
|
|
||||||
self.subtotal['.heap']+self.subtotal['.stack']),
|
|
||||||
'total_flash': (self.subtotal['.text'] + self.subtotal['.data'] +
|
|
||||||
self.misc_flash_mem),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.mem_report = []
|
self.mem_report = []
|
||||||
for i in sorted(self.modules):
|
for i in sorted(self.short_modules):
|
||||||
self.mem_report.append({
|
self.mem_report.append({
|
||||||
"module":i,
|
"module":i,
|
||||||
"size":{
|
"size":{
|
||||||
k:self.modules[i][k] for k in self.print_sections
|
k:self.short_modules[i][k] for k in self.print_sections
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -592,19 +765,21 @@ class MemapParser(object):
|
||||||
result = True
|
result = True
|
||||||
try:
|
try:
|
||||||
with open(mapfile, 'r') as file_input:
|
with open(mapfile, 'r') as file_input:
|
||||||
|
|
||||||
|
# Common to all toolchains: first search for objects in BUILD
|
||||||
|
self.list_dir_obj(os.path.abspath(mapfile))
|
||||||
|
|
||||||
if toolchain == "ARM" or toolchain == "ARM_STD" or\
|
if toolchain == "ARM" or toolchain == "ARM_STD" or\
|
||||||
toolchain == "ARM_MICRO":
|
toolchain == "ARM_MICRO":
|
||||||
self.search_objects(os.path.abspath(mapfile))
|
|
||||||
self.parse_map_file_armcc(file_input)
|
self.parse_map_file_armcc(file_input)
|
||||||
elif toolchain == "GCC_ARM" or toolchain == "GCC_CR":
|
elif toolchain == "GCC_ARM" or toolchain == "GCC_CR":
|
||||||
self.parse_map_file_gcc(file_input)
|
self.parse_map_file_gcc(file_input)
|
||||||
elif toolchain == "IAR":
|
elif toolchain == "IAR":
|
||||||
self.search_objects(os.path.abspath(mapfile))
|
|
||||||
self.parse_map_file_iar(file_input)
|
self.parse_map_file_iar(file_input)
|
||||||
else:
|
else:
|
||||||
result = False
|
result = False
|
||||||
|
|
||||||
self.compute_report()
|
self.remove_unused_modules()
|
||||||
|
|
||||||
except IOError as error:
|
except IOError as error:
|
||||||
print "I/O error({0}): {1}".format(error.errno, error.strerror)
|
print "I/O error({0}): {1}".format(error.errno, error.strerror)
|
||||||
|
@ -614,7 +789,7 @@ class MemapParser(object):
|
||||||
def main():
|
def main():
|
||||||
"""Entry Point"""
|
"""Entry Point"""
|
||||||
|
|
||||||
version = '0.3.12'
|
version = '0.4.0'
|
||||||
|
|
||||||
# Parser handling
|
# Parser handling
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
|
@ -631,6 +806,10 @@ def main():
|
||||||
required=True,
|
required=True,
|
||||||
type=argparse_uppercase_type(MemapParser.toolchains, "toolchain"))
|
type=argparse_uppercase_type(MemapParser.toolchains, "toolchain"))
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'-d', '--depth', dest='depth', type=int,
|
||||||
|
help='specify directory depth level to display report', required=False)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-o', '--output', help='output file name', required=False)
|
'-o', '--output', help='output file name', required=False)
|
||||||
|
|
||||||
|
@ -643,30 +822,33 @@ def main():
|
||||||
|
|
||||||
parser.add_argument('-v', '--version', action='version', version=version)
|
parser.add_argument('-v', '--version', action='version', version=version)
|
||||||
|
|
||||||
parser.add_argument('-d', '--detailed', action='store_true', help='Displays the elements in "Misc" in a detailed fashion', required=False)
|
|
||||||
|
|
||||||
# Parse/run command
|
# Parse/run command
|
||||||
if len(sys.argv) <= 1:
|
if len(sys.argv) <= 1:
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
# Create memap object
|
# Create memap object
|
||||||
memap = MemapParser(detailed_misc=args.detailed)
|
memap = MemapParser()
|
||||||
|
|
||||||
# Parse and decode a map file
|
# Parse and decode a map file
|
||||||
if args.file and args.toolchain:
|
if args.file and args.toolchain:
|
||||||
if memap.parse(args.file, args.toolchain) is False:
|
if memap.parse(args.file, args.toolchain) is False:
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
if args.depth is None:
|
||||||
|
depth = 2 # default depth level
|
||||||
|
else:
|
||||||
|
depth = args.depth
|
||||||
|
|
||||||
returned_string = None
|
returned_string = None
|
||||||
# Write output in file
|
# Write output in file
|
||||||
if args.output != None:
|
if args.output != None:
|
||||||
returned_string = memap.generate_output(args.export, args.output)
|
returned_string = memap.generate_output(args.export, \
|
||||||
|
depth, args.output)
|
||||||
else: # Write output in screen
|
else: # Write output in screen
|
||||||
returned_string = memap.generate_output(args.export)
|
returned_string = memap.generate_output(args.export, depth)
|
||||||
|
|
||||||
if args.export == 'table' and returned_string:
|
if args.export == 'table' and returned_string:
|
||||||
print returned_string
|
print returned_string
|
||||||
|
|
|
@ -100,6 +100,12 @@ if __name__ == '__main__':
|
||||||
default=False,
|
default=False,
|
||||||
help="Verbose diagnostic output")
|
help="Verbose diagnostic output")
|
||||||
|
|
||||||
|
parser.add_argument("--stats-depth",
|
||||||
|
type=int,
|
||||||
|
dest="stats_depth",
|
||||||
|
default=2,
|
||||||
|
help="Depth level for static memory report")
|
||||||
|
|
||||||
options = parser.parse_args()
|
options = parser.parse_args()
|
||||||
|
|
||||||
# Filter tests by path if specified
|
# Filter tests by path if specified
|
||||||
|
@ -215,7 +221,8 @@ if __name__ == '__main__':
|
||||||
jobs=options.jobs,
|
jobs=options.jobs,
|
||||||
continue_on_build_fail=options.continue_on_build_fail,
|
continue_on_build_fail=options.continue_on_build_fail,
|
||||||
app_config=options.app_config,
|
app_config=options.app_config,
|
||||||
build_profile=profile)
|
build_profile=profile,
|
||||||
|
stats_depth=options.stats_depth)
|
||||||
|
|
||||||
# If a path to a test spec is provided, write it to a file
|
# If a path to a test spec is provided, write it to a file
|
||||||
if options.test_spec:
|
if options.test_spec:
|
||||||
|
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
||||||
"""
|
"""
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
import json
|
||||||
|
|
||||||
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))
|
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))
|
||||||
sys.path.insert(0, ROOT)
|
sys.path.insert(0, ROOT)
|
||||||
|
@ -42,55 +43,103 @@ class MemapParserTests(unittest.TestCase):
|
||||||
self.memap_parser = MemapParser()
|
self.memap_parser = MemapParser()
|
||||||
|
|
||||||
self.memap_parser.modules = {
|
self.memap_parser.modules = {
|
||||||
"Misc": {
|
"mbed-os/targets/TARGET/TARGET_MCUS/api/pinmap.o": {
|
||||||
"unknown": 0,
|
".text": 1,
|
||||||
".ARM": 8,
|
".data": 2,
|
||||||
".ARM.extab": 0,
|
".bss": 3,
|
||||||
".init": 12,
|
|
||||||
"OUTPUT": 0,
|
|
||||||
".stack": 0,
|
|
||||||
".eh_frame": 0,
|
|
||||||
".fini_array": 4,
|
|
||||||
".heap": 0,
|
".heap": 0,
|
||||||
".stabstr": 0,
|
|
||||||
".interrupts_ram": 0,
|
|
||||||
".init_array": 0,
|
|
||||||
".stab": 0,
|
|
||||||
".ARM.attributes": 7347,
|
|
||||||
".bss": 8517,
|
|
||||||
".flash_config": 16,
|
|
||||||
".interrupts": 1024,
|
|
||||||
".data": 2325,
|
|
||||||
".ARM.exidx": 0,
|
|
||||||
".text": 59906,
|
|
||||||
".jcr": 0
|
|
||||||
},
|
|
||||||
"Fill": {
|
|
||||||
"unknown": 12,
|
|
||||||
".ARM": 0,
|
|
||||||
".ARM.extab": 0,
|
|
||||||
".init": 0,
|
|
||||||
"OUTPUT": 0,
|
|
||||||
".stack": 0,
|
".stack": 0,
|
||||||
".eh_frame": 0,
|
".interrupts_ram":0,
|
||||||
".fini_array": 0,
|
".init":0,
|
||||||
".heap": 65536,
|
".ARM.extab":0,
|
||||||
".stabstr": 0,
|
|
||||||
".interrupts_ram": 1024,
|
|
||||||
".init_array": 0,
|
|
||||||
".stab": 0,
|
|
||||||
".ARM.attributes": 0,
|
|
||||||
".bss": 2235,
|
|
||||||
".flash_config": 0,
|
|
||||||
".interrupts": 0,
|
|
||||||
".data": 3,
|
|
||||||
".ARM.exidx":0,
|
".ARM.exidx":0,
|
||||||
".text": 136,
|
".ARM.attributes":0,
|
||||||
".jcr": 0
|
".eh_frame":0,
|
||||||
|
".init_array":0,
|
||||||
|
".fini_array":0,
|
||||||
|
".jcr":0,
|
||||||
|
".stab":0,
|
||||||
|
".stabstr":0,
|
||||||
|
".ARM.exidx":0,
|
||||||
|
".ARM":0,
|
||||||
|
".interrupts":0,
|
||||||
|
".flash_config":0,
|
||||||
|
"unknown":0,
|
||||||
|
"OUTPUT":0,
|
||||||
|
},
|
||||||
|
"[lib]/libc.a/lib_a-printf.o": {
|
||||||
|
".text": 4,
|
||||||
|
".data": 5,
|
||||||
|
".bss": 6,
|
||||||
|
".heap": 0,
|
||||||
|
".stack": 0,
|
||||||
|
".interrupts_ram":0,
|
||||||
|
".init":0,
|
||||||
|
".ARM.extab":0,
|
||||||
|
".ARM.exidx":0,
|
||||||
|
".ARM.attributes":0,
|
||||||
|
".eh_frame":0,
|
||||||
|
".init_array":0,
|
||||||
|
".fini_array":0,
|
||||||
|
".jcr":0,
|
||||||
|
".stab":0,
|
||||||
|
".stabstr":0,
|
||||||
|
".ARM.exidx":0,
|
||||||
|
".ARM":0,
|
||||||
|
".interrupts":0,
|
||||||
|
".flash_config":0,
|
||||||
|
"unknown":0,
|
||||||
|
"OUTPUT":0,
|
||||||
|
},
|
||||||
|
"main.o": {
|
||||||
|
".text": 7,
|
||||||
|
".data": 8,
|
||||||
|
".bss": 0,
|
||||||
|
".heap": 0,
|
||||||
|
".stack": 0,
|
||||||
|
".interrupts_ram":0,
|
||||||
|
".init":0,
|
||||||
|
".ARM.extab":0,
|
||||||
|
".ARM.exidx":0,
|
||||||
|
".ARM.attributes":0,
|
||||||
|
".eh_frame":0,
|
||||||
|
".init_array":0,
|
||||||
|
".fini_array":0,
|
||||||
|
".jcr":0,
|
||||||
|
".stab":0,
|
||||||
|
".stabstr":0,
|
||||||
|
".ARM.exidx":0,
|
||||||
|
".ARM":0,
|
||||||
|
".interrupts":0,
|
||||||
|
".flash_config":0,
|
||||||
|
"unknown":0,
|
||||||
|
"OUTPUT":0,
|
||||||
|
},
|
||||||
|
"test.o": {
|
||||||
|
".text": 0,
|
||||||
|
".data": 0,
|
||||||
|
".bss": 0,
|
||||||
|
".heap": 0,
|
||||||
|
".stack": 0,
|
||||||
|
".interrupts_ram":0,
|
||||||
|
".init":0,
|
||||||
|
".ARM.extab":0,
|
||||||
|
".ARM.exidx":0,
|
||||||
|
".ARM.attributes":0,
|
||||||
|
".eh_frame":0,
|
||||||
|
".init_array":0,
|
||||||
|
".fini_array":0,
|
||||||
|
".jcr":0,
|
||||||
|
".stab":0,
|
||||||
|
".stabstr":0,
|
||||||
|
".ARM.exidx":0,
|
||||||
|
".ARM":0,
|
||||||
|
".interrupts":0,
|
||||||
|
".flash_config":0,
|
||||||
|
"unknown":0,
|
||||||
|
"OUTPUT":0,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
self.memap_parser.compute_report()
|
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
"""
|
"""
|
||||||
|
@ -100,10 +149,10 @@ class MemapParserTests(unittest.TestCase):
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def generate_test_helper(self, output_type, file_output=None):
|
def generate_test_helper(self, output_type, depth, file_output=None):
|
||||||
"""
|
"""
|
||||||
Helper that ensures that the member variables "modules", "mem_report",
|
Helper that ensures that the member variables "modules" is
|
||||||
and "mem_summary" are unchanged after calling "generate_output"
|
unchanged after calling "generate_output"
|
||||||
|
|
||||||
:param output_type: type string that is passed to "generate_output"
|
:param output_type: type string that is passed to "generate_output"
|
||||||
:param file_output: path to output file that is passed to "generate_output"
|
:param file_output: path to output file that is passed to "generate_output"
|
||||||
|
@ -111,15 +160,12 @@ class MemapParserTests(unittest.TestCase):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
old_modules = deepcopy(self.memap_parser.modules)
|
old_modules = deepcopy(self.memap_parser.modules)
|
||||||
old_report = deepcopy(self.memap_parser.mem_report)
|
|
||||||
old_summary = deepcopy(self.memap_parser.mem_summary)
|
self.memap_parser.generate_output(output_type, depth, file_output)
|
||||||
self.memap_parser.generate_output(output_type, file_output)
|
|
||||||
self.assertEqual(self.memap_parser.modules, old_modules,
|
self.assertEqual(self.memap_parser.modules, old_modules,
|
||||||
"generate_output modified the 'modules' property")
|
"generate_output modified the 'modules' property")
|
||||||
self.assertEqual(self.memap_parser.mem_report, old_report,
|
|
||||||
"generate_output modified the 'mem_report' property")
|
|
||||||
self.assertEqual(self.memap_parser.mem_summary, old_summary,
|
|
||||||
"generate_output modified the 'mem_summary' property")
|
|
||||||
|
|
||||||
def test_report_computed(self):
|
def test_report_computed(self):
|
||||||
"""
|
"""
|
||||||
|
@ -127,11 +173,12 @@ class MemapParserTests(unittest.TestCase):
|
||||||
|
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
self.assertTrue(self.memap_parser.mem_report)
|
|
||||||
|
self.memap_parser.generate_output('table', 2)
|
||||||
|
|
||||||
|
# Report is created after generating output
|
||||||
self.assertTrue(self.memap_parser.mem_summary)
|
self.assertTrue(self.memap_parser.mem_summary)
|
||||||
self.assertEqual(self.memap_parser.mem_report[-1]['summary'],
|
self.assertTrue(self.memap_parser.mem_report)
|
||||||
self.memap_parser.mem_summary,
|
|
||||||
"mem_report did not contain a correct copy of mem_summary")
|
|
||||||
|
|
||||||
def test_generate_output_table(self):
|
def test_generate_output_table(self):
|
||||||
"""
|
"""
|
||||||
|
@ -139,7 +186,8 @@ class MemapParserTests(unittest.TestCase):
|
||||||
|
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
self.generate_test_helper('table')
|
depth = 2
|
||||||
|
self.generate_test_helper('table', depth)
|
||||||
|
|
||||||
def test_generate_output_json(self):
|
def test_generate_output_json(self):
|
||||||
"""
|
"""
|
||||||
|
@ -148,7 +196,8 @@ class MemapParserTests(unittest.TestCase):
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
file_name = '.json_test_output.json'
|
file_name = '.json_test_output.json'
|
||||||
self.generate_test_helper('json', file_output=file_name)
|
depth = 2
|
||||||
|
self.generate_test_helper('json', depth, file_output=file_name)
|
||||||
self.assertTrue(os.path.exists(file_name), "Failed to create json file")
|
self.assertTrue(os.path.exists(file_name), "Failed to create json file")
|
||||||
os.remove(file_name)
|
os.remove(file_name)
|
||||||
|
|
||||||
|
@ -159,7 +208,8 @@ class MemapParserTests(unittest.TestCase):
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
file_name = '.csv_ci_test_output.csv'
|
file_name = '.csv_ci_test_output.csv'
|
||||||
self.generate_test_helper('csv-ci', file_output=file_name)
|
depth = 2
|
||||||
|
self.generate_test_helper('csv-ci', depth, file_output=file_name)
|
||||||
self.assertTrue(os.path.exists(file_name), "Failed to create csv-ci file")
|
self.assertTrue(os.path.exists(file_name), "Failed to create csv-ci file")
|
||||||
os.remove(file_name)
|
os.remove(file_name)
|
||||||
|
|
||||||
|
|
|
@ -496,7 +496,8 @@ class SingleTestRunner(object):
|
||||||
properties=build_properties,
|
properties=build_properties,
|
||||||
project_id=test_id,
|
project_id=test_id,
|
||||||
project_description=test.get_description(),
|
project_description=test.get_description(),
|
||||||
build_profile=profile)
|
build_profile=profile,
|
||||||
|
stats_depth=stats_depth)
|
||||||
|
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
project_name_str = project_name if project_name is not None else test_id
|
project_name_str = project_name if project_name is not None else test_id
|
||||||
|
@ -2122,7 +2123,7 @@ def build_tests(tests, base_source_paths, build_path, target, toolchain_name,
|
||||||
clean=False, notify=None, verbose=False, jobs=1, macros=None,
|
clean=False, notify=None, verbose=False, jobs=1, macros=None,
|
||||||
silent=False, report=None, properties=None,
|
silent=False, report=None, properties=None,
|
||||||
continue_on_build_fail=False, app_config=None,
|
continue_on_build_fail=False, app_config=None,
|
||||||
build_profile=None):
|
build_profile=None, stats_depth=None):
|
||||||
"""Given the data structure from 'find_tests' and the typical build parameters,
|
"""Given the data structure from 'find_tests' and the typical build parameters,
|
||||||
build all the tests
|
build all the tests
|
||||||
|
|
||||||
|
@ -2172,7 +2173,8 @@ def build_tests(tests, base_source_paths, build_path, target, toolchain_name,
|
||||||
'app_config': app_config,
|
'app_config': app_config,
|
||||||
'build_profile': build_profile,
|
'build_profile': build_profile,
|
||||||
'silent': True,
|
'silent': True,
|
||||||
'toolchain_paths': TOOLCHAIN_PATHS
|
'toolchain_paths': TOOLCHAIN_PATHS,
|
||||||
|
'stats_depth': stats_depth
|
||||||
}
|
}
|
||||||
|
|
||||||
results.append(p.apply_async(build_test_worker, args, kwargs))
|
results.append(p.apply_async(build_test_worker, args, kwargs))
|
||||||
|
|
|
@ -416,7 +416,6 @@ class mbedToolchain:
|
||||||
|
|
||||||
# Print output buffer
|
# Print output buffer
|
||||||
self.output = str()
|
self.output = str()
|
||||||
self.map_outputs = list() # Place to store memmap scan results in JSON like data structures
|
|
||||||
|
|
||||||
# uVisor spepcific rules
|
# uVisor spepcific rules
|
||||||
if 'UVISOR' in self.target.features and 'UVISOR_SUPPORTED' in self.target.extra_labels:
|
if 'UVISOR' in self.target.features and 'UVISOR_SUPPORTED' in self.target.extra_labels:
|
||||||
|
@ -1128,7 +1127,8 @@ class mbedToolchain:
|
||||||
self.progress("elf2bin", name)
|
self.progress("elf2bin", name)
|
||||||
self.binary(r, elf, bin)
|
self.binary(r, elf, bin)
|
||||||
|
|
||||||
self.map_outputs = self.mem_stats(map)
|
# Initialize memap and process map file. This doesn't generate output.
|
||||||
|
self.mem_stats(map)
|
||||||
|
|
||||||
self.var("compile_succeded", True)
|
self.var("compile_succeded", True)
|
||||||
self.var("binary", filename)
|
self.var("binary", filename)
|
||||||
|
@ -1193,8 +1193,7 @@ class mbedToolchain:
|
||||||
def mem_stats(self, map):
|
def mem_stats(self, map):
|
||||||
"""! Creates parser object
|
"""! Creates parser object
|
||||||
@param map Path to linker map file to parse and decode
|
@param map Path to linker map file to parse and decode
|
||||||
@return Memory summary structure with memory usage statistics
|
@return None
|
||||||
None if map file can't be opened and processed
|
|
||||||
"""
|
"""
|
||||||
toolchain = self.__class__.__name__
|
toolchain = self.__class__.__name__
|
||||||
|
|
||||||
|
@ -1209,10 +1208,10 @@ class mbedToolchain:
|
||||||
# Store the memap instance for later use
|
# Store the memap instance for later use
|
||||||
self.memap_instance = memap
|
self.memap_instance = memap
|
||||||
|
|
||||||
# Here we return memory statistics structure (constructed after
|
# Note: memory statistics are not returned.
|
||||||
# call to generate_output) which contains raw data in bytes
|
# Need call to generate_output later (depends on depth & output format)
|
||||||
# about sections + summary
|
|
||||||
return memap.mem_report
|
return None
|
||||||
|
|
||||||
# Set the configuration data
|
# Set the configuration data
|
||||||
def set_config_data(self, config_data):
|
def set_config_data(self, config_data):
|
||||||
|
|
Loading…
Reference in New Issue