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