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.
Dependents: mbed-TFT-example-NCS36510 mbed-Accelerometer-example-NCS36510 mbed-Accelerometer-example-NCS36510
memap.py
00001 #!/usr/bin/env python 00002 00003 """Memory Map File Analyser for ARM mbed""" 00004 00005 import sys 00006 import os 00007 import re 00008 import csv 00009 import json 00010 import argparse 00011 from prettytable import PrettyTable 00012 00013 from utils import argparse_filestring_type, \ 00014 argparse_lowercase_hyphen_type, argparse_uppercase_type 00015 00016 DEBUG = False 00017 00018 RE_ARMCC = re.compile( 00019 r'^\s+0x(\w{8})\s+0x(\w{8})\s+(\w+)\s+(\w+)\s+(\d+)\s+[*]?.+\s+(.+)$') 00020 RE_IAR = re.compile( 00021 r'^\s+(.+)\s+(zero|const|ro code|inited|uninit)\s' 00022 r'+0x(\w{8})\s+0x(\w+)\s+(.+)\s.+$') 00023 00024 class MemapParser (object): 00025 """An object that represents parsed results, parses the memory map files, 00026 and writes out different file types of memory results 00027 """ 00028 00029 print_sections = ('.text', '.data', '.bss') 00030 00031 misc_flash_sections = ('.interrupts', '.flash_config') 00032 00033 other_sections = ('.interrupts_ram', '.init', '.ARM.extab', 00034 '.ARM.exidx', '.ARM.attributes', '.eh_frame', 00035 '.init_array', '.fini_array', '.jcr', '.stab', 00036 '.stabstr', '.ARM.exidx', '.ARM') 00037 00038 # sections to print info (generic for all toolchains) 00039 sections = ('.text', '.data', '.bss', '.heap', '.stack') 00040 00041 def __init__ (self, detailed_misc=False): 00042 """ General initialization 00043 """ 00044 # 00045 self.detailed_misc = detailed_misc 00046 00047 # list of all modules and their sections 00048 self.modules = dict() 00049 00050 # sections must be defined in this order to take irrelevant out 00051 self.all_sections = self.sections + self.other_sections + \ 00052 self.misc_flash_sections + ('unknown', 'OUTPUT') 00053 00054 # list of all object files and mappting to module names 00055 self.object_to_module = dict() 00056 00057 # Memory report (sections + summary) 00058 self.mem_report = [] 00059 00060 # Just the memory summary section 00061 self.mem_summary = dict() 00062 00063 self.subtotal = dict() 00064 00065 def module_add (self, module_name, size, section): 00066 """ Adds a module / section to the list 00067 00068 Positional arguments: 00069 module_name - name of the module to add 00070 size - the size of the module being added 00071 section - the section the module contributes to 00072 """ 00073 00074 if module_name in self.modules : 00075 self.modules [module_name][section] += size 00076 else: 00077 temp_dic = dict() 00078 for section_idx in self.all_sections : 00079 temp_dic[section_idx] = 0 00080 temp_dic[section] = size 00081 self.modules [module_name] = temp_dic 00082 00083 def check_new_section_gcc (self, line): 00084 """ Check whether a new section in a map file has been detected (only 00085 applies to gcc) 00086 00087 Positional arguments: 00088 line - the line to check for a new section 00089 """ 00090 00091 for i in self.all_sections : 00092 if line.startswith(i): 00093 # should name of the section (assuming it's a known one) 00094 return i 00095 00096 if line.startswith('.'): 00097 return 'unknown' # all others are classified are unknown 00098 else: 00099 return False # everything else, means no change in section 00100 00101 00102 def path_object_to_module_name (self, txt): 00103 """ Parse a path to object file to extract it's module and object data 00104 00105 Positional arguments: 00106 txt - the path to parse the object and module name from 00107 """ 00108 00109 txt = txt.replace('\\', '/') 00110 rex_mbed_os_name = r'^.+mbed-os\/(.+)\/(.+\.o)$' 00111 test_rex_mbed_os_name = re.match(rex_mbed_os_name, txt) 00112 00113 if test_rex_mbed_os_name: 00114 00115 object_name = test_rex_mbed_os_name.group(2) 00116 data = test_rex_mbed_os_name.group(1).split('/') 00117 ndata = len(data) 00118 00119 if ndata == 1: 00120 module_name = data[0] 00121 else: 00122 module_name = data[0] + '/' + data[1] 00123 00124 return [module_name, object_name] 00125 00126 elif self.detailed_misc : 00127 rex_obj_name = r'^.+\/(.+\.o\)*)$' 00128 test_rex_obj_name = re.match(rex_obj_name, txt) 00129 if test_rex_obj_name: 00130 object_name = test_rex_obj_name.group(1) 00131 return ['Misc/' + object_name, ""] 00132 00133 return ['Misc', ""] 00134 else: 00135 return ['Misc', ""] 00136 00137 def parse_section_gcc (self, line): 00138 """ Parse data from a section of gcc map file 00139 00140 examples: 00141 0x00004308 0x7c ./BUILD/K64F/GCC_ARM/mbed-os/hal/targets/hal/TARGET_Freescale/TARGET_KPSDK_MCUS/spi_api.o 00142 .text 0x00000608 0x198 ./BUILD/K64F/GCC_ARM/mbed-os/core/mbed-rtos/rtx/TARGET_CORTEX_M/TARGET_RTOS_M4_M7/TOOLCHAIN_GCC/HAL_CM4.o 00143 00144 Positional arguments: 00145 line - the line to parse a section from 00146 """ 00147 rex_address_len_name = re.compile( 00148 r'^\s+.*0x(\w{8,16})\s+0x(\w+)\s(.+)$') 00149 00150 test_address_len_name = re.match(rex_address_len_name, line) 00151 00152 if test_address_len_name: 00153 00154 if int(test_address_len_name.group(2), 16) == 0: # size == 0 00155 return ["", 0] # no valid entry 00156 else: 00157 m_name, _ = self.path_object_to_module_name ( 00158 test_address_len_name.group(3)) 00159 m_size = int(test_address_len_name.group(2), 16) 00160 return [m_name, m_size] 00161 00162 else: # special corner case for *fill* sections 00163 # example 00164 # *fill* 0x0000abe4 0x4 00165 rex_address_len = r'^\s+\*fill\*\s+0x(\w{8,16})\s+0x(\w+).*$' 00166 test_address_len = re.match(rex_address_len, line) 00167 00168 if test_address_len: 00169 if int(test_address_len.group(2), 16) == 0: # size == 0 00170 return ["", 0] # no valid entry 00171 else: 00172 m_name = 'Fill' 00173 m_size = int(test_address_len.group(2), 16) 00174 return [m_name, m_size] 00175 else: 00176 return ["", 0] # no valid entry 00177 00178 def parse_map_file_gcc (self, file_desc): 00179 """ Main logic to decode gcc map files 00180 00181 Positional arguments: 00182 file_desc - a stream object to parse as a gcc map file 00183 """ 00184 00185 current_section = 'unknown' 00186 00187 with file_desc as infile: 00188 00189 # Search area to parse 00190 for line in infile: 00191 if line.startswith('Linker script and memory map'): 00192 current_section = "unknown" 00193 break 00194 00195 # Start decoding the map file 00196 for line in infile: 00197 00198 change_section = self.check_new_section_gcc (line) 00199 00200 if change_section == "OUTPUT": # finish parsing file: exit 00201 break 00202 elif change_section != False: 00203 current_section = change_section 00204 00205 [module_name, module_size] = self.parse_section_gcc (line) 00206 00207 if module_size == 0 or module_name == "": 00208 pass 00209 else: 00210 self.module_add (module_name, module_size, current_section) 00211 00212 if DEBUG: 00213 print "Line: %s" % line, 00214 print "Module: %s\tSection: %s\tSize: %s" % \ 00215 (module_name, current_section, module_size) 00216 raw_input("----------") 00217 00218 def parse_section_armcc (self, line): 00219 """ Parse data from an armcc map file 00220 00221 Examples of armcc map file: 00222 Base_Addr Size Type Attr Idx E Section Name Object 00223 0x00000000 0x00000400 Data RO 11222 RESET startup_MK64F12.o 00224 0x00000410 0x00000008 Code RO 49364 * !!!main c_w.l(__main.o) 00225 00226 Positional arguments: 00227 line - the line to parse the section data from 00228 """ 00229 00230 test_rex_armcc = re.match(RE_ARMCC, line) 00231 00232 if test_rex_armcc: 00233 00234 size = int(test_rex_armcc.group(2), 16) 00235 00236 if test_rex_armcc.group(4) == 'RO': 00237 section = '.text' 00238 else: 00239 if test_rex_armcc.group(3) == 'Data': 00240 section = '.data' 00241 elif test_rex_armcc.group(3) == 'Zero': 00242 section = '.bss' 00243 else: 00244 print "BUG armcc map parser" 00245 raw_input() 00246 00247 # lookup object in dictionary and return module name 00248 object_name = test_rex_armcc.group(6) 00249 if object_name in self.object_to_module : 00250 module_name = self.object_to_module [object_name] 00251 else: 00252 module_name = 'Misc' 00253 00254 return [module_name, size, section] 00255 00256 else: 00257 return ["", 0, ""] # no valid entry 00258 00259 def parse_section_iar (self, line): 00260 """ Parse data from an IAR map file 00261 00262 Examples of IAR map file: 00263 Section Kind Address Size Object 00264 .intvec ro code 0x00000000 0x198 startup_MK64F12.o [15] 00265 .rodata const 0x00000198 0x0 zero_init3.o [133] 00266 .iar.init_table const 0x00008384 0x2c - Linker created - 00267 Initializer bytes const 0x00000198 0xb2 <for P3 s0> 00268 .data inited 0x20000000 0xd4 driverAtmelRFInterface.o [70] 00269 .bss zero 0x20000598 0x318 RTX_Conf_CM.o [4] 00270 .iar.dynexit uninit 0x20001448 0x204 <Block tail> 00271 HEAP uninit 0x20001650 0x10000 <Block tail> 00272 00273 Positional_arguments: 00274 line - the line to parse section data from 00275 """ 00276 00277 test_rex_iar = re.match(RE_IAR, line) 00278 00279 if test_rex_iar: 00280 00281 size = int(test_rex_iar.group(4), 16) 00282 00283 if test_rex_iar.group(2) == 'const' or \ 00284 test_rex_iar.group(2) == 'ro code': 00285 section = '.text' 00286 elif test_rex_iar.group(2) == 'zero' or \ 00287 test_rex_iar.group(2) == 'uninit': 00288 if test_rex_iar.group(1)[0:4] == 'HEAP': 00289 section = '.heap' 00290 elif test_rex_iar.group(1)[0:6] == 'CSTACK': 00291 section = '.stack' 00292 else: 00293 section = '.bss' # default section 00294 00295 elif test_rex_iar.group(2) == 'inited': 00296 section = '.data' 00297 else: 00298 print "BUG IAR map parser" 00299 raw_input() 00300 00301 # lookup object in dictionary and return module name 00302 object_name = test_rex_iar.group(5) 00303 if object_name in self.object_to_module : 00304 module_name = self.object_to_module [object_name] 00305 else: 00306 module_name = 'Misc' 00307 00308 return [module_name, size, section] 00309 00310 else: 00311 return ["", 0, ""] # no valid entry 00312 00313 def parse_map_file_armcc (self, file_desc): 00314 """ Main logic to decode armc5 map files 00315 00316 Positional arguments: 00317 file_desc - a file like object to parse as an armc5 map file 00318 """ 00319 00320 with file_desc as infile: 00321 00322 # Search area to parse 00323 for line in infile: 00324 if line.startswith(' Base Addr Size'): 00325 break 00326 00327 # Start decoding the map file 00328 for line in infile: 00329 00330 [name, size, section] = self.parse_section_armcc (line) 00331 00332 if size == 0 or name == "" or section == "": 00333 pass 00334 else: 00335 self.module_add (name, size, section) 00336 00337 def parse_map_file_iar (self, file_desc): 00338 """ Main logic to decode IAR map files 00339 00340 Positional arguments: 00341 file_desc - a file like object to parse as an IAR map file 00342 """ 00343 00344 with file_desc as infile: 00345 00346 # Search area to parse 00347 for line in infile: 00348 if line.startswith(' Section '): 00349 break 00350 00351 # Start decoding the map file 00352 for line in infile: 00353 00354 [name, size, section] = self.parse_section_iar (line) 00355 00356 if size == 0 or name == "" or section == "": 00357 pass 00358 else: 00359 self.module_add (name, size, section) 00360 00361 def search_objects (self, path): 00362 """ Searches for object files and creates mapping: object --> module 00363 00364 Positional arguments: 00365 path - the path to an object file 00366 """ 00367 00368 path = path.replace('\\', '/') 00369 00370 # check location of map file 00371 rex = r'^(.+)' + r'\/(.+\.map)$' 00372 test_rex = re.match(rex, path) 00373 00374 if test_rex: 00375 search_path = test_rex.group(1) + '/mbed-os/' 00376 else: 00377 print "Warning: this doesn't look like an mbed project" 00378 return 00379 00380 for root, _, obj_files in os.walk(search_path): 00381 for obj_file in obj_files: 00382 if obj_file.endswith(".o"): 00383 module_name, object_name = self.path_object_to_module_name ( 00384 os.path.join(root, obj_file)) 00385 00386 if object_name in self.object_to_module : 00387 if DEBUG: 00388 print "WARNING: multiple usages of object file: %s"\ 00389 % object_name 00390 print " Current: %s" % \ 00391 self.object_to_module [object_name] 00392 print " New: %s" % module_name 00393 print " " 00394 else: 00395 self.object_to_module .update({object_name:module_name}) 00396 00397 export_formats = ["json", "csv-ci", "table"] 00398 00399 def generate_output (self, export_format, file_output=None): 00400 """ Generates summary of memory map data 00401 00402 Positional arguments: 00403 export_format - the format to dump 00404 00405 Keyword arguments: 00406 file_desc - descriptor (either stdout or file) 00407 00408 Returns: generated string for the 'table' format, otherwise None 00409 """ 00410 00411 try: 00412 if file_output: 00413 file_desc = open(file_output, 'wb') 00414 else: 00415 file_desc = sys.stdout 00416 except IOError as error: 00417 print "I/O error({0}): {1}".format(error.errno, error.strerror) 00418 return False 00419 00420 to_call = {'json': self.generate_json , 00421 'csv-ci': self.generate_csv , 00422 'table': self.generate_table }[export_format] 00423 output = to_call(file_desc) 00424 00425 if file_desc is not sys.stdout: 00426 file_desc.close() 00427 00428 return output 00429 00430 def generate_json (self, file_desc): 00431 """Generate a json file from a memory map 00432 00433 Positional arguments: 00434 file_desc - the file to write out the final report to 00435 """ 00436 file_desc.write(json.dumps(self.mem_report , indent=4)) 00437 file_desc.write('\n') 00438 00439 return None 00440 00441 def generate_csv (self, file_desc): 00442 """Generate a CSV file from a memoy map 00443 00444 Positional arguments: 00445 file_desc - the file to write out the final report to 00446 """ 00447 csv_writer = csv.writer(file_desc, delimiter=',', 00448 quoting=csv.QUOTE_MINIMAL) 00449 00450 csv_module_section = [] 00451 csv_sizes = [] 00452 for i in sorted(self.modules ): 00453 for k in self.print_sections : 00454 csv_module_section += [i+k] 00455 csv_sizes += [self.modules [i][k]] 00456 00457 csv_module_section += ['static_ram'] 00458 csv_sizes += [self.mem_summary ['static_ram']] 00459 00460 csv_module_section += ['heap'] 00461 if self.mem_summary ['heap'] == 0: 00462 csv_sizes += ['unknown'] 00463 else: 00464 csv_sizes += [self.mem_summary ['heap']] 00465 00466 csv_module_section += ['stack'] 00467 if self.mem_summary ['stack'] == 0: 00468 csv_sizes += ['unknown'] 00469 else: 00470 csv_sizes += [self.mem_summary ['stack']] 00471 00472 csv_module_section += ['total_ram'] 00473 csv_sizes += [self.mem_summary ['total_ram']] 00474 00475 csv_module_section += ['total_flash'] 00476 csv_sizes += [self.mem_summary ['total_flash']] 00477 00478 csv_writer.writerow(csv_module_section) 00479 csv_writer.writerow(csv_sizes) 00480 00481 return None 00482 00483 def generate_table (self, file_desc): 00484 """Generate a table from a memoy map 00485 00486 Positional arguments: 00487 file_desc - the file to write out the final report to 00488 00489 Returns: string of the generated table 00490 """ 00491 # Create table 00492 columns = ['Module'] 00493 columns.extend(self.print_sections ) 00494 00495 table = PrettyTable(columns) 00496 table.align["Module"] = "l" 00497 for col in self.print_sections : 00498 table.align[col] = 'r' 00499 00500 for i in list(self.print_sections ): 00501 table.align[i] = 'r' 00502 00503 for i in sorted(self.modules ): 00504 row = [i] 00505 00506 for k in self.print_sections : 00507 row.append(self.modules [i][k]) 00508 00509 table.add_row(row) 00510 00511 subtotal_row = ['Subtotals'] 00512 for k in self.print_sections : 00513 subtotal_row.append(self.subtotal [k]) 00514 00515 table.add_row(subtotal_row) 00516 00517 output = table.get_string() 00518 output += '\n' 00519 00520 if self.mem_summary ['heap'] == 0: 00521 output += "Allocated Heap: unknown\n" 00522 else: 00523 output += "Allocated Heap: %s bytes\n" % \ 00524 str(self.mem_summary ['heap']) 00525 00526 if self.mem_summary ['stack'] == 0: 00527 output += "Allocated Stack: unknown\n" 00528 else: 00529 output += "Allocated Stack: %s bytes\n" % \ 00530 str(self.mem_summary ['stack']) 00531 00532 output += "Total Static RAM memory (data + bss): %s bytes\n" % \ 00533 str(self.mem_summary ['static_ram']) 00534 output += "Total RAM memory (data + bss + heap + stack): %s bytes\n" % \ 00535 str(self.mem_summary ['total_ram']) 00536 output += "Total Flash memory (text + data + misc): %s bytes\n" % \ 00537 str(self.mem_summary ['total_flash']) 00538 00539 return output 00540 00541 toolchains = ["ARM", "ARM_STD", "ARM_MICRO", "GCC_ARM", "IAR"] 00542 00543 def compute_report(self): 00544 for k in self.sections : 00545 self.subtotal [k] = 0 00546 00547 for i in sorted(self.modules ): 00548 for k in self.sections : 00549 self.subtotal [k] += self.modules [i][k] 00550 00551 # Calculate misc flash sections 00552 self.misc_flash_mem = 0 00553 for i in self.modules : 00554 for k in self.misc_flash_sections : 00555 if self.modules [i][k]: 00556 self.misc_flash_mem += self.modules [i][k] 00557 00558 self.mem_summary = { 00559 'static_ram': (self.subtotal ['.data'] + self.subtotal ['.bss']), 00560 'heap': (self.subtotal ['.heap']), 00561 'stack': (self.subtotal ['.stack']), 00562 'total_ram': (self.subtotal ['.data'] + self.subtotal ['.bss'] + 00563 self.subtotal ['.heap']+self.subtotal ['.stack']), 00564 'total_flash': (self.subtotal ['.text'] + self.subtotal ['.data'] + 00565 self.misc_flash_mem ), 00566 } 00567 00568 self.mem_report = [] 00569 for i in sorted(self.modules ): 00570 self.mem_report .append({ 00571 "module":i, 00572 "size":{ 00573 k:self.modules [i][k] for k in self.print_sections 00574 } 00575 }) 00576 00577 self.mem_report .append({ 00578 'summary': self.mem_summary 00579 }) 00580 00581 def parse (self, mapfile, toolchain): 00582 """ Parse and decode map file depending on the toolchain 00583 00584 Positional arguments: 00585 mapfile - the file name of the memory map file 00586 toolchain - the toolchain used to create the file 00587 """ 00588 00589 result = True 00590 try: 00591 with open(mapfile, 'r') as file_input: 00592 if toolchain == "ARM" or toolchain == "ARM_STD" or\ 00593 toolchain == "ARM_MICRO": 00594 self.search_objects (os.path.abspath(mapfile)) 00595 self.parse_map_file_armcc (file_input) 00596 elif toolchain == "GCC_ARM": 00597 self.parse_map_file_gcc (file_input) 00598 elif toolchain == "IAR": 00599 self.search_objects (os.path.abspath(mapfile)) 00600 self.parse_map_file_iar (file_input) 00601 else: 00602 result = False 00603 00604 self.compute_report () 00605 00606 except IOError as error: 00607 print "I/O error({0}): {1}".format(error.errno, error.strerror) 00608 result = False 00609 return result 00610 00611 def main(): 00612 """Entry Point""" 00613 00614 version = '0.3.12' 00615 00616 # Parser handling 00617 parser = argparse.ArgumentParser( 00618 description="Memory Map File Analyser for ARM mbed\nversion %s" % 00619 version) 00620 00621 parser.add_argument( 00622 'file', type=argparse_filestring_type, help='memory map file') 00623 00624 parser.add_argument( 00625 '-t', '--toolchain', dest='toolchain', 00626 help='select a toolchain used to build the memory map file (%s)' % 00627 ", ".join(MemapParser.toolchains), 00628 required=True, 00629 type=argparse_uppercase_type(MemapParser.toolchains, "toolchain")) 00630 00631 parser.add_argument( 00632 '-o', '--output', help='output file name', required=False) 00633 00634 parser.add_argument( 00635 '-e', '--export', dest='export', required=False, default='table', 00636 type=argparse_lowercase_hyphen_type(MemapParser.export_formats, 00637 'export format'), 00638 help="export format (examples: %s: default)" % 00639 ", ".join(MemapParser.export_formats)) 00640 00641 parser.add_argument('-v', '--version', action='version', version=version) 00642 00643 parser.add_argument('-d', '--detailed', action='store_true', help='Displays the elements in "Misc" in a detailed fashion', required=False) 00644 00645 # Parse/run command 00646 if len(sys.argv) <= 1: 00647 parser.print_help() 00648 sys.exit(1) 00649 00650 00651 args = parser.parse_args() 00652 00653 # Create memap object 00654 memap = MemapParser(detailed_misc=args.detailed) 00655 00656 # Parse and decode a map file 00657 if args.file and args.toolchain: 00658 if memap.parse(args.file, args.toolchain) is False: 00659 sys.exit(0) 00660 00661 returned_string = None 00662 # Write output in file 00663 if args.output != None: 00664 returned_string = memap.generate_output(args.export, args.output) 00665 else: # Write output in screen 00666 returned_string = memap.generate_output(args.export) 00667 00668 if args.export == 'table' and returned_string: 00669 print returned_string 00670 00671 sys.exit(0) 00672 00673 if __name__ == "__main__": 00674 main()
Generated on Tue Jul 12 2022 11:02:47 by
