Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
memap.py
00001 #!/usr/bin/env python 00002 00003 """Memory Map File Analyser for ARM mbed""" 00004 from __future__ import print_function, division, absolute_import 00005 00006 from abc import abstractmethod, ABCMeta 00007 from sys import stdout, exit, argv, path 00008 from os import sep, rename, remove 00009 from os.path import (basename, dirname, join, relpath, abspath, commonprefix, 00010 splitext, exists) 00011 00012 # Be sure that the tools directory is in the search path 00013 ROOT = abspath(join(dirname(__file__), "..")) 00014 path.insert(0, ROOT) 00015 00016 import re 00017 import csv 00018 import json 00019 from argparse import ArgumentParser 00020 from copy import deepcopy 00021 from collections import defaultdict 00022 from prettytable import PrettyTable, HEADER 00023 from jinja2 import FileSystemLoader, StrictUndefined 00024 from jinja2.environment import Environment 00025 00026 from tools.utils import (argparse_filestring_type, argparse_lowercase_hyphen_type, 00027 argparse_uppercase_type) 00028 from tools.settings import COMPARE_FIXED 00029 00030 00031 class _Parser (object): 00032 """Internal interface for parsing""" 00033 __metaclass__ = ABCMeta 00034 SECTIONS = ('.text', '.data', '.bss', '.heap', '.stack') 00035 MISC_FLASH_SECTIONS = ('.interrupts', '.flash_config') 00036 OTHER_SECTIONS = ('.interrupts_ram', '.init', '.ARM.extab', 00037 '.ARM.exidx', '.ARM.attributes', '.eh_frame', 00038 '.init_array', '.fini_array', '.jcr', '.stab', 00039 '.stabstr', '.ARM.exidx', '.ARM') 00040 00041 def __init__(self): 00042 self.modules = dict() 00043 00044 def module_add (self, object_name, size, section): 00045 """ Adds a module or section to the list 00046 00047 Positional arguments: 00048 object_name - name of the entry to add 00049 size - the size of the module being added 00050 section - the section the module contributes to 00051 """ 00052 if not object_name or not size or not section: 00053 return 00054 00055 if object_name in self.modules : 00056 self.modules [object_name].setdefault(section, 0) 00057 self.modules [object_name][section] += size 00058 return 00059 00060 obj_split = sep + basename(object_name) 00061 for module_path, contents in self.modules .items(): 00062 if module_path.endswith(obj_split) or module_path == object_name: 00063 contents.setdefault(section, 0) 00064 contents[section] += size 00065 return 00066 00067 new_module = defaultdict(int) 00068 new_module[section] = size 00069 self.modules [object_name] = new_module 00070 00071 def module_replace (self, old_object, new_object): 00072 """ Replaces an object name with a new one 00073 """ 00074 if old_object in self.modules : 00075 self.modules [new_object] = self.modules [old_object] 00076 del self.modules [old_object] 00077 00078 @abstractmethod 00079 def parse_mapfile (self, mapfile): 00080 """Parse a given file object pointing to a map file 00081 00082 Positional arguments: 00083 mapfile - an open file object that reads a map file 00084 00085 return value - a dict mapping from object names to section dicts, 00086 where a section dict maps from sections to sizes 00087 """ 00088 raise NotImplemented 00089 00090 00091 class _GccParser(_Parser ): 00092 RE_OBJECT_FILE = re.compile(r'^(.+\/.+\.o)$') 00093 RE_LIBRARY_OBJECT = re.compile(r'^.+' + r''.format(sep) + r'lib((.+\.a)\((.+\.o)\))$') 00094 RE_STD_SECTION = re.compile(r'^\s+.*0x(\w{8,16})\s+0x(\w+)\s(.+)$') 00095 RE_FILL_SECTION = re.compile(r'^\s*\*fill\*\s+0x(\w{8,16})\s+0x(\w+).*$') 00096 00097 ALL_SECTIONS = _Parser.SECTIONS + _Parser.OTHER_SECTIONS + \ 00098 _Parser.MISC_FLASH_SECTIONS + ('unknown', 'OUTPUT') 00099 00100 def check_new_section(self, line): 00101 """ Check whether a new section in a map file has been detected 00102 00103 Positional arguments: 00104 line - the line to check for a new section 00105 00106 return value - A section name, if a new section was found, False 00107 otherwise 00108 """ 00109 for i in self.ALL_SECTIONS: 00110 if line.startswith(i): 00111 # should name of the section (assuming it's a known one) 00112 return i 00113 00114 if line.startswith('.'): 00115 return 'unknown' # all others are classified are unknown 00116 else: 00117 return False # everything else, means no change in section 00118 00119 00120 def parse_object_name(self, line): 00121 """ Parse a path to object file 00122 00123 Positional arguments: 00124 line - the path to parse the object and module name from 00125 00126 return value - an object file name 00127 """ 00128 test_re_mbed_os_name = re.match(self.RE_OBJECT_FILE, line) 00129 00130 if test_re_mbed_os_name: 00131 object_name = test_re_mbed_os_name.group(1) 00132 00133 # corner case: certain objects are provided by the GCC toolchain 00134 if 'arm-none-eabi' in line: 00135 return join('[lib]', 'misc', basename(object_name)) 00136 return object_name 00137 00138 else: 00139 test_re_obj_name = re.match(self.RE_LIBRARY_OBJECT, line) 00140 00141 if test_re_obj_name: 00142 return join('[lib]', test_re_obj_name.group(2), 00143 test_re_obj_name.group(3)) 00144 else: 00145 print("Unknown object name found in GCC map file: %s" % line) 00146 return '[misc]' 00147 00148 def parse_section(self, line): 00149 """ Parse data from a section of gcc map file 00150 00151 examples: 00152 0x00004308 0x7c ./BUILD/K64F/GCC_ARM/mbed-os/hal/targets/hal/TARGET_Freescale/TARGET_KPSDK_MCUS/spi_api.o 00153 .text 0x00000608 0x198 ./BUILD/K64F/GCC_ARM/mbed-os/core/mbed-rtos/rtx/TARGET_CORTEX_M/TARGET_RTOS_M4_M7/TOOLCHAIN/HAL_CM4.o 00154 00155 Positional arguments: 00156 line - the line to parse a section from 00157 """ 00158 is_fill = re.match(self.RE_FILL_SECTION, line) 00159 if is_fill: 00160 o_name = '[fill]' 00161 o_size = int(is_fill.group(2), 16) 00162 return [o_name, o_size] 00163 00164 is_section = re.match(self.RE_STD_SECTION, line) 00165 if is_section: 00166 o_size = int(is_section.group(2), 16) 00167 if o_size: 00168 o_name = self.parse_object_name(is_section.group(3)) 00169 return [o_name, o_size] 00170 00171 return ["", 0] 00172 00173 def parse_mapfile (self, file_desc): 00174 """ Main logic to decode gcc map files 00175 00176 Positional arguments: 00177 file_desc - a stream object to parse as a gcc map file 00178 """ 00179 current_section = 'unknown' 00180 00181 with file_desc as infile: 00182 for line in infile: 00183 if line.startswith('Linker script and memory map'): 00184 current_section = "unknown" 00185 break 00186 00187 for line in infile: 00188 next_section = self.check_new_section(line) 00189 00190 if next_section == "OUTPUT": 00191 break 00192 elif next_section: 00193 current_section = next_section 00194 00195 object_name, object_size = self.parse_section(line) 00196 self.module_add (object_name, object_size, current_section) 00197 00198 common_prefix = dirname(commonprefix([ 00199 o for o in self.modules .keys() if (o.endswith(".o") and not o.startswith("[lib]"))])) 00200 new_modules = {} 00201 for name, stats in self.modules .items(): 00202 if name.startswith("[lib]"): 00203 new_modules[name] = stats 00204 elif name.endswith(".o"): 00205 new_modules[relpath(name, common_prefix)] = stats 00206 else: 00207 new_modules[name] = stats 00208 return new_modules 00209 00210 00211 class _ArmccParser(_Parser ): 00212 RE = re.compile( 00213 r'^\s+0x(\w{8})\s+0x(\w{8})\s+(\w+)\s+(\w+)\s+(\d+)\s+[*]?.+\s+(.+)$') 00214 RE_OBJECT = re.compile(r'(.+\.(l|ar))\((.+\.o)\)') 00215 00216 def parse_object_name(self, line): 00217 """ Parse object file 00218 00219 Positional arguments: 00220 line - the line containing the object or library 00221 """ 00222 if line.endswith(".o"): 00223 return line 00224 00225 else: 00226 is_obj = re.match(self.RE_OBJECT, line) 00227 if is_obj: 00228 return join('[lib]', basename(is_obj.group(1)), is_obj.group(3)) 00229 else: 00230 print("Malformed input found when parsing ARMCC map: %s" % line) 00231 return '[misc]' 00232 00233 def parse_section(self, line): 00234 """ Parse data from an armcc map file 00235 00236 Examples of armcc map file: 00237 Base_Addr Size Type Attr Idx E Section Name Object 00238 0x00000000 0x00000400 Data RO 11222 self.RESET startup_MK64F12.o 00239 0x00000410 0x00000008 Code RO 49364 * !!!main c_w.l(__main.o) 00240 00241 Positional arguments: 00242 line - the line to parse the section data from 00243 """ 00244 test_re = re.match(self.RE, line) 00245 00246 if test_re: 00247 size = int(test_re.group(2), 16) 00248 00249 if test_re.group(4) == 'RO': 00250 section = '.text' 00251 else: 00252 if test_re.group(3) == 'Data': 00253 section = '.data' 00254 elif test_re.group(3) == 'Zero': 00255 section = '.bss' 00256 elif test_re.group(3) == 'Code': 00257 section = '.text' 00258 else: 00259 print("Malformed input found when parsing armcc map: %s, %r" 00260 % (line, test_re.groups())) 00261 00262 return ["", 0, ""] 00263 00264 # check name of object or library 00265 object_name = self.parse_object_name( 00266 test_re.group(6)) 00267 00268 return [object_name, size, section] 00269 00270 else: 00271 return ["", 0, ""] 00272 00273 def parse_mapfile (self, file_desc): 00274 """ Main logic to decode armc5 map files 00275 00276 Positional arguments: 00277 file_desc - a file like object to parse as an armc5 map file 00278 """ 00279 with file_desc as infile: 00280 # Search area to parse 00281 for line in infile: 00282 if line.startswith(' Base Addr Size'): 00283 break 00284 00285 # Start decoding the map file 00286 for line in infile: 00287 self.module_add (*self.parse_section(line)) 00288 00289 common_prefix = dirname(commonprefix([ 00290 o for o in self.modules .keys() if (o.endswith(".o") and o != "anon$$obj.o" and not o.startswith("[lib]"))])) 00291 new_modules = {} 00292 for name, stats in self.modules .items(): 00293 if name == "anon$$obj.o" or name.startswith("[lib]"): 00294 new_modules[name] = stats 00295 elif name.endswith(".o"): 00296 new_modules[relpath(name, common_prefix)] = stats 00297 else: 00298 new_modules[name] = stats 00299 return new_modules 00300 00301 00302 class _IarParser(_Parser ): 00303 RE = re.compile( 00304 r'^\s+(.+)\s+(zero|const|ro code|inited|uninit)\s' 00305 r'+0x(\w{8})\s+0x(\w+)\s+(.+)\s.+$') 00306 00307 RE_CMDLINE_FILE = re.compile(r'^#\s+(.+\.o)') 00308 RE_LIBRARY = re.compile(r'^(.+\.a)\:.+$') 00309 RE_OBJECT_LIBRARY = re.compile(r'^\s+(.+\.o)\s.*') 00310 00311 def __init__(self): 00312 _Parser.__init__(self) 00313 # Modules passed to the linker on the command line 00314 # this is a dict because modules are looked up by their basename 00315 self.cmd_modules = {} 00316 00317 def parse_object_name(self, object_name): 00318 """ Parse object file 00319 00320 Positional arguments: 00321 line - the line containing the object or library 00322 """ 00323 if object_name.endswith(".o"): 00324 try: 00325 return self.cmd_modules[object_name] 00326 except KeyError: 00327 return object_name 00328 else: 00329 return '[misc]' 00330 00331 def parse_section(self, line): 00332 """ Parse data from an IAR map file 00333 00334 Examples of IAR map file: 00335 Section Kind Address Size Object 00336 .intvec ro code 0x00000000 0x198 startup_MK64F12.o [15] 00337 .rodata const 0x00000198 0x0 zero_init3.o [133] 00338 .iar.init_table const 0x00008384 0x2c - Linker created - 00339 Initializer bytes const 0x00000198 0xb2 <for P3 s0> 00340 .data inited 0x20000000 0xd4 driverAtmelRFInterface.o [70] 00341 .bss zero 0x20000598 0x318 RTX_Conf_CM.o [4] 00342 .iar.dynexit uninit 0x20001448 0x204 <Block tail> 00343 HEAP uninit 0x20001650 0x10000 <Block tail> 00344 00345 Positional_arguments: 00346 line - the line to parse section data from 00347 """ 00348 test_re = re.match(self.RE, line) 00349 if test_re: 00350 if (test_re.group(2) == 'const' or 00351 test_re.group(2) == 'ro code'): 00352 section = '.text' 00353 elif (test_re.group(2) == 'zero' or 00354 test_re.group(2) == 'uninit'): 00355 if test_re.group(1)[0:4] == 'HEAP': 00356 section = '.heap' 00357 elif test_re.group(1)[0:6] == 'CSTACK': 00358 section = '.stack' 00359 else: 00360 section = '.bss' # default section 00361 00362 elif test_re.group(2) == 'inited': 00363 section = '.data' 00364 else: 00365 print("Malformed input found when parsing IAR map: %s" % line) 00366 return ["", 0, ""] 00367 00368 # lookup object in dictionary and return module name 00369 object_name = self.parse_object_name(test_re.group(5)) 00370 00371 size = int(test_re.group(4), 16) 00372 return [object_name, size, section] 00373 00374 else: 00375 return ["", 0, ""] 00376 00377 def check_new_library(self, line): 00378 """ 00379 Searches for libraries and returns name. Example: 00380 m7M_tls.a: [43] 00381 00382 """ 00383 test_address_line = re.match(self.RE_LIBRARY, line) 00384 if test_address_line: 00385 return test_address_line.group(1) 00386 else: 00387 return "" 00388 00389 def check_new_object_lib(self, line): 00390 """ 00391 Searches for objects within a library section and returns name. Example: 00392 rt7M_tl.a: [44] 00393 ABImemclr4.o 6 00394 ABImemcpy_unaligned.o 118 00395 ABImemset48.o 50 00396 I64DivMod.o 238 00397 I64DivZer.o 2 00398 00399 """ 00400 test_address_line = re.match(self.RE_OBJECT_LIBRARY, line) 00401 if test_address_line: 00402 return test_address_line.group(1) 00403 else: 00404 return "" 00405 00406 def parse_command_line(self, lines): 00407 """Parse the files passed on the command line to the iar linker 00408 00409 Positional arguments: 00410 lines -- an iterator over the lines within a file 00411 """ 00412 for line in lines: 00413 if line.startswith("*"): 00414 break 00415 for arg in line.split(" "): 00416 arg = arg.rstrip(" \n") 00417 if (not arg.startswith("-")) and arg.endswith(".o"): 00418 self.cmd_modules[basename(arg)] = arg 00419 00420 common_prefix = dirname(commonprefix(list(self.cmd_modules.values()))) 00421 self.cmd_modules = {s: relpath(f, common_prefix) 00422 for s, f in self.cmd_modules.items()} 00423 00424 def parse_mapfile (self, file_desc): 00425 """ Main logic to decode IAR map files 00426 00427 Positional arguments: 00428 file_desc - a file like object to parse as an IAR map file 00429 """ 00430 with file_desc as infile: 00431 self.parse_command_line(infile) 00432 00433 for line in infile: 00434 if line.startswith(' Section '): 00435 break 00436 00437 for line in infile: 00438 self.module_add (*self.parse_section(line)) 00439 00440 if line.startswith('*** MODULE SUMMARY'): # finish section 00441 break 00442 00443 current_library = "" 00444 for line in infile: 00445 library = self.check_new_library(line) 00446 00447 if library: 00448 current_library = library 00449 00450 object_name = self.check_new_object_lib(line) 00451 00452 if object_name and current_library: 00453 temp = join('[lib]', current_library, object_name) 00454 self.module_replace (object_name, temp) 00455 return self.modules 00456 00457 00458 class MemapParser (object): 00459 """An object that represents parsed results, parses the memory map files, 00460 and writes out different file types of memory results 00461 """ 00462 00463 print_sections = ('.text', '.data', '.bss') 00464 delta_sections = ('.text-delta', '.data-delta', '.bss-delta') 00465 00466 00467 # sections to print info (generic for all toolchains) 00468 sections = _Parser.SECTIONS 00469 misc_flash_sections = _Parser.MISC_FLASH_SECTIONS 00470 other_sections = _Parser.OTHER_SECTIONS 00471 00472 def __init__(self): 00473 # list of all modules and their sections 00474 # full list - doesn't change with depth 00475 self.modules = dict() 00476 self.old_modules = None 00477 # short version with specific depth 00478 self.short_modules = dict() 00479 00480 00481 # Memory report (sections + summary) 00482 self.mem_report = [] 00483 00484 # Memory summary 00485 self.mem_summary = dict() 00486 00487 # Totals of ".text", ".data" and ".bss" 00488 self.subtotal = dict() 00489 00490 # Flash no associated with a module 00491 self.misc_flash_mem = 0 00492 00493 # Name of the toolchain, for better headings 00494 self.tc_name = None 00495 00496 def reduce_depth (self, depth): 00497 """ 00498 populates the short_modules attribute with a truncated module list 00499 00500 (1) depth = 1: 00501 main.o 00502 mbed-os 00503 00504 (2) depth = 2: 00505 main.o 00506 mbed-os/test.o 00507 mbed-os/drivers 00508 00509 """ 00510 if depth == 0 or depth == None: 00511 self.short_modules = deepcopy(self.modules ) 00512 else: 00513 self.short_modules = dict() 00514 for module_name, v in self.modules .items(): 00515 split_name = module_name.split(sep) 00516 if split_name[0] == '': 00517 split_name = split_name[1:] 00518 new_name = join(*split_name[:depth]) 00519 self.short_modules .setdefault(new_name, defaultdict(int)) 00520 for section_idx, value in v.items(): 00521 self.short_modules [new_name][section_idx] += self.modules [module_name][section_idx] 00522 self.short_modules [new_name][section_idx + '-delta'] += self.modules [module_name][section_idx] 00523 if self.old_modules : 00524 for module_name, v in self.old_modules .items(): 00525 split_name = module_name.split(sep) 00526 if split_name[0] == '': 00527 split_name = split_name[1:] 00528 new_name = join(*split_name[:depth]) 00529 self.short_modules .setdefault(new_name, defaultdict(int)) 00530 for section_idx, value in v.items(): 00531 self.short_modules [new_name][section_idx + '-delta'] -= self.old_modules [module_name][section_idx] 00532 00533 export_formats = ["json", "csv-ci", "html", "table"] 00534 00535 def generate_output (self, export_format, depth, file_output=None): 00536 """ Generates summary of memory map data 00537 00538 Positional arguments: 00539 export_format - the format to dump 00540 00541 Keyword arguments: 00542 file_desc - descriptor (either stdout or file) 00543 depth - directory depth on report 00544 00545 Returns: generated string for the 'table' format, otherwise None 00546 """ 00547 if depth is None or depth > 0: 00548 self.reduce_depth (depth) 00549 self.compute_report () 00550 try: 00551 if file_output: 00552 file_desc = open(file_output, 'w') 00553 else: 00554 file_desc = stdout 00555 except IOError as error: 00556 print("I/O error({0}): {1}".format(error.errno, error.strerror)) 00557 return False 00558 00559 to_call = {'json': self.generate_json , 00560 'html': self.generate_html , 00561 'csv-ci': self.generate_csv , 00562 'table': self.generate_table }[export_format] 00563 output = to_call(file_desc) 00564 00565 if file_desc is not stdout: 00566 file_desc.close() 00567 00568 return output 00569 00570 @staticmethod 00571 def _move_up_tree(tree, next_module): 00572 tree.setdefault("children", []) 00573 for child in tree["children"]: 00574 if child["name"] == next_module: 00575 return child 00576 else: 00577 new_module = {"name": next_module, "value": 0, "delta": 0} 00578 tree["children"].append(new_module) 00579 return new_module 00580 00581 def generate_html (self, file_desc): 00582 """Generate a json file from a memory map for D3 00583 00584 Positional arguments: 00585 file_desc - the file to write out the final report to 00586 """ 00587 tree_text = {"name": ".text", "value": 0, "delta": 0} 00588 tree_bss = {"name": ".bss", "value": 0, "delta": 0} 00589 tree_data = {"name": ".data", "value": 0, "delta": 0} 00590 for name, dct in self.modules .items(): 00591 cur_text = tree_text 00592 cur_bss = tree_bss 00593 cur_data = tree_data 00594 modules = name.split(sep) 00595 while True: 00596 try: 00597 cur_text["value"] += dct['.text'] 00598 cur_text["delta"] += dct['.text'] 00599 except KeyError: 00600 pass 00601 try: 00602 cur_bss["value"] += dct['.bss'] 00603 cur_bss["delta"] += dct['.bss'] 00604 except KeyError: 00605 pass 00606 try: 00607 cur_data["value"] += dct['.data'] 00608 cur_data["delta"] += dct['.data'] 00609 except KeyError: 00610 pass 00611 if not modules: 00612 break 00613 next_module = modules.pop(0) 00614 cur_text = self._move_up_tree (cur_text, next_module) 00615 cur_data = self._move_up_tree (cur_data, next_module) 00616 cur_bss = self._move_up_tree (cur_bss, next_module) 00617 if self.old_modules : 00618 for name, dct in self.old_modules .items(): 00619 cur_text = tree_text 00620 cur_bss = tree_bss 00621 cur_data = tree_data 00622 modules = name.split(sep) 00623 while True: 00624 try: 00625 cur_text["delta"] -= dct['.text'] 00626 except KeyError: 00627 pass 00628 try: 00629 cur_bss["delta"] -= dct['.bss'] 00630 except KeyError: 00631 pass 00632 try: 00633 cur_data["delta"] -= dct['.data'] 00634 except KeyError: 00635 pass 00636 if not modules: 00637 break 00638 next_module = modules.pop(0) 00639 if not any(cld['name'] == next_module for cld in cur_text['children']): 00640 break 00641 cur_text = self._move_up_tree (cur_text, next_module) 00642 cur_data = self._move_up_tree (cur_data, next_module) 00643 cur_bss = self._move_up_tree (cur_bss, next_module) 00644 00645 tree_rom = { 00646 "name": "ROM", 00647 "value": tree_text["value"] + tree_data["value"], 00648 "delta": tree_text["delta"] + tree_data["delta"], 00649 "children": [tree_text, tree_data] 00650 } 00651 tree_ram = { 00652 "name": "RAM", 00653 "value": tree_bss["value"] + tree_data["value"], 00654 "delta": tree_bss["delta"] + tree_data["delta"], 00655 "children": [tree_bss, tree_data] 00656 } 00657 00658 jinja_loader = FileSystemLoader(dirname(abspath(__file__))) 00659 jinja_environment = Environment(loader=jinja_loader, 00660 undefined=StrictUndefined) 00661 00662 template = jinja_environment.get_template("memap_flamegraph.html") 00663 name, _ = splitext(basename(file_desc.name)) 00664 if name.endswith("_map"): 00665 name = name[:-4] 00666 if self.tc_name : 00667 name = "%s %s" % (name, self.tc_name ) 00668 data = { 00669 "name": name, 00670 "rom": json.dumps(tree_rom), 00671 "ram": json.dumps(tree_ram), 00672 } 00673 file_desc.write(template.render(data)) 00674 return None 00675 00676 def generate_json (self, file_desc): 00677 """Generate a json file from a memory map 00678 00679 Positional arguments: 00680 file_desc - the file to write out the final report to 00681 """ 00682 file_desc.write(json.dumps(self.mem_report , indent=4)) 00683 file_desc.write('\n') 00684 return None 00685 00686 RAM_FORMAT_STR = ( 00687 "Total Static RAM memory (data + bss): {}({:+}) bytes\n" 00688 ) 00689 00690 ROM_FORMAT_STR = ( 00691 "Total Flash memory (text + data): {}({:+}) bytes\n" 00692 ) 00693 00694 def generate_csv (self, file_desc): 00695 """Generate a CSV file from a memoy map 00696 00697 Positional arguments: 00698 file_desc - the file to write out the final report to 00699 """ 00700 writer = csv.writer(file_desc, delimiter=',', 00701 quoting=csv.QUOTE_MINIMAL) 00702 00703 module_section = [] 00704 sizes = [] 00705 for i in sorted(self.short_modules ): 00706 for k in self.print_sections + self.delta_sections : 00707 module_section.append((i + k)) 00708 sizes += [self.short_modules [i][k]] 00709 00710 module_section.append('static_ram') 00711 sizes.append(self.mem_summary ['static_ram']) 00712 00713 module_section.append('total_flash') 00714 sizes.append(self.mem_summary ['total_flash']) 00715 00716 writer.writerow(module_section) 00717 writer.writerow(sizes) 00718 return None 00719 00720 def generate_table (self, file_desc): 00721 """Generate a table from a memoy map 00722 00723 Returns: string of the generated table 00724 """ 00725 # Create table 00726 columns = ['Module'] 00727 columns.extend(self.print_sections ) 00728 00729 table = PrettyTable(columns, junction_char="|", hrules=HEADER) 00730 table.align["Module"] = "l" 00731 for col in self.print_sections : 00732 table.align[col] = 'r' 00733 00734 for i in list(self.print_sections ): 00735 table.align[i] = 'r' 00736 00737 for i in sorted(self.short_modules ): 00738 row = [i] 00739 00740 for k in self.print_sections : 00741 row.append("{}({:+})".format(self.short_modules [i][k], 00742 self.short_modules [i][k + "-delta"])) 00743 00744 table.add_row(row) 00745 00746 subtotal_row = ['Subtotals'] 00747 for k in self.print_sections : 00748 subtotal_row.append("{}({:+})".format( 00749 self.subtotal [k], self.subtotal [k + '-delta'])) 00750 00751 table.add_row(subtotal_row) 00752 00753 output = table.get_string() 00754 output += '\n' 00755 00756 output += self.RAM_FORMAT_STR .format( 00757 self.mem_summary ['static_ram'], 00758 self.mem_summary ['static_ram_delta'] 00759 ) 00760 output += self.ROM_FORMAT_STR .format( 00761 self.mem_summary ['total_flash'], 00762 self.mem_summary ['total_flash_delta'] 00763 ) 00764 00765 return output 00766 00767 toolchains = ["ARM", "ARM_STD", "ARM_MICRO", "GCC_ARM", "GCC_CR", "IAR"] 00768 00769 def compute_report (self): 00770 """ Generates summary of memory usage for main areas 00771 """ 00772 self.subtotal = defaultdict(int) 00773 00774 for mod in self.modules .values(): 00775 for k in self.sections : 00776 self.subtotal [k] += mod[k] 00777 self.subtotal [k + '-delta'] += mod[k] 00778 if self.old_modules : 00779 for mod in self.old_modules .values(): 00780 for k in self.sections : 00781 self.subtotal [k + '-delta'] -= mod[k] 00782 00783 self.mem_summary = { 00784 'static_ram': self.subtotal ['.data'] + self.subtotal ['.bss'], 00785 'static_ram_delta': 00786 self.subtotal ['.data-delta'] + self.subtotal ['.bss-delta'], 00787 'total_flash': (self.subtotal ['.text'] + self.subtotal ['.data']), 00788 'total_flash_delta': 00789 self.subtotal ['.text-delta'] + self.subtotal ['.data-delta'], 00790 } 00791 00792 self.mem_report = [] 00793 if self.short_modules : 00794 for name, sizes in sorted(self.short_modules .items()): 00795 self.mem_report .append({ 00796 "module": name, 00797 "size":{ 00798 k: sizes.get(k, 0) for k in (self.print_sections + 00799 self.delta_sections ) 00800 } 00801 }) 00802 00803 self.mem_report .append({ 00804 'summary': self.mem_summary 00805 }) 00806 00807 def parse (self, mapfile, toolchain): 00808 """ Parse and decode map file depending on the toolchain 00809 00810 Positional arguments: 00811 mapfile - the file name of the memory map file 00812 toolchain - the toolchain used to create the file 00813 """ 00814 self.tc_name = toolchain.title() 00815 if toolchain in ("ARM", "ARM_STD", "ARM_MICRO", "ARMC6"): 00816 parser = _ArmccParser 00817 elif toolchain == "GCC_ARM" or toolchain == "GCC_CR": 00818 parser = _GccParser 00819 elif toolchain == "IAR": 00820 parser = _IarParser 00821 else: 00822 return False 00823 try: 00824 with open(mapfile, 'r') as file_input: 00825 self.modules = parser().parse_mapfile(file_input) 00826 try: 00827 with open("%s.old" % mapfile, 'r') as old_input: 00828 self.old_modules = parser().parse_mapfile(old_input) 00829 except IOError: 00830 self.old_modules = None 00831 if not COMPARE_FIXED: 00832 old_mapfile = "%s.old" % mapfile 00833 if exists(old_mapfile): 00834 remove(old_mapfile) 00835 rename(mapfile, old_mapfile) 00836 return True 00837 00838 except IOError as error: 00839 print("I/O error({0}): {1}".format(error.errno, error.strerror)) 00840 return False 00841 00842 def main(): 00843 """Entry Point""" 00844 version = '0.4.0' 00845 00846 # Parser handling 00847 parser = ArgumentParser( 00848 description="Memory Map File Analyser for ARM mbed\nversion %s" % 00849 version) 00850 00851 parser.add_argument( 00852 'file', type=argparse_filestring_type, help='memory map file') 00853 00854 parser.add_argument( 00855 '-t', '--toolchain', dest='toolchain', 00856 help='select a toolchain used to build the memory map file (%s)' % 00857 ", ".join(MemapParser.toolchains), 00858 required=True, 00859 type=argparse_uppercase_type(MemapParser.toolchains, "toolchain")) 00860 00861 parser.add_argument( 00862 '-d', '--depth', dest='depth', type=int, 00863 help='specify directory depth level to display report', required=False) 00864 00865 parser.add_argument( 00866 '-o', '--output', help='output file name', required=False) 00867 00868 parser.add_argument( 00869 '-e', '--export', dest='export', required=False, default='table', 00870 type=argparse_lowercase_hyphen_type(MemapParser.export_formats, 00871 'export format'), 00872 help="export format (examples: %s: default)" % 00873 ", ".join(MemapParser.export_formats)) 00874 00875 parser.add_argument('-v', '--version', action='version', version=version) 00876 00877 # Parse/run command 00878 if len(argv) <= 1: 00879 parser.print_help() 00880 exit(1) 00881 00882 args = parser.parse_args() 00883 00884 # Create memap object 00885 memap = MemapParser() 00886 00887 # Parse and decode a map file 00888 if args.file and args.toolchain: 00889 if memap.parse(args.file, args.toolchain) is False: 00890 exit(0) 00891 00892 if args.depth is None: 00893 depth = 2 # default depth level 00894 else: 00895 depth = args.depth 00896 00897 returned_string = None 00898 # Write output in file 00899 if args.output != None: 00900 returned_string = memap.generate_output(args.export, \ 00901 depth, args.output) 00902 else: # Write output in screen 00903 returned_string = memap.generate_output(args.export, depth) 00904 00905 if args.export == 'table' and returned_string: 00906 print(returned_string) 00907 00908 exit(0) 00909 00910 if __name__ == "__main__": 00911 main()
Generated on Tue Jul 12 2022 17:12:47 by
