Anders Blomdell / mbed-sdk-tools
Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers memap.py Source File

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()