Clone of official tools

Committer:
theotherjimmy
Date:
Tue Sep 25 13:43:09 2018 -0500
Revision:
43:2a7da56ebd24
Parent:
41:2a77626a4c21
Release 5.10.0

Who changed what in which revision?

UserRevisionLine numberNew contents of line
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 1 #!/usr/bin/env python
screamer 8:a8ac6ed29081 2
screamer 29:1210849dba19 3 """Memory Map File Analyser for ARM mbed"""
theotherjimmy 43:2a7da56ebd24 4 from __future__ import print_function, division, absolute_import
screamer 8:a8ac6ed29081 5
theotherjimmy 43:2a7da56ebd24 6 from abc import abstractmethod, ABCMeta
theotherjimmy 43:2a7da56ebd24 7 from sys import stdout, exit, argv, path
theotherjimmy 43:2a7da56ebd24 8 from os import sep, rename, remove
theotherjimmy 43:2a7da56ebd24 9 from os.path import (basename, dirname, join, relpath, abspath, commonprefix,
theotherjimmy 43:2a7da56ebd24 10 splitext, exists)
theotherjimmy 43:2a7da56ebd24 11
theotherjimmy 43:2a7da56ebd24 12 # Be sure that the tools directory is in the search path
theotherjimmy 43:2a7da56ebd24 13 ROOT = abspath(join(dirname(__file__), ".."))
theotherjimmy 43:2a7da56ebd24 14 path.insert(0, ROOT)
theotherjimmy 43:2a7da56ebd24 15
screamer 8:a8ac6ed29081 16 import re
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 17 import csv
screamer 8:a8ac6ed29081 18 import json
theotherjimmy 43:2a7da56ebd24 19 from argparse import ArgumentParser
theotherjimmy 40:7d3fa6b99b2b 20 from copy import deepcopy
theotherjimmy 43:2a7da56ebd24 21 from collections import defaultdict
theotherjimmy 43:2a7da56ebd24 22 from prettytable import PrettyTable, HEADER
theotherjimmy 43:2a7da56ebd24 23 from jinja2 import FileSystemLoader, StrictUndefined
theotherjimmy 43:2a7da56ebd24 24 from jinja2.environment import Environment
screamer 29:1210849dba19 25
theotherjimmy 43:2a7da56ebd24 26 from tools.utils import (argparse_filestring_type, argparse_lowercase_hyphen_type,
theotherjimmy 43:2a7da56ebd24 27 argparse_uppercase_type)
theotherjimmy 43:2a7da56ebd24 28 from tools.settings import COMPARE_FIXED
theotherjimmy 40:7d3fa6b99b2b 29
theotherjimmy 40:7d3fa6b99b2b 30
theotherjimmy 43:2a7da56ebd24 31 class _Parser(object):
theotherjimmy 43:2a7da56ebd24 32 """Internal interface for parsing"""
theotherjimmy 43:2a7da56ebd24 33 __metaclass__ = ABCMeta
theotherjimmy 43:2a7da56ebd24 34 SECTIONS = ('.text', '.data', '.bss', '.heap', '.stack')
theotherjimmy 43:2a7da56ebd24 35 MISC_FLASH_SECTIONS = ('.interrupts', '.flash_config')
theotherjimmy 43:2a7da56ebd24 36 OTHER_SECTIONS = ('.interrupts_ram', '.init', '.ARM.extab',
screamer 29:1210849dba19 37 '.ARM.exidx', '.ARM.attributes', '.eh_frame',
screamer 29:1210849dba19 38 '.init_array', '.fini_array', '.jcr', '.stab',
screamer 29:1210849dba19 39 '.stabstr', '.ARM.exidx', '.ARM')
screamer 29:1210849dba19 40
theotherjimmy 40:7d3fa6b99b2b 41 def __init__(self):
theotherjimmy 43:2a7da56ebd24 42 self.modules = dict()
theotherjimmy 40:7d3fa6b99b2b 43
theotherjimmy 40:7d3fa6b99b2b 44 def module_add(self, object_name, size, section):
theotherjimmy 43:2a7da56ebd24 45 """ Adds a module or section to the list
screamer 29:1210849dba19 46
screamer 29:1210849dba19 47 Positional arguments:
theotherjimmy 40:7d3fa6b99b2b 48 object_name - name of the entry to add
screamer 29:1210849dba19 49 size - the size of the module being added
screamer 29:1210849dba19 50 section - the section the module contributes to
screamer 8:a8ac6ed29081 51 """
theotherjimmy 40:7d3fa6b99b2b 52 if not object_name or not size or not section:
theotherjimmy 40:7d3fa6b99b2b 53 return
theotherjimmy 40:7d3fa6b99b2b 54
theotherjimmy 40:7d3fa6b99b2b 55 if object_name in self.modules:
theotherjimmy 40:7d3fa6b99b2b 56 self.modules[object_name].setdefault(section, 0)
theotherjimmy 40:7d3fa6b99b2b 57 self.modules[object_name][section] += size
theotherjimmy 40:7d3fa6b99b2b 58 return
theotherjimmy 40:7d3fa6b99b2b 59
theotherjimmy 43:2a7da56ebd24 60 obj_split = sep + basename(object_name)
theotherjimmy 40:7d3fa6b99b2b 61 for module_path, contents in self.modules.items():
theotherjimmy 40:7d3fa6b99b2b 62 if module_path.endswith(obj_split) or module_path == object_name:
theotherjimmy 40:7d3fa6b99b2b 63 contents.setdefault(section, 0)
theotherjimmy 40:7d3fa6b99b2b 64 contents[section] += size
theotherjimmy 40:7d3fa6b99b2b 65 return
theotherjimmy 40:7d3fa6b99b2b 66
theotherjimmy 43:2a7da56ebd24 67 new_module = defaultdict(int)
theotherjimmy 43:2a7da56ebd24 68 new_module[section] = size
theotherjimmy 40:7d3fa6b99b2b 69 self.modules[object_name] = new_module
theotherjimmy 40:7d3fa6b99b2b 70
theotherjimmy 40:7d3fa6b99b2b 71 def module_replace(self, old_object, new_object):
theotherjimmy 40:7d3fa6b99b2b 72 """ Replaces an object name with a new one
theotherjimmy 40:7d3fa6b99b2b 73 """
theotherjimmy 40:7d3fa6b99b2b 74 if old_object in self.modules:
theotherjimmy 40:7d3fa6b99b2b 75 self.modules[new_object] = self.modules[old_object]
theotherjimmy 40:7d3fa6b99b2b 76 del self.modules[old_object]
screamer 8:a8ac6ed29081 77
theotherjimmy 43:2a7da56ebd24 78 @abstractmethod
theotherjimmy 43:2a7da56ebd24 79 def parse_mapfile(self, mapfile):
theotherjimmy 43:2a7da56ebd24 80 """Parse a given file object pointing to a map file
theotherjimmy 43:2a7da56ebd24 81
theotherjimmy 43:2a7da56ebd24 82 Positional arguments:
theotherjimmy 43:2a7da56ebd24 83 mapfile - an open file object that reads a map file
theotherjimmy 43:2a7da56ebd24 84
theotherjimmy 43:2a7da56ebd24 85 return value - a dict mapping from object names to section dicts,
theotherjimmy 43:2a7da56ebd24 86 where a section dict maps from sections to sizes
theotherjimmy 43:2a7da56ebd24 87 """
theotherjimmy 43:2a7da56ebd24 88 raise NotImplemented
theotherjimmy 43:2a7da56ebd24 89
theotherjimmy 43:2a7da56ebd24 90
theotherjimmy 43:2a7da56ebd24 91 class _GccParser(_Parser):
theotherjimmy 43:2a7da56ebd24 92 RE_OBJECT_FILE = re.compile(r'^(.+\/.+\.o)$')
theotherjimmy 43:2a7da56ebd24 93 RE_LIBRARY_OBJECT = re.compile(r'^.+' + r''.format(sep) + r'lib((.+\.a)\((.+\.o)\))$')
theotherjimmy 43:2a7da56ebd24 94 RE_STD_SECTION = re.compile(r'^\s+.*0x(\w{8,16})\s+0x(\w+)\s(.+)$')
theotherjimmy 43:2a7da56ebd24 95 RE_FILL_SECTION = re.compile(r'^\s*\*fill\*\s+0x(\w{8,16})\s+0x(\w+).*$')
theotherjimmy 43:2a7da56ebd24 96
theotherjimmy 43:2a7da56ebd24 97 ALL_SECTIONS = _Parser.SECTIONS + _Parser.OTHER_SECTIONS + \
theotherjimmy 43:2a7da56ebd24 98 _Parser.MISC_FLASH_SECTIONS + ('unknown', 'OUTPUT')
theotherjimmy 43:2a7da56ebd24 99
theotherjimmy 43:2a7da56ebd24 100 def check_new_section(self, line):
theotherjimmy 43:2a7da56ebd24 101 """ Check whether a new section in a map file has been detected
screamer 29:1210849dba19 102
screamer 29:1210849dba19 103 Positional arguments:
screamer 29:1210849dba19 104 line - the line to check for a new section
theotherjimmy 43:2a7da56ebd24 105
theotherjimmy 43:2a7da56ebd24 106 return value - A section name, if a new section was found, False
theotherjimmy 43:2a7da56ebd24 107 otherwise
screamer 8:a8ac6ed29081 108 """
theotherjimmy 43:2a7da56ebd24 109 for i in self.ALL_SECTIONS:
screamer 8:a8ac6ed29081 110 if line.startswith(i):
screamer 29:1210849dba19 111 # should name of the section (assuming it's a known one)
screamer 29:1210849dba19 112 return i
screamer 8:a8ac6ed29081 113
screamer 8:a8ac6ed29081 114 if line.startswith('.'):
screamer 22:9e85236d8716 115 return 'unknown' # all others are classified are unknown
screamer 8:a8ac6ed29081 116 else:
screamer 8:a8ac6ed29081 117 return False # everything else, means no change in section
screamer 8:a8ac6ed29081 118
theotherjimmy 40:7d3fa6b99b2b 119
theotherjimmy 43:2a7da56ebd24 120 def parse_object_name(self, line):
theotherjimmy 40:7d3fa6b99b2b 121 """ Parse a path to object file
screamer 29:1210849dba19 122
screamer 29:1210849dba19 123 Positional arguments:
theotherjimmy 43:2a7da56ebd24 124 line - the path to parse the object and module name from
screamer 8:a8ac6ed29081 125
theotherjimmy 43:2a7da56ebd24 126 return value - an object file name
theotherjimmy 43:2a7da56ebd24 127 """
theotherjimmy 43:2a7da56ebd24 128 test_re_mbed_os_name = re.match(self.RE_OBJECT_FILE, line)
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 129
theotherjimmy 40:7d3fa6b99b2b 130 if test_re_mbed_os_name:
theotherjimmy 40:7d3fa6b99b2b 131 object_name = test_re_mbed_os_name.group(1)
screamer 8:a8ac6ed29081 132
theotherjimmy 40:7d3fa6b99b2b 133 # corner case: certain objects are provided by the GCC toolchain
theotherjimmy 40:7d3fa6b99b2b 134 if 'arm-none-eabi' in line:
theotherjimmy 43:2a7da56ebd24 135 return join('[lib]', 'misc', basename(object_name))
theotherjimmy 40:7d3fa6b99b2b 136 return object_name
screamer 8:a8ac6ed29081 137
theotherjimmy 40:7d3fa6b99b2b 138 else:
theotherjimmy 43:2a7da56ebd24 139 test_re_obj_name = re.match(self.RE_LIBRARY_OBJECT, line)
theotherjimmy 40:7d3fa6b99b2b 140
theotherjimmy 40:7d3fa6b99b2b 141 if test_re_obj_name:
theotherjimmy 43:2a7da56ebd24 142 return join('[lib]', test_re_obj_name.group(2),
theotherjimmy 43:2a7da56ebd24 143 test_re_obj_name.group(3))
The Other Jimmy 36:96847d42f010 144 else:
theotherjimmy 43:2a7da56ebd24 145 print("Unknown object name found in GCC map file: %s" % line)
theotherjimmy 40:7d3fa6b99b2b 146 return '[misc]'
screamer 8:a8ac6ed29081 147
theotherjimmy 43:2a7da56ebd24 148 def parse_section(self, line):
screamer 29:1210849dba19 149 """ Parse data from a section of gcc map file
screamer 29:1210849dba19 150
screamer 29:1210849dba19 151 examples:
The Other Jimmy 31:8ea194f6145b 152 0x00004308 0x7c ./BUILD/K64F/GCC_ARM/mbed-os/hal/targets/hal/TARGET_Freescale/TARGET_KPSDK_MCUS/spi_api.o
theotherjimmy 43:2a7da56ebd24 153 .text 0x00000608 0x198 ./BUILD/K64F/GCC_ARM/mbed-os/core/mbed-rtos/rtx/TARGET_CORTEX_M/TARGET_RTOS_M4_M7/TOOLCHAIN/HAL_CM4.o
screamer 29:1210849dba19 154
screamer 29:1210849dba19 155 Positional arguments:
screamer 29:1210849dba19 156 line - the line to parse a section from
screamer 8:a8ac6ed29081 157 """
theotherjimmy 43:2a7da56ebd24 158 is_fill = re.match(self.RE_FILL_SECTION, line)
theotherjimmy 40:7d3fa6b99b2b 159 if is_fill:
theotherjimmy 40:7d3fa6b99b2b 160 o_name = '[fill]'
theotherjimmy 40:7d3fa6b99b2b 161 o_size = int(is_fill.group(2), 16)
theotherjimmy 40:7d3fa6b99b2b 162 return [o_name, o_size]
screamer 8:a8ac6ed29081 163
theotherjimmy 43:2a7da56ebd24 164 is_section = re.match(self.RE_STD_SECTION, line)
theotherjimmy 40:7d3fa6b99b2b 165 if is_section:
theotherjimmy 40:7d3fa6b99b2b 166 o_size = int(is_section.group(2), 16)
theotherjimmy 40:7d3fa6b99b2b 167 if o_size:
theotherjimmy 43:2a7da56ebd24 168 o_name = self.parse_object_name(is_section.group(3))
theotherjimmy 40:7d3fa6b99b2b 169 return [o_name, o_size]
screamer 8:a8ac6ed29081 170
theotherjimmy 40:7d3fa6b99b2b 171 return ["", 0]
screamer 8:a8ac6ed29081 172
theotherjimmy 43:2a7da56ebd24 173 def parse_mapfile(self, file_desc):
screamer 29:1210849dba19 174 """ Main logic to decode gcc map files
screamer 29:1210849dba19 175
screamer 29:1210849dba19 176 Positional arguments:
screamer 29:1210849dba19 177 file_desc - a stream object to parse as a gcc map file
screamer 8:a8ac6ed29081 178 """
screamer 8:a8ac6ed29081 179 current_section = 'unknown'
screamer 8:a8ac6ed29081 180
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 181 with file_desc as infile:
screamer 8:a8ac6ed29081 182 for line in infile:
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 183 if line.startswith('Linker script and memory map'):
screamer 8:a8ac6ed29081 184 current_section = "unknown"
screamer 8:a8ac6ed29081 185 break
screamer 8:a8ac6ed29081 186
screamer 8:a8ac6ed29081 187 for line in infile:
theotherjimmy 43:2a7da56ebd24 188 next_section = self.check_new_section(line)
screamer 8:a8ac6ed29081 189
theotherjimmy 40:7d3fa6b99b2b 190 if next_section == "OUTPUT":
screamer 8:a8ac6ed29081 191 break
theotherjimmy 40:7d3fa6b99b2b 192 elif next_section:
theotherjimmy 40:7d3fa6b99b2b 193 current_section = next_section
theotherjimmy 40:7d3fa6b99b2b 194
theotherjimmy 43:2a7da56ebd24 195 object_name, object_size = self.parse_section(line)
theotherjimmy 40:7d3fa6b99b2b 196 self.module_add(object_name, object_size, current_section)
screamer 8:a8ac6ed29081 197
theotherjimmy 43:2a7da56ebd24 198 common_prefix = dirname(commonprefix([
theotherjimmy 40:7d3fa6b99b2b 199 o for o in self.modules.keys() if (o.endswith(".o") and not o.startswith("[lib]"))]))
theotherjimmy 40:7d3fa6b99b2b 200 new_modules = {}
theotherjimmy 40:7d3fa6b99b2b 201 for name, stats in self.modules.items():
theotherjimmy 40:7d3fa6b99b2b 202 if name.startswith("[lib]"):
theotherjimmy 40:7d3fa6b99b2b 203 new_modules[name] = stats
theotherjimmy 40:7d3fa6b99b2b 204 elif name.endswith(".o"):
theotherjimmy 43:2a7da56ebd24 205 new_modules[relpath(name, common_prefix)] = stats
theotherjimmy 40:7d3fa6b99b2b 206 else:
theotherjimmy 40:7d3fa6b99b2b 207 new_modules[name] = stats
theotherjimmy 43:2a7da56ebd24 208 return new_modules
theotherjimmy 43:2a7da56ebd24 209
theotherjimmy 40:7d3fa6b99b2b 210
theotherjimmy 43:2a7da56ebd24 211 class _ArmccParser(_Parser):
theotherjimmy 43:2a7da56ebd24 212 RE = re.compile(
theotherjimmy 43:2a7da56ebd24 213 r'^\s+0x(\w{8})\s+0x(\w{8})\s+(\w+)\s+(\w+)\s+(\d+)\s+[*]?.+\s+(.+)$')
theotherjimmy 43:2a7da56ebd24 214 RE_OBJECT = re.compile(r'(.+\.(l|ar))\((.+\.o)\)')
theotherjimmy 43:2a7da56ebd24 215
theotherjimmy 43:2a7da56ebd24 216 def parse_object_name(self, line):
theotherjimmy 40:7d3fa6b99b2b 217 """ Parse object file
screamer 8:a8ac6ed29081 218
theotherjimmy 40:7d3fa6b99b2b 219 Positional arguments:
theotherjimmy 40:7d3fa6b99b2b 220 line - the line containing the object or library
theotherjimmy 40:7d3fa6b99b2b 221 """
theotherjimmy 43:2a7da56ebd24 222 if line.endswith(".o"):
theotherjimmy 40:7d3fa6b99b2b 223 return line
screamer 8:a8ac6ed29081 224
theotherjimmy 40:7d3fa6b99b2b 225 else:
theotherjimmy 43:2a7da56ebd24 226 is_obj = re.match(self.RE_OBJECT, line)
theotherjimmy 40:7d3fa6b99b2b 227 if is_obj:
theotherjimmy 43:2a7da56ebd24 228 return join('[lib]', basename(is_obj.group(1)), is_obj.group(3))
theotherjimmy 40:7d3fa6b99b2b 229 else:
theotherjimmy 43:2a7da56ebd24 230 print("Malformed input found when parsing ARMCC map: %s" % line)
theotherjimmy 40:7d3fa6b99b2b 231 return '[misc]'
theotherjimmy 40:7d3fa6b99b2b 232
theotherjimmy 43:2a7da56ebd24 233 def parse_section(self, line):
screamer 29:1210849dba19 234 """ Parse data from an armcc map file
screamer 29:1210849dba19 235
screamer 29:1210849dba19 236 Examples of armcc map file:
screamer 29:1210849dba19 237 Base_Addr Size Type Attr Idx E Section Name Object
theotherjimmy 43:2a7da56ebd24 238 0x00000000 0x00000400 Data RO 11222 self.RESET startup_MK64F12.o
screamer 29:1210849dba19 239 0x00000410 0x00000008 Code RO 49364 * !!!main c_w.l(__main.o)
screamer 29:1210849dba19 240
screamer 29:1210849dba19 241 Positional arguments:
screamer 29:1210849dba19 242 line - the line to parse the section data from
screamer 8:a8ac6ed29081 243 """
theotherjimmy 43:2a7da56ebd24 244 test_re = re.match(self.RE, line)
screamer 8:a8ac6ed29081 245
theotherjimmy 43:2a7da56ebd24 246 if test_re:
theotherjimmy 43:2a7da56ebd24 247 size = int(test_re.group(2), 16)
screamer 8:a8ac6ed29081 248
theotherjimmy 43:2a7da56ebd24 249 if test_re.group(4) == 'RO':
screamer 8:a8ac6ed29081 250 section = '.text'
screamer 8:a8ac6ed29081 251 else:
theotherjimmy 43:2a7da56ebd24 252 if test_re.group(3) == 'Data':
screamer 8:a8ac6ed29081 253 section = '.data'
theotherjimmy 43:2a7da56ebd24 254 elif test_re.group(3) == 'Zero':
screamer 8:a8ac6ed29081 255 section = '.bss'
theotherjimmy 43:2a7da56ebd24 256 elif test_re.group(3) == 'Code':
theotherjimmy 43:2a7da56ebd24 257 section = '.text'
screamer 8:a8ac6ed29081 258 else:
theotherjimmy 43:2a7da56ebd24 259 print("Malformed input found when parsing armcc map: %s, %r"
theotherjimmy 43:2a7da56ebd24 260 % (line, test_re.groups()))
theotherjimmy 43:2a7da56ebd24 261
theotherjimmy 43:2a7da56ebd24 262 return ["", 0, ""]
screamer 8:a8ac6ed29081 263
theotherjimmy 40:7d3fa6b99b2b 264 # check name of object or library
theotherjimmy 43:2a7da56ebd24 265 object_name = self.parse_object_name(
theotherjimmy 43:2a7da56ebd24 266 test_re.group(6))
screamer 8:a8ac6ed29081 267
theotherjimmy 40:7d3fa6b99b2b 268 return [object_name, size, section]
screamer 8:a8ac6ed29081 269
screamer 8:a8ac6ed29081 270 else:
theotherjimmy 40:7d3fa6b99b2b 271 return ["", 0, ""]
theotherjimmy 40:7d3fa6b99b2b 272
theotherjimmy 43:2a7da56ebd24 273 def parse_mapfile(self, file_desc):
theotherjimmy 43:2a7da56ebd24 274 """ Main logic to decode armc5 map files
theotherjimmy 43:2a7da56ebd24 275
theotherjimmy 43:2a7da56ebd24 276 Positional arguments:
theotherjimmy 43:2a7da56ebd24 277 file_desc - a file like object to parse as an armc5 map file
theotherjimmy 43:2a7da56ebd24 278 """
theotherjimmy 43:2a7da56ebd24 279 with file_desc as infile:
theotherjimmy 43:2a7da56ebd24 280 # Search area to parse
theotherjimmy 43:2a7da56ebd24 281 for line in infile:
theotherjimmy 43:2a7da56ebd24 282 if line.startswith(' Base Addr Size'):
theotherjimmy 43:2a7da56ebd24 283 break
theotherjimmy 43:2a7da56ebd24 284
theotherjimmy 43:2a7da56ebd24 285 # Start decoding the map file
theotherjimmy 43:2a7da56ebd24 286 for line in infile:
theotherjimmy 43:2a7da56ebd24 287 self.module_add(*self.parse_section(line))
theotherjimmy 43:2a7da56ebd24 288
theotherjimmy 43:2a7da56ebd24 289 common_prefix = dirname(commonprefix([
theotherjimmy 43:2a7da56ebd24 290 o for o in self.modules.keys() if (o.endswith(".o") and o != "anon$$obj.o" and not o.startswith("[lib]"))]))
theotherjimmy 43:2a7da56ebd24 291 new_modules = {}
theotherjimmy 43:2a7da56ebd24 292 for name, stats in self.modules.items():
theotherjimmy 43:2a7da56ebd24 293 if name == "anon$$obj.o" or name.startswith("[lib]"):
theotherjimmy 43:2a7da56ebd24 294 new_modules[name] = stats
theotherjimmy 43:2a7da56ebd24 295 elif name.endswith(".o"):
theotherjimmy 43:2a7da56ebd24 296 new_modules[relpath(name, common_prefix)] = stats
theotherjimmy 43:2a7da56ebd24 297 else:
theotherjimmy 43:2a7da56ebd24 298 new_modules[name] = stats
theotherjimmy 43:2a7da56ebd24 299 return new_modules
theotherjimmy 43:2a7da56ebd24 300
theotherjimmy 43:2a7da56ebd24 301
theotherjimmy 43:2a7da56ebd24 302 class _IarParser(_Parser):
theotherjimmy 43:2a7da56ebd24 303 RE = re.compile(
theotherjimmy 43:2a7da56ebd24 304 r'^\s+(.+)\s+(zero|const|ro code|inited|uninit)\s'
theotherjimmy 43:2a7da56ebd24 305 r'+0x(\w{8})\s+0x(\w+)\s+(.+)\s.+$')
theotherjimmy 43:2a7da56ebd24 306
theotherjimmy 43:2a7da56ebd24 307 RE_CMDLINE_FILE = re.compile(r'^#\s+(.+\.o)')
theotherjimmy 43:2a7da56ebd24 308 RE_LIBRARY = re.compile(r'^(.+\.a)\:.+$')
theotherjimmy 43:2a7da56ebd24 309 RE_OBJECT_LIBRARY = re.compile(r'^\s+(.+\.o)\s.*')
theotherjimmy 43:2a7da56ebd24 310
theotherjimmy 43:2a7da56ebd24 311 def __init__(self):
theotherjimmy 43:2a7da56ebd24 312 _Parser.__init__(self)
theotherjimmy 43:2a7da56ebd24 313 # Modules passed to the linker on the command line
theotherjimmy 43:2a7da56ebd24 314 # this is a dict because modules are looked up by their basename
theotherjimmy 43:2a7da56ebd24 315 self.cmd_modules = {}
theotherjimmy 43:2a7da56ebd24 316
theotherjimmy 43:2a7da56ebd24 317 def parse_object_name(self, object_name):
theotherjimmy 40:7d3fa6b99b2b 318 """ Parse object file
theotherjimmy 40:7d3fa6b99b2b 319
theotherjimmy 40:7d3fa6b99b2b 320 Positional arguments:
theotherjimmy 40:7d3fa6b99b2b 321 line - the line containing the object or library
theotherjimmy 40:7d3fa6b99b2b 322 """
theotherjimmy 40:7d3fa6b99b2b 323 if object_name.endswith(".o"):
theotherjimmy 40:7d3fa6b99b2b 324 try:
theotherjimmy 40:7d3fa6b99b2b 325 return self.cmd_modules[object_name]
theotherjimmy 40:7d3fa6b99b2b 326 except KeyError:
theotherjimmy 40:7d3fa6b99b2b 327 return object_name
theotherjimmy 40:7d3fa6b99b2b 328 else:
theotherjimmy 40:7d3fa6b99b2b 329 return '[misc]'
theotherjimmy 40:7d3fa6b99b2b 330
theotherjimmy 43:2a7da56ebd24 331 def parse_section(self, line):
screamer 29:1210849dba19 332 """ Parse data from an IAR map file
screamer 29:1210849dba19 333
screamer 29:1210849dba19 334 Examples of IAR map file:
screamer 29:1210849dba19 335 Section Kind Address Size Object
screamer 29:1210849dba19 336 .intvec ro code 0x00000000 0x198 startup_MK64F12.o [15]
screamer 29:1210849dba19 337 .rodata const 0x00000198 0x0 zero_init3.o [133]
screamer 29:1210849dba19 338 .iar.init_table const 0x00008384 0x2c - Linker created -
screamer 29:1210849dba19 339 Initializer bytes const 0x00000198 0xb2 <for P3 s0>
screamer 29:1210849dba19 340 .data inited 0x20000000 0xd4 driverAtmelRFInterface.o [70]
screamer 29:1210849dba19 341 .bss zero 0x20000598 0x318 RTX_Conf_CM.o [4]
screamer 29:1210849dba19 342 .iar.dynexit uninit 0x20001448 0x204 <Block tail>
screamer 29:1210849dba19 343 HEAP uninit 0x20001650 0x10000 <Block tail>
screamer 29:1210849dba19 344
screamer 29:1210849dba19 345 Positional_arguments:
screamer 29:1210849dba19 346 line - the line to parse section data from
screamer 8:a8ac6ed29081 347 """
theotherjimmy 43:2a7da56ebd24 348 test_re = re.match(self.RE, line)
theotherjimmy 43:2a7da56ebd24 349 if test_re:
theotherjimmy 43:2a7da56ebd24 350 if (test_re.group(2) == 'const' or
theotherjimmy 43:2a7da56ebd24 351 test_re.group(2) == 'ro code'):
screamer 8:a8ac6ed29081 352 section = '.text'
theotherjimmy 43:2a7da56ebd24 353 elif (test_re.group(2) == 'zero' or
theotherjimmy 43:2a7da56ebd24 354 test_re.group(2) == 'uninit'):
theotherjimmy 43:2a7da56ebd24 355 if test_re.group(1)[0:4] == 'HEAP':
screamer 8:a8ac6ed29081 356 section = '.heap'
theotherjimmy 43:2a7da56ebd24 357 elif test_re.group(1)[0:6] == 'CSTACK':
screamer 8:a8ac6ed29081 358 section = '.stack'
screamer 8:a8ac6ed29081 359 else:
screamer 8:a8ac6ed29081 360 section = '.bss' # default section
screamer 8:a8ac6ed29081 361
theotherjimmy 43:2a7da56ebd24 362 elif test_re.group(2) == 'inited':
screamer 8:a8ac6ed29081 363 section = '.data'
screamer 8:a8ac6ed29081 364 else:
theotherjimmy 43:2a7da56ebd24 365 print("Malformed input found when parsing IAR map: %s" % line)
theotherjimmy 43:2a7da56ebd24 366 return ["", 0, ""]
screamer 8:a8ac6ed29081 367
screamer 8:a8ac6ed29081 368 # lookup object in dictionary and return module name
theotherjimmy 43:2a7da56ebd24 369 object_name = self.parse_object_name(test_re.group(5))
screamer 8:a8ac6ed29081 370
theotherjimmy 43:2a7da56ebd24 371 size = int(test_re.group(4), 16)
theotherjimmy 40:7d3fa6b99b2b 372 return [object_name, size, section]
screamer 8:a8ac6ed29081 373
screamer 8:a8ac6ed29081 374 else:
theotherjimmy 43:2a7da56ebd24 375 return ["", 0, ""]
screamer 8:a8ac6ed29081 376
theotherjimmy 43:2a7da56ebd24 377 def check_new_library(self, line):
theotherjimmy 40:7d3fa6b99b2b 378 """
theotherjimmy 40:7d3fa6b99b2b 379 Searches for libraries and returns name. Example:
theotherjimmy 40:7d3fa6b99b2b 380 m7M_tls.a: [43]
theotherjimmy 40:7d3fa6b99b2b 381
theotherjimmy 40:7d3fa6b99b2b 382 """
theotherjimmy 43:2a7da56ebd24 383 test_address_line = re.match(self.RE_LIBRARY, line)
theotherjimmy 40:7d3fa6b99b2b 384 if test_address_line:
theotherjimmy 40:7d3fa6b99b2b 385 return test_address_line.group(1)
theotherjimmy 40:7d3fa6b99b2b 386 else:
theotherjimmy 40:7d3fa6b99b2b 387 return ""
screamer 8:a8ac6ed29081 388
theotherjimmy 43:2a7da56ebd24 389 def check_new_object_lib(self, line):
theotherjimmy 40:7d3fa6b99b2b 390 """
theotherjimmy 40:7d3fa6b99b2b 391 Searches for objects within a library section and returns name. Example:
theotherjimmy 40:7d3fa6b99b2b 392 rt7M_tl.a: [44]
theotherjimmy 40:7d3fa6b99b2b 393 ABImemclr4.o 6
theotherjimmy 40:7d3fa6b99b2b 394 ABImemcpy_unaligned.o 118
theotherjimmy 40:7d3fa6b99b2b 395 ABImemset48.o 50
theotherjimmy 40:7d3fa6b99b2b 396 I64DivMod.o 238
theotherjimmy 40:7d3fa6b99b2b 397 I64DivZer.o 2
theotherjimmy 40:7d3fa6b99b2b 398
theotherjimmy 40:7d3fa6b99b2b 399 """
theotherjimmy 43:2a7da56ebd24 400 test_address_line = re.match(self.RE_OBJECT_LIBRARY, line)
theotherjimmy 40:7d3fa6b99b2b 401 if test_address_line:
theotherjimmy 40:7d3fa6b99b2b 402 return test_address_line.group(1)
theotherjimmy 40:7d3fa6b99b2b 403 else:
theotherjimmy 40:7d3fa6b99b2b 404 return ""
theotherjimmy 40:7d3fa6b99b2b 405
theotherjimmy 43:2a7da56ebd24 406 def parse_command_line(self, lines):
theotherjimmy 40:7d3fa6b99b2b 407 """Parse the files passed on the command line to the iar linker
theotherjimmy 40:7d3fa6b99b2b 408
theotherjimmy 40:7d3fa6b99b2b 409 Positional arguments:
theotherjimmy 40:7d3fa6b99b2b 410 lines -- an iterator over the lines within a file
theotherjimmy 40:7d3fa6b99b2b 411 """
theotherjimmy 40:7d3fa6b99b2b 412 for line in lines:
theotherjimmy 40:7d3fa6b99b2b 413 if line.startswith("*"):
theotherjimmy 40:7d3fa6b99b2b 414 break
theotherjimmy 43:2a7da56ebd24 415 for arg in line.split(" "):
theotherjimmy 43:2a7da56ebd24 416 arg = arg.rstrip(" \n")
theotherjimmy 43:2a7da56ebd24 417 if (not arg.startswith("-")) and arg.endswith(".o"):
theotherjimmy 43:2a7da56ebd24 418 self.cmd_modules[basename(arg)] = arg
theotherjimmy 40:7d3fa6b99b2b 419
theotherjimmy 43:2a7da56ebd24 420 common_prefix = dirname(commonprefix(list(self.cmd_modules.values())))
theotherjimmy 43:2a7da56ebd24 421 self.cmd_modules = {s: relpath(f, common_prefix)
theotherjimmy 40:7d3fa6b99b2b 422 for s, f in self.cmd_modules.items()}
theotherjimmy 40:7d3fa6b99b2b 423
theotherjimmy 43:2a7da56ebd24 424 def parse_mapfile(self, file_desc):
screamer 29:1210849dba19 425 """ Main logic to decode IAR map files
screamer 29:1210849dba19 426
screamer 29:1210849dba19 427 Positional arguments:
screamer 29:1210849dba19 428 file_desc - a file like object to parse as an IAR map file
screamer 8:a8ac6ed29081 429 """
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 430 with file_desc as infile:
theotherjimmy 43:2a7da56ebd24 431 self.parse_command_line(infile)
screamer 8:a8ac6ed29081 432
screamer 8:a8ac6ed29081 433 for line in infile:
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 434 if line.startswith(' Section '):
screamer 8:a8ac6ed29081 435 break
screamer 8:a8ac6ed29081 436
theotherjimmy 40:7d3fa6b99b2b 437 for line in infile:
theotherjimmy 43:2a7da56ebd24 438 self.module_add(*self.parse_section(line))
theotherjimmy 40:7d3fa6b99b2b 439
theotherjimmy 40:7d3fa6b99b2b 440 if line.startswith('*** MODULE SUMMARY'): # finish section
theotherjimmy 40:7d3fa6b99b2b 441 break
theotherjimmy 40:7d3fa6b99b2b 442
theotherjimmy 40:7d3fa6b99b2b 443 current_library = ""
screamer 8:a8ac6ed29081 444 for line in infile:
theotherjimmy 43:2a7da56ebd24 445 library = self.check_new_library(line)
screamer 8:a8ac6ed29081 446
theotherjimmy 40:7d3fa6b99b2b 447 if library:
theotherjimmy 40:7d3fa6b99b2b 448 current_library = library
screamer 8:a8ac6ed29081 449
theotherjimmy 43:2a7da56ebd24 450 object_name = self.check_new_object_lib(line)
screamer 29:1210849dba19 451
theotherjimmy 40:7d3fa6b99b2b 452 if object_name and current_library:
theotherjimmy 43:2a7da56ebd24 453 temp = join('[lib]', current_library, object_name)
theotherjimmy 40:7d3fa6b99b2b 454 self.module_replace(object_name, temp)
theotherjimmy 43:2a7da56ebd24 455 return self.modules
theotherjimmy 40:7d3fa6b99b2b 456
screamer 8:a8ac6ed29081 457
theotherjimmy 43:2a7da56ebd24 458 class MemapParser(object):
theotherjimmy 43:2a7da56ebd24 459 """An object that represents parsed results, parses the memory map files,
theotherjimmy 43:2a7da56ebd24 460 and writes out different file types of memory results
theotherjimmy 43:2a7da56ebd24 461 """
theotherjimmy 43:2a7da56ebd24 462
theotherjimmy 43:2a7da56ebd24 463 print_sections = ('.text', '.data', '.bss')
theotherjimmy 43:2a7da56ebd24 464 delta_sections = ('.text-delta', '.data-delta', '.bss-delta')
theotherjimmy 43:2a7da56ebd24 465
theotherjimmy 43:2a7da56ebd24 466
theotherjimmy 43:2a7da56ebd24 467 # sections to print info (generic for all toolchains)
theotherjimmy 43:2a7da56ebd24 468 sections = _Parser.SECTIONS
theotherjimmy 43:2a7da56ebd24 469 misc_flash_sections = _Parser.MISC_FLASH_SECTIONS
theotherjimmy 43:2a7da56ebd24 470 other_sections = _Parser.OTHER_SECTIONS
theotherjimmy 43:2a7da56ebd24 471
theotherjimmy 43:2a7da56ebd24 472 def __init__(self):
theotherjimmy 43:2a7da56ebd24 473 # list of all modules and their sections
theotherjimmy 43:2a7da56ebd24 474 # full list - doesn't change with depth
theotherjimmy 43:2a7da56ebd24 475 self.modules = dict()
theotherjimmy 43:2a7da56ebd24 476 self.old_modules = None
theotherjimmy 43:2a7da56ebd24 477 # short version with specific depth
theotherjimmy 43:2a7da56ebd24 478 self.short_modules = dict()
theotherjimmy 43:2a7da56ebd24 479
theotherjimmy 43:2a7da56ebd24 480
theotherjimmy 43:2a7da56ebd24 481 # Memory report (sections + summary)
theotherjimmy 43:2a7da56ebd24 482 self.mem_report = []
theotherjimmy 43:2a7da56ebd24 483
theotherjimmy 43:2a7da56ebd24 484 # Memory summary
theotherjimmy 43:2a7da56ebd24 485 self.mem_summary = dict()
theotherjimmy 43:2a7da56ebd24 486
theotherjimmy 43:2a7da56ebd24 487 # Totals of ".text", ".data" and ".bss"
theotherjimmy 43:2a7da56ebd24 488 self.subtotal = dict()
theotherjimmy 43:2a7da56ebd24 489
theotherjimmy 43:2a7da56ebd24 490 # Flash no associated with a module
theotherjimmy 43:2a7da56ebd24 491 self.misc_flash_mem = 0
theotherjimmy 43:2a7da56ebd24 492
theotherjimmy 43:2a7da56ebd24 493 # Name of the toolchain, for better headings
theotherjimmy 43:2a7da56ebd24 494 self.tc_name = None
theotherjimmy 43:2a7da56ebd24 495
theotherjimmy 40:7d3fa6b99b2b 496 def reduce_depth(self, depth):
theotherjimmy 40:7d3fa6b99b2b 497 """
theotherjimmy 40:7d3fa6b99b2b 498 populates the short_modules attribute with a truncated module list
screamer 8:a8ac6ed29081 499
theotherjimmy 40:7d3fa6b99b2b 500 (1) depth = 1:
theotherjimmy 40:7d3fa6b99b2b 501 main.o
theotherjimmy 40:7d3fa6b99b2b 502 mbed-os
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 503
theotherjimmy 40:7d3fa6b99b2b 504 (2) depth = 2:
theotherjimmy 40:7d3fa6b99b2b 505 main.o
theotherjimmy 40:7d3fa6b99b2b 506 mbed-os/test.o
theotherjimmy 40:7d3fa6b99b2b 507 mbed-os/drivers
screamer 8:a8ac6ed29081 508
theotherjimmy 40:7d3fa6b99b2b 509 """
theotherjimmy 40:7d3fa6b99b2b 510 if depth == 0 or depth == None:
theotherjimmy 40:7d3fa6b99b2b 511 self.short_modules = deepcopy(self.modules)
theotherjimmy 40:7d3fa6b99b2b 512 else:
theotherjimmy 40:7d3fa6b99b2b 513 self.short_modules = dict()
theotherjimmy 40:7d3fa6b99b2b 514 for module_name, v in self.modules.items():
theotherjimmy 43:2a7da56ebd24 515 split_name = module_name.split(sep)
theotherjimmy 40:7d3fa6b99b2b 516 if split_name[0] == '':
theotherjimmy 40:7d3fa6b99b2b 517 split_name = split_name[1:]
theotherjimmy 43:2a7da56ebd24 518 new_name = join(*split_name[:depth])
theotherjimmy 43:2a7da56ebd24 519 self.short_modules.setdefault(new_name, defaultdict(int))
theotherjimmy 40:7d3fa6b99b2b 520 for section_idx, value in v.items():
theotherjimmy 40:7d3fa6b99b2b 521 self.short_modules[new_name][section_idx] += self.modules[module_name][section_idx]
theotherjimmy 43:2a7da56ebd24 522 self.short_modules[new_name][section_idx + '-delta'] += self.modules[module_name][section_idx]
theotherjimmy 43:2a7da56ebd24 523 if self.old_modules:
theotherjimmy 43:2a7da56ebd24 524 for module_name, v in self.old_modules.items():
theotherjimmy 43:2a7da56ebd24 525 split_name = module_name.split(sep)
theotherjimmy 43:2a7da56ebd24 526 if split_name[0] == '':
theotherjimmy 43:2a7da56ebd24 527 split_name = split_name[1:]
theotherjimmy 43:2a7da56ebd24 528 new_name = join(*split_name[:depth])
theotherjimmy 43:2a7da56ebd24 529 self.short_modules.setdefault(new_name, defaultdict(int))
theotherjimmy 43:2a7da56ebd24 530 for section_idx, value in v.items():
theotherjimmy 43:2a7da56ebd24 531 self.short_modules[new_name][section_idx + '-delta'] -= self.old_modules[module_name][section_idx]
screamer 8:a8ac6ed29081 532
theotherjimmy 43:2a7da56ebd24 533 export_formats = ["json", "csv-ci", "html", "table"]
screamer 22:9e85236d8716 534
theotherjimmy 40:7d3fa6b99b2b 535 def generate_output(self, export_format, depth, file_output=None):
screamer 29:1210849dba19 536 """ Generates summary of memory map data
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 537
screamer 29:1210849dba19 538 Positional arguments:
screamer 29:1210849dba19 539 export_format - the format to dump
screamer 29:1210849dba19 540
screamer 29:1210849dba19 541 Keyword arguments:
screamer 29:1210849dba19 542 file_desc - descriptor (either stdout or file)
theotherjimmy 40:7d3fa6b99b2b 543 depth - directory depth on report
The Other Jimmy 31:8ea194f6145b 544
The Other Jimmy 31:8ea194f6145b 545 Returns: generated string for the 'table' format, otherwise None
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 546 """
theotherjimmy 43:2a7da56ebd24 547 if depth is None or depth > 0:
theotherjimmy 43:2a7da56ebd24 548 self.reduce_depth(depth)
theotherjimmy 40:7d3fa6b99b2b 549 self.compute_report()
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 550 try:
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 551 if file_output:
theotherjimmy 43:2a7da56ebd24 552 file_desc = open(file_output, 'w')
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 553 else:
theotherjimmy 43:2a7da56ebd24 554 file_desc = stdout
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 555 except IOError as error:
theotherjimmy 43:2a7da56ebd24 556 print("I/O error({0}): {1}".format(error.errno, error.strerror))
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 557 return False
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 558
screamer 29:1210849dba19 559 to_call = {'json': self.generate_json,
theotherjimmy 43:2a7da56ebd24 560 'html': self.generate_html,
screamer 29:1210849dba19 561 'csv-ci': self.generate_csv,
screamer 29:1210849dba19 562 'table': self.generate_table}[export_format]
The Other Jimmy 31:8ea194f6145b 563 output = to_call(file_desc)
screamer 29:1210849dba19 564
theotherjimmy 43:2a7da56ebd24 565 if file_desc is not stdout:
screamer 29:1210849dba19 566 file_desc.close()
screamer 29:1210849dba19 567
The Other Jimmy 31:8ea194f6145b 568 return output
The Other Jimmy 31:8ea194f6145b 569
theotherjimmy 43:2a7da56ebd24 570 @staticmethod
theotherjimmy 43:2a7da56ebd24 571 def _move_up_tree(tree, next_module):
theotherjimmy 43:2a7da56ebd24 572 tree.setdefault("children", [])
theotherjimmy 43:2a7da56ebd24 573 for child in tree["children"]:
theotherjimmy 43:2a7da56ebd24 574 if child["name"] == next_module:
theotherjimmy 43:2a7da56ebd24 575 return child
theotherjimmy 43:2a7da56ebd24 576 else:
theotherjimmy 43:2a7da56ebd24 577 new_module = {"name": next_module, "value": 0, "delta": 0}
theotherjimmy 43:2a7da56ebd24 578 tree["children"].append(new_module)
theotherjimmy 43:2a7da56ebd24 579 return new_module
theotherjimmy 43:2a7da56ebd24 580
theotherjimmy 43:2a7da56ebd24 581 def generate_html(self, file_desc):
theotherjimmy 43:2a7da56ebd24 582 """Generate a json file from a memory map for D3
theotherjimmy 43:2a7da56ebd24 583
theotherjimmy 43:2a7da56ebd24 584 Positional arguments:
theotherjimmy 43:2a7da56ebd24 585 file_desc - the file to write out the final report to
theotherjimmy 43:2a7da56ebd24 586 """
theotherjimmy 43:2a7da56ebd24 587 tree_text = {"name": ".text", "value": 0, "delta": 0}
theotherjimmy 43:2a7da56ebd24 588 tree_bss = {"name": ".bss", "value": 0, "delta": 0}
theotherjimmy 43:2a7da56ebd24 589 tree_data = {"name": ".data", "value": 0, "delta": 0}
theotherjimmy 43:2a7da56ebd24 590 for name, dct in self.modules.items():
theotherjimmy 43:2a7da56ebd24 591 cur_text = tree_text
theotherjimmy 43:2a7da56ebd24 592 cur_bss = tree_bss
theotherjimmy 43:2a7da56ebd24 593 cur_data = tree_data
theotherjimmy 43:2a7da56ebd24 594 modules = name.split(sep)
theotherjimmy 43:2a7da56ebd24 595 while True:
theotherjimmy 43:2a7da56ebd24 596 try:
theotherjimmy 43:2a7da56ebd24 597 cur_text["value"] += dct['.text']
theotherjimmy 43:2a7da56ebd24 598 cur_text["delta"] += dct['.text']
theotherjimmy 43:2a7da56ebd24 599 except KeyError:
theotherjimmy 43:2a7da56ebd24 600 pass
theotherjimmy 43:2a7da56ebd24 601 try:
theotherjimmy 43:2a7da56ebd24 602 cur_bss["value"] += dct['.bss']
theotherjimmy 43:2a7da56ebd24 603 cur_bss["delta"] += dct['.bss']
theotherjimmy 43:2a7da56ebd24 604 except KeyError:
theotherjimmy 43:2a7da56ebd24 605 pass
theotherjimmy 43:2a7da56ebd24 606 try:
theotherjimmy 43:2a7da56ebd24 607 cur_data["value"] += dct['.data']
theotherjimmy 43:2a7da56ebd24 608 cur_data["delta"] += dct['.data']
theotherjimmy 43:2a7da56ebd24 609 except KeyError:
theotherjimmy 43:2a7da56ebd24 610 pass
theotherjimmy 43:2a7da56ebd24 611 if not modules:
theotherjimmy 43:2a7da56ebd24 612 break
theotherjimmy 43:2a7da56ebd24 613 next_module = modules.pop(0)
theotherjimmy 43:2a7da56ebd24 614 cur_text = self._move_up_tree(cur_text, next_module)
theotherjimmy 43:2a7da56ebd24 615 cur_data = self._move_up_tree(cur_data, next_module)
theotherjimmy 43:2a7da56ebd24 616 cur_bss = self._move_up_tree(cur_bss, next_module)
theotherjimmy 43:2a7da56ebd24 617 if self.old_modules:
theotherjimmy 43:2a7da56ebd24 618 for name, dct in self.old_modules.items():
theotherjimmy 43:2a7da56ebd24 619 cur_text = tree_text
theotherjimmy 43:2a7da56ebd24 620 cur_bss = tree_bss
theotherjimmy 43:2a7da56ebd24 621 cur_data = tree_data
theotherjimmy 43:2a7da56ebd24 622 modules = name.split(sep)
theotherjimmy 43:2a7da56ebd24 623 while True:
theotherjimmy 43:2a7da56ebd24 624 try:
theotherjimmy 43:2a7da56ebd24 625 cur_text["delta"] -= dct['.text']
theotherjimmy 43:2a7da56ebd24 626 except KeyError:
theotherjimmy 43:2a7da56ebd24 627 pass
theotherjimmy 43:2a7da56ebd24 628 try:
theotherjimmy 43:2a7da56ebd24 629 cur_bss["delta"] -= dct['.bss']
theotherjimmy 43:2a7da56ebd24 630 except KeyError:
theotherjimmy 43:2a7da56ebd24 631 pass
theotherjimmy 43:2a7da56ebd24 632 try:
theotherjimmy 43:2a7da56ebd24 633 cur_data["delta"] -= dct['.data']
theotherjimmy 43:2a7da56ebd24 634 except KeyError:
theotherjimmy 43:2a7da56ebd24 635 pass
theotherjimmy 43:2a7da56ebd24 636 if not modules:
theotherjimmy 43:2a7da56ebd24 637 break
theotherjimmy 43:2a7da56ebd24 638 next_module = modules.pop(0)
theotherjimmy 43:2a7da56ebd24 639 if not any(cld['name'] == next_module for cld in cur_text['children']):
theotherjimmy 43:2a7da56ebd24 640 break
theotherjimmy 43:2a7da56ebd24 641 cur_text = self._move_up_tree(cur_text, next_module)
theotherjimmy 43:2a7da56ebd24 642 cur_data = self._move_up_tree(cur_data, next_module)
theotherjimmy 43:2a7da56ebd24 643 cur_bss = self._move_up_tree(cur_bss, next_module)
theotherjimmy 43:2a7da56ebd24 644
theotherjimmy 43:2a7da56ebd24 645 tree_rom = {
theotherjimmy 43:2a7da56ebd24 646 "name": "ROM",
theotherjimmy 43:2a7da56ebd24 647 "value": tree_text["value"] + tree_data["value"],
theotherjimmy 43:2a7da56ebd24 648 "delta": tree_text["delta"] + tree_data["delta"],
theotherjimmy 43:2a7da56ebd24 649 "children": [tree_text, tree_data]
theotherjimmy 43:2a7da56ebd24 650 }
theotherjimmy 43:2a7da56ebd24 651 tree_ram = {
theotherjimmy 43:2a7da56ebd24 652 "name": "RAM",
theotherjimmy 43:2a7da56ebd24 653 "value": tree_bss["value"] + tree_data["value"],
theotherjimmy 43:2a7da56ebd24 654 "delta": tree_bss["delta"] + tree_data["delta"],
theotherjimmy 43:2a7da56ebd24 655 "children": [tree_bss, tree_data]
theotherjimmy 43:2a7da56ebd24 656 }
theotherjimmy 43:2a7da56ebd24 657
theotherjimmy 43:2a7da56ebd24 658 jinja_loader = FileSystemLoader(dirname(abspath(__file__)))
theotherjimmy 43:2a7da56ebd24 659 jinja_environment = Environment(loader=jinja_loader,
theotherjimmy 43:2a7da56ebd24 660 undefined=StrictUndefined)
theotherjimmy 43:2a7da56ebd24 661
theotherjimmy 43:2a7da56ebd24 662 template = jinja_environment.get_template("memap_flamegraph.html")
theotherjimmy 43:2a7da56ebd24 663 name, _ = splitext(basename(file_desc.name))
theotherjimmy 43:2a7da56ebd24 664 if name.endswith("_map"):
theotherjimmy 43:2a7da56ebd24 665 name = name[:-4]
theotherjimmy 43:2a7da56ebd24 666 if self.tc_name:
theotherjimmy 43:2a7da56ebd24 667 name = "%s %s" % (name, self.tc_name)
theotherjimmy 43:2a7da56ebd24 668 data = {
theotherjimmy 43:2a7da56ebd24 669 "name": name,
theotherjimmy 43:2a7da56ebd24 670 "rom": json.dumps(tree_rom),
theotherjimmy 43:2a7da56ebd24 671 "ram": json.dumps(tree_ram),
theotherjimmy 43:2a7da56ebd24 672 }
theotherjimmy 43:2a7da56ebd24 673 file_desc.write(template.render(data))
theotherjimmy 43:2a7da56ebd24 674 return None
theotherjimmy 43:2a7da56ebd24 675
The Other Jimmy 31:8ea194f6145b 676 def generate_json(self, file_desc):
screamer 29:1210849dba19 677 """Generate a json file from a memory map
screamer 29:1210849dba19 678
screamer 29:1210849dba19 679 Positional arguments:
screamer 29:1210849dba19 680 file_desc - the file to write out the final report to
screamer 29:1210849dba19 681 """
The Other Jimmy 31:8ea194f6145b 682 file_desc.write(json.dumps(self.mem_report, indent=4))
screamer 29:1210849dba19 683 file_desc.write('\n')
theotherjimmy 43:2a7da56ebd24 684 return None
screamer 29:1210849dba19 685
theotherjimmy 43:2a7da56ebd24 686 RAM_FORMAT_STR = (
theotherjimmy 43:2a7da56ebd24 687 "Total Static RAM memory (data + bss): {}({:+}) bytes\n"
theotherjimmy 43:2a7da56ebd24 688 )
theotherjimmy 43:2a7da56ebd24 689
theotherjimmy 43:2a7da56ebd24 690 ROM_FORMAT_STR = (
theotherjimmy 43:2a7da56ebd24 691 "Total Flash memory (text + data): {}({:+}) bytes\n"
theotherjimmy 43:2a7da56ebd24 692 )
The Other Jimmy 31:8ea194f6145b 693
The Other Jimmy 31:8ea194f6145b 694 def generate_csv(self, file_desc):
screamer 29:1210849dba19 695 """Generate a CSV file from a memoy map
screamer 29:1210849dba19 696
screamer 29:1210849dba19 697 Positional arguments:
screamer 29:1210849dba19 698 file_desc - the file to write out the final report to
screamer 29:1210849dba19 699 """
theotherjimmy 43:2a7da56ebd24 700 writer = csv.writer(file_desc, delimiter=',',
theotherjimmy 43:2a7da56ebd24 701 quoting=csv.QUOTE_MINIMAL)
screamer 29:1210849dba19 702
theotherjimmy 43:2a7da56ebd24 703 module_section = []
theotherjimmy 43:2a7da56ebd24 704 sizes = []
theotherjimmy 43:2a7da56ebd24 705 for i in sorted(self.short_modules):
theotherjimmy 43:2a7da56ebd24 706 for k in self.print_sections + self.delta_sections:
theotherjimmy 43:2a7da56ebd24 707 module_section.append((i + k))
theotherjimmy 43:2a7da56ebd24 708 sizes += [self.short_modules[i][k]]
screamer 29:1210849dba19 709
theotherjimmy 43:2a7da56ebd24 710 module_section.append('static_ram')
theotherjimmy 43:2a7da56ebd24 711 sizes.append(self.mem_summary['static_ram'])
screamer 29:1210849dba19 712
theotherjimmy 43:2a7da56ebd24 713 module_section.append('total_flash')
theotherjimmy 43:2a7da56ebd24 714 sizes.append(self.mem_summary['total_flash'])
screamer 29:1210849dba19 715
theotherjimmy 43:2a7da56ebd24 716 writer.writerow(module_section)
theotherjimmy 43:2a7da56ebd24 717 writer.writerow(sizes)
The Other Jimmy 31:8ea194f6145b 718 return None
The Other Jimmy 31:8ea194f6145b 719
The Other Jimmy 31:8ea194f6145b 720 def generate_table(self, file_desc):
screamer 29:1210849dba19 721 """Generate a table from a memoy map
screamer 29:1210849dba19 722
The Other Jimmy 31:8ea194f6145b 723 Returns: string of the generated table
screamer 29:1210849dba19 724 """
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 725 # Create table
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 726 columns = ['Module']
screamer 22:9e85236d8716 727 columns.extend(self.print_sections)
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 728
theotherjimmy 43:2a7da56ebd24 729 table = PrettyTable(columns, junction_char="|", hrules=HEADER)
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 730 table.align["Module"] = "l"
screamer 22:9e85236d8716 731 for col in self.print_sections:
screamer 22:9e85236d8716 732 table.align[col] = 'r'
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 733
screamer 13:ab47a20b66f0 734 for i in list(self.print_sections):
screamer 13:ab47a20b66f0 735 table.align[i] = 'r'
screamer 13:ab47a20b66f0 736
theotherjimmy 40:7d3fa6b99b2b 737 for i in sorted(self.short_modules):
The Other Jimmy 31:8ea194f6145b 738 row = [i]
The Other Jimmy 31:8ea194f6145b 739
The Other Jimmy 31:8ea194f6145b 740 for k in self.print_sections:
theotherjimmy 43:2a7da56ebd24 741 row.append("{}({:+})".format(self.short_modules[i][k],
theotherjimmy 43:2a7da56ebd24 742 self.short_modules[i][k + "-delta"]))
The Other Jimmy 31:8ea194f6145b 743
The Other Jimmy 31:8ea194f6145b 744 table.add_row(row)
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 745
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 746 subtotal_row = ['Subtotals']
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 747 for k in self.print_sections:
theotherjimmy 43:2a7da56ebd24 748 subtotal_row.append("{}({:+})".format(
theotherjimmy 43:2a7da56ebd24 749 self.subtotal[k], self.subtotal[k + '-delta']))
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 750
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 751 table.add_row(subtotal_row)
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 752
The Other Jimmy 31:8ea194f6145b 753 output = table.get_string()
The Other Jimmy 31:8ea194f6145b 754 output += '\n'
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 755
theotherjimmy 43:2a7da56ebd24 756 output += self.RAM_FORMAT_STR.format(
theotherjimmy 43:2a7da56ebd24 757 self.mem_summary['static_ram'],
theotherjimmy 43:2a7da56ebd24 758 self.mem_summary['static_ram_delta']
theotherjimmy 43:2a7da56ebd24 759 )
theotherjimmy 43:2a7da56ebd24 760 output += self.ROM_FORMAT_STR.format(
theotherjimmy 43:2a7da56ebd24 761 self.mem_summary['total_flash'],
theotherjimmy 43:2a7da56ebd24 762 self.mem_summary['total_flash_delta']
theotherjimmy 43:2a7da56ebd24 763 )
The Other Jimmy 31:8ea194f6145b 764
The Other Jimmy 31:8ea194f6145b 765 return output
screamer 22:9e85236d8716 766
The Other Jimmy 36:96847d42f010 767 toolchains = ["ARM", "ARM_STD", "ARM_MICRO", "GCC_ARM", "GCC_CR", "IAR"]
screamer 22:9e85236d8716 768
The Other Jimmy 31:8ea194f6145b 769 def compute_report(self):
theotherjimmy 40:7d3fa6b99b2b 770 """ Generates summary of memory usage for main areas
theotherjimmy 40:7d3fa6b99b2b 771 """
theotherjimmy 43:2a7da56ebd24 772 self.subtotal = defaultdict(int)
The Other Jimmy 31:8ea194f6145b 773
theotherjimmy 43:2a7da56ebd24 774 for mod in self.modules.values():
The Other Jimmy 31:8ea194f6145b 775 for k in self.sections:
theotherjimmy 43:2a7da56ebd24 776 self.subtotal[k] += mod[k]
theotherjimmy 43:2a7da56ebd24 777 self.subtotal[k + '-delta'] += mod[k]
theotherjimmy 43:2a7da56ebd24 778 if self.old_modules:
theotherjimmy 43:2a7da56ebd24 779 for mod in self.old_modules.values():
theotherjimmy 43:2a7da56ebd24 780 for k in self.sections:
theotherjimmy 43:2a7da56ebd24 781 self.subtotal[k + '-delta'] -= mod[k]
The Other Jimmy 31:8ea194f6145b 782
The Other Jimmy 31:8ea194f6145b 783 self.mem_summary = {
theotherjimmy 43:2a7da56ebd24 784 'static_ram': self.subtotal['.data'] + self.subtotal['.bss'],
theotherjimmy 43:2a7da56ebd24 785 'static_ram_delta':
theotherjimmy 43:2a7da56ebd24 786 self.subtotal['.data-delta'] + self.subtotal['.bss-delta'],
theotherjimmy 40:7d3fa6b99b2b 787 'total_flash': (self.subtotal['.text'] + self.subtotal['.data']),
theotherjimmy 43:2a7da56ebd24 788 'total_flash_delta':
theotherjimmy 43:2a7da56ebd24 789 self.subtotal['.text-delta'] + self.subtotal['.data-delta'],
The Other Jimmy 31:8ea194f6145b 790 }
The Other Jimmy 31:8ea194f6145b 791
The Other Jimmy 31:8ea194f6145b 792 self.mem_report = []
theotherjimmy 43:2a7da56ebd24 793 if self.short_modules:
theotherjimmy 43:2a7da56ebd24 794 for name, sizes in sorted(self.short_modules.items()):
theotherjimmy 43:2a7da56ebd24 795 self.mem_report.append({
theotherjimmy 43:2a7da56ebd24 796 "module": name,
theotherjimmy 43:2a7da56ebd24 797 "size":{
theotherjimmy 43:2a7da56ebd24 798 k: sizes.get(k, 0) for k in (self.print_sections +
theotherjimmy 43:2a7da56ebd24 799 self.delta_sections)
theotherjimmy 43:2a7da56ebd24 800 }
theotherjimmy 43:2a7da56ebd24 801 })
The Other Jimmy 31:8ea194f6145b 802
The Other Jimmy 31:8ea194f6145b 803 self.mem_report.append({
The Other Jimmy 31:8ea194f6145b 804 'summary': self.mem_summary
The Other Jimmy 31:8ea194f6145b 805 })
The Other Jimmy 31:8ea194f6145b 806
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 807 def parse(self, mapfile, toolchain):
screamer 29:1210849dba19 808 """ Parse and decode map file depending on the toolchain
screamer 29:1210849dba19 809
screamer 29:1210849dba19 810 Positional arguments:
screamer 29:1210849dba19 811 mapfile - the file name of the memory map file
screamer 29:1210849dba19 812 toolchain - the toolchain used to create the file
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 813 """
theotherjimmy 43:2a7da56ebd24 814 self.tc_name = toolchain.title()
theotherjimmy 43:2a7da56ebd24 815 if toolchain in ("ARM", "ARM_STD", "ARM_MICRO", "ARMC6"):
theotherjimmy 43:2a7da56ebd24 816 parser = _ArmccParser
theotherjimmy 43:2a7da56ebd24 817 elif toolchain == "GCC_ARM" or toolchain == "GCC_CR":
theotherjimmy 43:2a7da56ebd24 818 parser = _GccParser
theotherjimmy 43:2a7da56ebd24 819 elif toolchain == "IAR":
theotherjimmy 43:2a7da56ebd24 820 parser = _IarParser
theotherjimmy 43:2a7da56ebd24 821 else:
theotherjimmy 43:2a7da56ebd24 822 return False
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 823 try:
screamer 29:1210849dba19 824 with open(mapfile, 'r') as file_input:
theotherjimmy 43:2a7da56ebd24 825 self.modules = parser().parse_mapfile(file_input)
theotherjimmy 43:2a7da56ebd24 826 try:
theotherjimmy 43:2a7da56ebd24 827 with open("%s.old" % mapfile, 'r') as old_input:
theotherjimmy 43:2a7da56ebd24 828 self.old_modules = parser().parse_mapfile(old_input)
theotherjimmy 43:2a7da56ebd24 829 except IOError:
theotherjimmy 43:2a7da56ebd24 830 self.old_modules = None
theotherjimmy 43:2a7da56ebd24 831 if not COMPARE_FIXED:
theotherjimmy 43:2a7da56ebd24 832 old_mapfile = "%s.old" % mapfile
theotherjimmy 43:2a7da56ebd24 833 if exists(old_mapfile):
theotherjimmy 43:2a7da56ebd24 834 remove(old_mapfile)
theotherjimmy 43:2a7da56ebd24 835 rename(mapfile, old_mapfile)
theotherjimmy 43:2a7da56ebd24 836 return True
theotherjimmy 40:7d3fa6b99b2b 837
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 838 except IOError as error:
theotherjimmy 43:2a7da56ebd24 839 print("I/O error({0}): {1}".format(error.errno, error.strerror))
theotherjimmy 43:2a7da56ebd24 840 return False
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 841
screamer 8:a8ac6ed29081 842 def main():
screamer 29:1210849dba19 843 """Entry Point"""
theotherjimmy 40:7d3fa6b99b2b 844 version = '0.4.0'
screamer 8:a8ac6ed29081 845
screamer 8:a8ac6ed29081 846 # Parser handling
theotherjimmy 43:2a7da56ebd24 847 parser = ArgumentParser(
screamer 29:1210849dba19 848 description="Memory Map File Analyser for ARM mbed\nversion %s" %
screamer 29:1210849dba19 849 version)
screamer 8:a8ac6ed29081 850
screamer 29:1210849dba19 851 parser.add_argument(
screamer 29:1210849dba19 852 'file', type=argparse_filestring_type, help='memory map file')
screamer 8:a8ac6ed29081 853
screamer 29:1210849dba19 854 parser.add_argument(
screamer 29:1210849dba19 855 '-t', '--toolchain', dest='toolchain',
screamer 29:1210849dba19 856 help='select a toolchain used to build the memory map file (%s)' %
screamer 29:1210849dba19 857 ", ".join(MemapParser.toolchains),
screamer 29:1210849dba19 858 required=True,
screamer 29:1210849dba19 859 type=argparse_uppercase_type(MemapParser.toolchains, "toolchain"))
screamer 8:a8ac6ed29081 860
screamer 29:1210849dba19 861 parser.add_argument(
theotherjimmy 40:7d3fa6b99b2b 862 '-d', '--depth', dest='depth', type=int,
theotherjimmy 40:7d3fa6b99b2b 863 help='specify directory depth level to display report', required=False)
theotherjimmy 40:7d3fa6b99b2b 864
theotherjimmy 40:7d3fa6b99b2b 865 parser.add_argument(
screamer 29:1210849dba19 866 '-o', '--output', help='output file name', required=False)
screamer 8:a8ac6ed29081 867
screamer 29:1210849dba19 868 parser.add_argument(
screamer 29:1210849dba19 869 '-e', '--export', dest='export', required=False, default='table',
screamer 29:1210849dba19 870 type=argparse_lowercase_hyphen_type(MemapParser.export_formats,
screamer 29:1210849dba19 871 'export format'),
screamer 29:1210849dba19 872 help="export format (examples: %s: default)" %
screamer 29:1210849dba19 873 ", ".join(MemapParser.export_formats))
screamer 8:a8ac6ed29081 874
screamer 8:a8ac6ed29081 875 parser.add_argument('-v', '--version', action='version', version=version)
screamer 8:a8ac6ed29081 876
screamer 8:a8ac6ed29081 877 # Parse/run command
theotherjimmy 43:2a7da56ebd24 878 if len(argv) <= 1:
screamer 8:a8ac6ed29081 879 parser.print_help()
theotherjimmy 43:2a7da56ebd24 880 exit(1)
screamer 8:a8ac6ed29081 881
screamer 29:1210849dba19 882 args = parser.parse_args()
screamer 8:a8ac6ed29081 883
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 884 # Create memap object
theotherjimmy 40:7d3fa6b99b2b 885 memap = MemapParser()
screamer 8:a8ac6ed29081 886
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 887 # Parse and decode a map file
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 888 if args.file and args.toolchain:
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 889 if memap.parse(args.file, args.toolchain) is False:
theotherjimmy 43:2a7da56ebd24 890 exit(0)
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 891
theotherjimmy 40:7d3fa6b99b2b 892 if args.depth is None:
theotherjimmy 40:7d3fa6b99b2b 893 depth = 2 # default depth level
theotherjimmy 40:7d3fa6b99b2b 894 else:
theotherjimmy 40:7d3fa6b99b2b 895 depth = args.depth
theotherjimmy 40:7d3fa6b99b2b 896
The Other Jimmy 31:8ea194f6145b 897 returned_string = None
screamer 8:a8ac6ed29081 898 # Write output in file
screamer 8:a8ac6ed29081 899 if args.output != None:
theotherjimmy 40:7d3fa6b99b2b 900 returned_string = memap.generate_output(args.export, \
theotherjimmy 40:7d3fa6b99b2b 901 depth, args.output)
screamer 8:a8ac6ed29081 902 else: # Write output in screen
theotherjimmy 40:7d3fa6b99b2b 903 returned_string = memap.generate_output(args.export, depth)
The Other Jimmy 31:8ea194f6145b 904
The Other Jimmy 31:8ea194f6145b 905 if args.export == 'table' and returned_string:
theotherjimmy 43:2a7da56ebd24 906 print(returned_string)
screamer 8:a8ac6ed29081 907
theotherjimmy 43:2a7da56ebd24 908 exit(0)
screamer 8:a8ac6ed29081 909
screamer 8:a8ac6ed29081 910 if __name__ == "__main__":
Screamer@Y5070-M.virtuoso 9:2d27d77ada5c 911 main()