1#!/usr/bin/env python 2# 3# esp-idf alternative to "size" to print ELF file sizes, also analyzes 4# the linker map file to dump higher resolution details. 5# 6# Includes information which is not shown in "xtensa-esp32-elf-size", 7# or easy to parse from "xtensa-esp32-elf-objdump" or raw map files. 8# 9# Copyright 2017-2021 Espressif Systems (Shanghai) CO LTD 10# 11# Licensed under the Apache License, Version 2.0 (the "License"); 12# you may not use this file except in compliance with the License. 13# You may obtain a copy of the License at 14# 15# http://www.apache.org/licenses/LICENSE-2.0 16# 17# Unless required by applicable law or agreed to in writing, software 18# distributed under the License is distributed on an "AS IS" BASIS, 19# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20# See the License for the specific language governing permissions and 21# limitations under the License. 22# 23from __future__ import division, print_function, unicode_literals 24 25import argparse 26import collections 27import json 28import os.path 29import re 30import sys 31 32from future.utils import iteritems 33 34GLOBAL_JSON_INDENT = 4 35GLOBAL_JSON_SEPARATORS = (',', ': ') 36 37 38class MemRegions(object): 39 (DRAM_ID, IRAM_ID, DIRAM_ID) = range(3) 40 41 @staticmethod 42 def get_mem_regions(target): 43 # The target specific memory structure is deduced from soc_memory_types defined in 44 # $IDF_PATH/components/soc/**/soc_memory_layout.c files. 45 46 # The order of variables in the tuple is the same as in the soc_memory_layout.c files 47 MemRegDef = collections.namedtuple('MemRegDef', ['primary_addr', 'length', 'type', 'secondary_addr']) 48 49 if target == 'esp32': 50 return sorted([ 51 # Consecutive MemRegDefs of the same type are joined into one MemRegDef 52 MemRegDef(0x3FFAE000, 17 * 0x2000 + 4 * 0x8000 + 4 * 0x4000, MemRegions.DRAM_ID, 0), 53 # MemRegDef(0x3FFAE000, 0x2000, MemRegions.DRAM_ID, 0), 54 # MemRegDef(0x3FFB0000, 0x8000, MemRegions.DRAM_ID, 0), 55 # MemRegDef(0x3FFB8000, 0x8000, MemRegions.DRAM_ID, 0), 56 # MemRegDef(0x3FFC0000, 0x2000, MemRegions.DRAM_ID, 0), 57 # MemRegDef(0x3FFC2000, 0x2000, MemRegions.DRAM_ID, 0), 58 # MemRegDef(0x3FFC4000, 0x2000, MemRegions.DRAM_ID, 0), 59 # MemRegDef(0x3FFC6000, 0x2000, MemRegions.DRAM_ID, 0), 60 # MemRegDef(0x3FFC8000, 0x2000, MemRegions.DRAM_ID, 0), 61 # MemRegDef(0x3FFCA000, 0x2000, MemRegions.DRAM_ID, 0), 62 # MemRegDef(0x3FFCC000, 0x2000, MemRegions.DRAM_ID, 0), 63 # MemRegDef(0x3FFCE000, 0x2000, MemRegions.DRAM_ID, 0), 64 # MemRegDef(0x3FFD0000, 0x2000, MemRegions.DRAM_ID, 0), 65 # MemRegDef(0x3FFD2000, 0x2000, MemRegions.DRAM_ID, 0), 66 # MemRegDef(0x3FFD4000, 0x2000, MemRegions.DRAM_ID, 0), 67 # MemRegDef(0x3FFD6000, 0x2000, MemRegions.DRAM_ID, 0), 68 # MemRegDef(0x3FFD8000, 0x2000, MemRegions.DRAM_ID, 0), 69 # MemRegDef(0x3FFDA000, 0x2000, MemRegions.DRAM_ID, 0), 70 # MemRegDef(0x3FFDC000, 0x2000, MemRegions.DRAM_ID, 0), 71 # MemRegDef(0x3FFDE000, 0x2000, MemRegions.DRAM_ID, 0), 72 # 73 # The bootloader is there and it has to been counted as DRAM 74 # MemRegDef(0x3FFE0000, 0x4000, MemRegions.DIRAM_ID, 0x400BC000), 75 # MemRegDef(0x3FFE4000, 0x4000, MemRegions.DIRAM_ID, 0x400B8000), 76 # MemRegDef(0x3FFE8000, 0x8000, MemRegions.DIRAM_ID, 0x400B0000), 77 # MemRegDef(0x3FFF0000, 0x8000, MemRegions.DIRAM_ID, 0x400A8000), 78 # MemRegDef(0x3FFF8000, 0x4000, MemRegions.DIRAM_ID, 0x400A4000), 79 # MemRegDef(0x3FFFC000, 0x4000, MemRegions.DIRAM_ID, 0x400A0000), 80 # 81 MemRegDef(0x40070000, 2 * 0x8000 + 16 * 0x2000, MemRegions.IRAM_ID, 0), 82 # MemRegDef(0x40070000, 0x8000, MemRegions.IRAM_ID, 0), 83 # MemRegDef(0x40078000, 0x8000, MemRegions.IRAM_ID, 0), 84 # MemRegDef(0x40080000, 0x2000, MemRegions.IRAM_ID, 0), 85 # MemRegDef(0x40082000, 0x2000, MemRegions.IRAM_ID, 0), 86 # MemRegDef(0x40084000, 0x2000, MemRegions.IRAM_ID, 0), 87 # MemRegDef(0x40086000, 0x2000, MemRegions.IRAM_ID, 0), 88 # MemRegDef(0x40088000, 0x2000, MemRegions.IRAM_ID, 0), 89 # MemRegDef(0x4008A000, 0x2000, MemRegions.IRAM_ID, 0), 90 # MemRegDef(0x4008C000, 0x2000, MemRegions.IRAM_ID, 0), 91 # MemRegDef(0x4008E000, 0x2000, MemRegions.IRAM_ID, 0), 92 # MemRegDef(0x40090000, 0x2000, MemRegions.IRAM_ID, 0), 93 # MemRegDef(0x40092000, 0x2000, MemRegions.IRAM_ID, 0), 94 # MemRegDef(0x40094000, 0x2000, MemRegions.IRAM_ID, 0), 95 # MemRegDef(0x40096000, 0x2000, MemRegions.IRAM_ID, 0), 96 # MemRegDef(0x40098000, 0x2000, MemRegions.IRAM_ID, 0), 97 # MemRegDef(0x4009A000, 0x2000, MemRegions.IRAM_ID, 0), 98 # MemRegDef(0x4009C000, 0x2000, MemRegions.IRAM_ID, 0), 99 # MemRegDef(0x4009E000, 0x2000, MemRegions.IRAM_ID, 0), 100 ]) 101 elif target == 'esp32s2': 102 return sorted([ 103 MemRegDef(0x3FFB2000, 3 * 0x2000 + 18 * 0x4000, MemRegions.DIRAM_ID, 0x40022000), 104 # MemRegDef(0x3FFB2000, 0x2000, MemRegions.DIRAM_ID, 0x40022000), 105 # MemRegDef(0x3FFB4000, 0x2000, MemRegions.DIRAM_ID, 0x40024000), 106 # MemRegDef(0x3FFB6000, 0x2000, MemRegions.DIRAM_ID, 0x40026000), 107 # MemRegDef(0x3FFB8000, 0x4000, MemRegions.DIRAM_ID, 0x40028000), 108 # MemRegDef(0x3FFBC000, 0x4000, MemRegions.DIRAM_ID, 0x4002C000), 109 # MemRegDef(0x3FFC0000, 0x4000, MemRegions.DIRAM_ID, 0x40030000), 110 # MemRegDef(0x3FFC4000, 0x4000, MemRegions.DIRAM_ID, 0x40034000), 111 # MemRegDef(0x3FFC8000, 0x4000, MemRegions.DIRAM_ID, 0x40038000), 112 # MemRegDef(0x3FFCC000, 0x4000, MemRegions.DIRAM_ID, 0x4003C000), 113 # MemRegDef(0x3FFD0000, 0x4000, MemRegions.DIRAM_ID, 0x40040000), 114 # MemRegDef(0x3FFD4000, 0x4000, MemRegions.DIRAM_ID, 0x40044000), 115 # MemRegDef(0x3FFD8000, 0x4000, MemRegions.DIRAM_ID, 0x40048000), 116 # MemRegDef(0x3FFDC000, 0x4000, MemRegions.DIRAM_ID, 0x4004C000), 117 # MemRegDef(0x3FFE0000, 0x4000, MemRegions.DIRAM_ID, 0x40050000), 118 # 119 # MemRegDef(0x3FFE4000, 0x4000, MemRegions.DIRAM_ID, 0x40054000), 120 # MemRegDef(0x3FFE8000, 0x4000, MemRegions.DIRAM_ID, 0x40058000), 121 # MemRegDef(0x3FFEC000, 0x4000, MemRegions.DIRAM_ID, 0x4005C000), 122 # MemRegDef(0x3FFF0000, 0x4000, MemRegions.DIRAM_ID, 0x40060000), 123 # MemRegDef(0x3FFF4000, 0x4000, MemRegions.DIRAM_ID, 0x40064000), 124 # MemRegDef(0x3FFF8000, 0x4000, MemRegions.DIRAM_ID, 0x40068000), 125 # MemRegDef(0x3FFFC000, 0x4000, MemRegions.DIRAM_ID, 0x4006C000), 126 ]) 127 elif target == 'esp32s3': 128 return sorted([ 129 MemRegDef(0x3FC88000, 0x8000 + 6 * 0x10000, MemRegions.DIRAM_ID, 0x40378000), 130 ]) 131 elif target == 'esp32c3': 132 return sorted([ 133 MemRegDef(0x3FC80000, 0x60000, MemRegions.DIRAM_ID, 0x40380000), 134 135 # MemRegDef(0x3FC80000, 0x20000, MemRegions.DIRAM_ID, 0x40380000), 136 # MemRegDef(0x3FCA0000, 0x20000, MemRegions.DIRAM_ID, 0x403A0000), 137 # MemRegDef(0x3FCC0000, 0x20000, MemRegions.DIRAM_ID, 0x403C0000), 138 139 # Used by cache 140 MemRegDef(0x4037C000, 0x4000, MemRegions.IRAM_ID, 0), 141 ]) 142 else: 143 return None 144 145 def __init__(self, target): 146 self.chip_mem_regions = self.get_mem_regions(target) 147 if not self.chip_mem_regions: 148 raise RuntimeError('Target {} is not implemented in idf_size'.format(target)) 149 150 def _address_in_range(self, address, length, reg_address, reg_length): 151 return address >= reg_address and (address - reg_address) <= (reg_length - length) 152 153 def get_names(self, dictionary, region_id): 154 def get_address(d): 155 try: 156 return d['address'] 157 except KeyError: 158 return d['origin'] 159 160 def get_size(d): 161 try: 162 return d['size'] 163 except KeyError: 164 return d['length'] 165 166 result = set() # using a set will remove possible duplicates and consequent operations with sets are more 167 # efficient 168 for m in self.chip_mem_regions: 169 if m.type != region_id: 170 continue 171 # the following code is intentionally not a one-liner for better readability 172 for (n, c) in iteritems(dictionary): 173 if (self._address_in_range(get_address(c), get_size(c), m.primary_addr, m.length) or 174 (m.type == self.DIRAM_ID and 175 self._address_in_range(get_address(c), get_size(c), m.secondary_addr, m.length))): 176 result.add(n) 177 return result 178 179 180def scan_to_header(f, header_line): 181 """ Scan forward in a file until you reach 'header_line', then return """ 182 for line in f: 183 if line.strip() == header_line: 184 return 185 raise RuntimeError("Didn't find line '%s' in file" % header_line) 186 187 188def format_json(json_object): 189 return json.dumps(json_object, 190 allow_nan=False, 191 indent=GLOBAL_JSON_INDENT, 192 separators=GLOBAL_JSON_SEPARATORS) + os.linesep 193 194 195def load_map_data(map_file): 196 memory_config = load_memory_config(map_file) 197 detected_chip = detect_target_chip(map_file) 198 sections = load_sections(map_file) 199 return detected_chip, memory_config, sections 200 201 202def load_memory_config(map_file): 203 """ Memory Configuration section is the total size of each output section """ 204 result = {} 205 scan_to_header(map_file, 'Memory Configuration') 206 RE_MEMORY_SECTION = re.compile(r'(?P<name>[^ ]+) +0x(?P<origin>[\da-f]+) +0x(?P<length>[\da-f]+)') 207 208 for line in map_file: 209 m = RE_MEMORY_SECTION.match(line) 210 if m is None: 211 if len(result) == 0: 212 continue # whitespace or a header, before the content we want 213 else: 214 return result # we're at the end of the Memory Configuration 215 section = { 216 'name': m.group('name'), 217 'origin': int(m.group('origin'), 16), 218 'length': int(m.group('length'), 16), 219 } 220 if section['name'] != '*default*': 221 result[section['name']] = section 222 raise RuntimeError('End of file while scanning memory configuration?') 223 224 225def detect_target_chip(map_file): 226 ''' Detect target chip based on the target archive name in the linker script part of the MAP file ''' 227 scan_to_header(map_file, 'Linker script and memory map') 228 229 RE_TARGET = re.compile(r'project_elf_src_(.*)\.c.obj') 230 # For back-compatible with make 231 RE_TARGET_MAKE = re.compile(r'^LOAD .*?/xtensa-([^-]+)-elf/') 232 233 for line in map_file: 234 m = RE_TARGET.search(line) 235 if m: 236 return m.group(1) 237 238 m = RE_TARGET_MAKE.search(line) 239 if m: 240 return m.group(1) 241 242 line = line.strip() 243 # There could be empty line(s) between the "Linker script and memory map" header and "LOAD lines". Therefore, 244 # line stripping and length is checked as well. The "LOAD lines" are between START GROUP and END GROUP for 245 # older MAP files. 246 if not line.startswith(('LOAD', 'START GROUP', 'END GROUP')) and len(line) > 0: 247 # This break is a failsafe to not process anything load_sections() might want to analyze. 248 break 249 250 return None 251 252 253def load_sections(map_file): 254 """ Load section size information from the MAP file. 255 256 Returns a dict of 'sections', where each key is a section name and the value 257 is a dict with details about this section, including a "sources" key which holds a list of source file line 258 information for each symbol linked into the section. 259 """ 260 # output section header, ie '.iram0.text 0x0000000040080400 0x129a5' 261 RE_SECTION_HEADER = re.compile(r'(?P<name>[^ ]+) +0x(?P<address>[\da-f]+) +0x(?P<size>[\da-f]+)$') 262 263 # source file line, ie 264 # 0x0000000040080400 0xa4 /home/gus/esp/32/idf/examples/get-started/hello_world/build/esp32/libesp32.a(cpu_start.o) 265 # cmake build system links some object files directly, not part of any archive, so make that part optional 266 # .xtensa.info 0x0000000000000000 0x38 CMakeFiles/hello-world.elf.dir/project_elf_src.c.obj 267 RE_SOURCE_LINE = re.compile(r'\s*(?P<sym_name>\S*) +0x(?P<address>[\da-f]+) +0x(?P<size>[\da-f]+) (?P<archive>.+\.a)?\(?(?P<object_file>.+\.(o|obj))\)?') 268 269 # Fast check to see if line is a potential source line before running the slower full regex against it 270 RE_PRE_FILTER = re.compile(r'.*\.(o|obj)\)?') 271 272 # Check for lines which only contain the sym name (and rest is on following lines) 273 RE_SYMBOL_ONLY_LINE = re.compile(r'^ (?P<sym_name>\S*)$') 274 275 sections = {} 276 section = None 277 sym_backup = None 278 for line in map_file: 279 280 if line.strip() == 'Cross Reference Table': 281 # stop processing lines because we are at the next section in the map file 282 break 283 284 m = RE_SECTION_HEADER.match(line) 285 if m is not None: # start of a new section 286 section = { 287 'name': m.group('name'), 288 'address': int(m.group('address'), 16), 289 'size': int(m.group('size'), 16), 290 'sources': [], 291 } 292 sections[section['name']] = section 293 continue 294 295 if section is not None: 296 m = RE_SYMBOL_ONLY_LINE.match(line) 297 if m is not None: 298 # In some cases the section name appears on the previous line, back it up in here 299 sym_backup = m.group('sym_name') 300 continue 301 302 if not RE_PRE_FILTER.match(line): 303 # line does not match our quick check, so skip to next line 304 continue 305 306 m = RE_SOURCE_LINE.match(line) 307 if m is not None: # input source file details=ma,e 308 sym_name = m.group('sym_name') if len(m.group('sym_name')) > 0 else sym_backup 309 archive = m.group('archive') 310 if archive is None: 311 # optional named group "archive" was not matched, so assign a value to it 312 archive = '(exe)' 313 314 source = { 315 'size': int(m.group('size'), 16), 316 'address': int(m.group('address'), 16), 317 'archive': os.path.basename(archive), 318 'object_file': os.path.basename(m.group('object_file')), 319 'sym_name': sym_name, 320 } 321 source['file'] = '%s:%s' % (source['archive'], source['object_file']) 322 section['sources'] += [source] 323 324 return sections 325 326 327class MemRegNames(object): 328 329 @staticmethod 330 def get(mem_regions, memory_config, sections): 331 mreg = MemRegNames() 332 mreg.iram_names = mem_regions.get_names(memory_config, MemRegions.IRAM_ID) 333 mreg.dram_names = mem_regions.get_names(memory_config, MemRegions.DRAM_ID) 334 mreg.diram_names = mem_regions.get_names(memory_config, MemRegions.DIRAM_ID) 335 mreg.used_iram_names = mem_regions.get_names(sections, MemRegions.IRAM_ID) 336 mreg.used_dram_names = mem_regions.get_names(sections, MemRegions.DRAM_ID) 337 mreg.used_diram_names = mem_regions.get_names(sections, MemRegions.DIRAM_ID) 338 return mreg 339 340 341def main(): 342 parser = argparse.ArgumentParser(description='idf_size - a tool to print size information from an IDF MAP file') 343 344 parser.add_argument( 345 '--json', 346 help='Output results as JSON', 347 action='store_true') 348 349 parser.add_argument( 350 'map_file', help='MAP file produced by linker', 351 type=argparse.FileType('r')) 352 353 parser.add_argument( 354 '--archives', help='Print per-archive sizes', action='store_true') 355 356 parser.add_argument( 357 '--archive_details', help='Print detailed symbols per archive') 358 359 parser.add_argument( 360 '--files', help='Print per-file sizes', action='store_true') 361 362 parser.add_argument( 363 '--target', help='Set target chip', default=None) 364 365 parser.add_argument( 366 '--diff', help='Show the differences in comparison with another MAP file', 367 metavar='ANOTHER_MAP_FILE', 368 default=None, 369 dest='another_map_file') 370 371 parser.add_argument( 372 '-o', 373 '--output-file', 374 type=argparse.FileType('w'), 375 default=sys.stdout, 376 help='Print output to the specified file instead of stdout') 377 378 args = parser.parse_args() 379 380 detected_target, memory_config, sections = load_map_data(args.map_file) 381 args.map_file.close() 382 383 def check_target(target, map_file): 384 if target is None: 385 raise RuntimeError('The target chip cannot be detected for {}. ' 386 'Please report the issue.'.format(map_file.name)) 387 388 check_target(detected_target, args.map_file) 389 390 if args.target is not None: 391 if args.target != detected_target: 392 print('WARNING: The detected chip target is {} but command line argument overwrites it to ' 393 '{}!'.format(detected_target, args.target)) 394 detected_target = args.target 395 396 if args.another_map_file: 397 with open(args.another_map_file, 'r') as f: 398 detected_target_diff, memory_config_diff, sections_diff = load_map_data(f) 399 check_target(detected_target_diff, f) 400 if detected_target_diff != detected_target: 401 print('WARNING: The target of the reference and other MAP files is {} and {}, respectively.' 402 ''.format(detected_target, detected_target_diff)) 403 else: 404 memory_config_diff, sections_diff = None, None 405 406 mem_regions = MemRegions(detected_target) 407 mem_reg = MemRegNames.get(mem_regions, memory_config, sections) 408 mem_reg_diff = MemRegNames.get(mem_regions, memory_config_diff, sections_diff) if args.another_map_file else None 409 410 output = '' 411 412 if not args.json or not (args.archives or args.files or args.archive_details): 413 output += get_summary(args.map_file.name, mem_reg, memory_config, sections, 414 args.json, 415 args.another_map_file, mem_reg_diff, memory_config_diff, sections_diff) 416 417 if args.archives: 418 output += get_detailed_sizes(mem_reg, sections, 'archive', 'Archive File', args.json, sections_diff) 419 if args.files: 420 output += get_detailed_sizes(mem_reg, sections, 'file', 'Object File', args.json, sections_diff) 421 422 if args.archive_details: 423 output += get_archive_symbols(mem_reg, sections, args.archive_details, args.json, sections_diff) 424 425 args.output_file.write(output) 426 args.output_file.close() 427 428 429class StructureForSummary(object): 430 (dram_data_names, dram_bss_names, dram_other_names, 431 diram_data_names, diram_bss_names) = (frozenset(), ) * 5 432 433 (total_iram, total_dram, total_dram, total_diram, 434 used_dram_data, used_dram_bss, used_dram_other, 435 used_dram, used_dram_ratio, 436 used_iram, used_iram_ratio, 437 used_diram_data, used_diram_bss, 438 used_diram, used_diram_ratio, 439 flash_code, flash_rodata, 440 total_size) = (0, ) * 18 441 442 @staticmethod 443 def get(reg, mem_conf, sects): 444 445 def _get_size(sects, section): 446 try: 447 return sects[section]['size'] 448 except KeyError: 449 return 0 450 451 r = StructureForSummary() 452 453 r.dram_data_names = frozenset([n for n in reg.used_dram_names if n.endswith('.data')]) 454 r.dram_bss_names = frozenset([n for n in reg.used_dram_names if n.endswith('.bss')]) 455 r.dram_other_names = reg.used_dram_names - r.dram_data_names - r.dram_bss_names 456 457 r.diram_data_names = frozenset([n for n in reg.used_diram_names if n.endswith('.data')]) 458 r.diram_bss_names = frozenset([n for n in reg.used_diram_names if n.endswith('.bss')]) 459 460 r.total_iram = sum(mem_conf[n]['length'] for n in reg.iram_names) 461 r.total_dram = sum(mem_conf[n]['length'] for n in reg.dram_names) 462 r.total_diram = sum(mem_conf[n]['length'] for n in reg.diram_names) 463 464 r.used_dram_data = sum(_get_size(sects, n) for n in r.dram_data_names) 465 r.used_dram_bss = sum(_get_size(sects, n) for n in r.dram_bss_names) 466 r.used_dram_other = sum(_get_size(sects, n) for n in r.dram_other_names) 467 r.used_dram = r.used_dram_data + r.used_dram_bss + r.used_dram_other 468 try: 469 r.used_dram_ratio = r.used_dram / r.total_dram 470 except ZeroDivisionError: 471 r.used_dram_ratio = float('nan') 472 473 r.used_iram = sum(_get_size(sects, s) for s in sects if s in reg.used_iram_names) 474 try: 475 r.used_iram_ratio = r.used_iram / r.total_iram 476 except ZeroDivisionError: 477 r.used_iram_ratio = float('nan') 478 479 r.used_diram_data = sum(_get_size(sects, n) for n in r.diram_data_names) 480 r.used_diram_bss = sum(_get_size(sects, n) for n in r.diram_bss_names) 481 r.used_diram = sum(_get_size(sects, n) for n in reg.used_diram_names) 482 try: 483 r.used_diram_ratio = r.used_diram / r.total_diram 484 except ZeroDivisionError: 485 r.used_diram_ratio = float('nan') 486 487 r.flash_code = _get_size(sects, '.flash.text') 488 r.flash_rodata = _get_size(sects, '.flash.rodata') 489 # The used DRAM BSS is counted into the "Used static DRAM" but not into the "Total image size" 490 r.total_size = r.used_dram - r.used_dram_bss + r.used_iram + r.used_diram - r.used_diram_bss + r.flash_code + r.flash_rodata 491 492 return r 493 494 def get_json_dic(self): 495 return collections.OrderedDict([ 496 ('dram_data', self.used_dram_data + self.used_diram_data), 497 ('dram_bss', self.used_dram_bss + self.used_diram_bss), 498 ('dram_other', self.used_dram_other), 499 ('used_dram', self.used_dram), 500 ('available_dram', self.total_dram - self.used_dram), 501 ('used_dram_ratio', self.used_dram_ratio if self.total_dram != 0 else 0), 502 ('used_iram', self.used_iram), 503 ('available_iram', self.total_iram - self.used_iram), 504 ('used_iram_ratio', self.used_iram_ratio if self.total_iram != 0 else 0), 505 ('used_diram', self.used_diram), 506 ('available_diram', self.total_diram - self.used_diram), 507 ('used_diram_ratio', self.used_diram_ratio if self.total_diram != 0 else 0), 508 ('flash_code', self.flash_code), 509 ('flash_rodata', self.flash_rodata), 510 ('total_size', self.total_size) 511 ]) 512 513 514def get_summary(path, mem_reg, memory_config, sections, 515 as_json=False, 516 path_diff=None, mem_reg_diff=None, memory_config_diff=None, sections_diff=None): 517 518 diff_en = mem_reg_diff and memory_config_diff and sections_diff 519 520 current = StructureForSummary.get(mem_reg, memory_config, sections) 521 reference = StructureForSummary.get(mem_reg_diff, 522 memory_config_diff, 523 sections_diff) if diff_en else StructureForSummary() 524 525 if as_json: 526 current_json_dic = current.get_json_dic() 527 if diff_en: 528 reference_json_dic = reference.get_json_dic() 529 diff_json_dic = collections.OrderedDict([(k, 530 v - reference_json_dic[k]) for k, v in iteritems(current_json_dic)]) 531 output = format_json(collections.OrderedDict([('current', current_json_dic), 532 ('reference', reference_json_dic), 533 ('diff', diff_json_dic), 534 ])) 535 else: 536 output = format_json(current_json_dic) 537 else: 538 rows = [] 539 if diff_en: 540 rows += [('<CURRENT> MAP file: {}'.format(path), '', '', '')] 541 rows += [('<REFERENCE> MAP file: {}'.format(path_diff), '', '', '')] 542 rows += [('Difference is counted as <CURRENT> - <REFERENCE>, ' 543 'i.e. a positive number means that <CURRENT> is larger.', 544 '', '', '')] 545 rows += [('Total sizes{}:'.format(' of <CURRENT>' if diff_en else ''), '<REFERENCE>', 'Difference', '')] 546 rows += [(' DRAM .data size: {f_dram_data:>7} bytes', '{f_dram_data_2:>7}', '{f_dram_data_diff:+}', '')] 547 rows += [(' DRAM .bss size: {f_dram_bss:>7} bytes', '{f_dram_bss_2:>7}', '{f_dram_bss_diff:+}', '')] 548 549 if current.used_dram_other > 0 or reference.used_dram_other > 0: 550 diff_list = ['+{}'.format(x) for x in current.dram_other_names - reference.dram_other_names] 551 diff_list += ['-{}'.format(x) for x in reference.dram_other_names - current.dram_other_names] 552 other_diff_str = '' if len(diff_list) == 0 else '({})'.format(', '.join(sorted(diff_list))) 553 rows += [(' DRAM other size: {f_dram_other:>7} bytes ' + '({})'.format(', '.join(current.dram_other_names)), 554 '{f_dram_other_2:>7}', 555 '{f_dram_other_diff:+}', 556 other_diff_str)] 557 558 rows += [('Used static DRAM: {f_used_dram:>7} bytes ({f_dram_avail:>7} available, ' 559 '{f_used_dram_ratio:.1%} used)', 560 '{f_used_dram_2:>7}', 561 '{f_used_dram_diff:+}', 562 '({f_dram_avail_diff:>+7} available, {f_dram_total_diff:>+7} total)')] 563 rows += [('Used static IRAM: {f_used_iram:>7} bytes ({f_iram_avail:>7} available, ' 564 '{f_used_iram_ratio:.1%} used)', 565 '{f_used_iram_2:>7}', 566 '{f_used_iram_diff:+}', 567 '({f_iram_avail_diff:>+7} available, {f_iram_total_diff:>+7} total)')] 568 569 if current.total_diram > 0 or reference.total_diram > 0: 570 rows += [('Used stat D/IRAM: {f_used_diram:>7} bytes ({f_diram_avail:>7} available, ' 571 '{f_used_diram_ratio:.1%} used)', 572 '{f_used_diram_2:>7}', 573 '{f_used_diram_diff:+}', 574 '({f_diram_avail_diff:>+7} available, {f_diram_total_diff:>+7} total)')] 575 576 rows += [(' Flash code: {f_flash_code:>7} bytes', 577 '{f_flash_code_2:>7}', 578 '{f_flash_code_diff:+}', 579 '')] 580 rows += [(' Flash rodata: {f_flash_rodata:>7} bytes', 581 '{f_flash_rodata_2:>7}', 582 '{f_flash_rodata_diff:+}', 583 '')] 584 rows += [('Total image size:~{f_total_size:>7} bytes (.bin may be padded larger)', 585 '{f_total_size_2:>7}', 586 '{f_total_size_diff:+}', 587 '')] 588 589 f_dic = {'f_dram_data': current.used_dram_data + current.used_diram_data, 590 'f_dram_bss': current.used_dram_bss + current.used_diram_bss, 591 'f_dram_other': current.used_dram_other, 592 'f_used_dram': current.used_dram, 593 'f_dram_avail': current.total_dram - current.used_dram, 594 'f_used_dram_ratio': current.used_dram_ratio, 595 'f_used_iram': current.used_iram, 596 'f_iram_avail': current.total_iram - current.used_iram, 597 'f_used_iram_ratio': current.used_iram_ratio, 598 'f_used_diram': current.used_diram, 599 'f_diram_avail': current.total_diram - current.used_diram, 600 'f_used_diram_ratio': current.used_diram_ratio, 601 'f_flash_code': current.flash_code, 602 'f_flash_rodata': current.flash_rodata, 603 'f_total_size': current.total_size, 604 605 'f_dram_data_2': reference.used_dram_data + reference.used_diram_data, 606 'f_dram_bss_2': reference.used_dram_bss + reference.used_diram_bss, 607 'f_dram_other_2': reference.used_dram_other, 608 'f_used_dram_2': reference.used_dram, 609 'f_used_iram_2': reference.used_iram, 610 'f_used_diram_2': reference.used_diram, 611 'f_flash_code_2': reference.flash_code, 612 'f_flash_rodata_2': reference.flash_rodata, 613 'f_total_size_2': reference.total_size, 614 615 'f_dram_total_diff': current.total_dram - reference.total_dram, 616 'f_iram_total_diff': current.total_iram - reference.total_iram, 617 'f_diram_total_diff': current.total_diram - reference.total_diram, 618 619 'f_dram_data_diff': current.used_dram_data + current.used_diram_data - (reference.used_dram_data + 620 reference.used_diram_data), 621 'f_dram_bss_diff': current.used_dram_bss + current.used_diram_bss - (reference.used_dram_bss + 622 reference.used_diram_bss), 623 'f_dram_other_diff': current.used_dram_other - reference.used_dram_other, 624 'f_used_dram_diff': current.used_dram - reference.used_dram, 625 'f_dram_avail_diff': current.total_dram - current.used_dram - (reference.total_dram - 626 reference.used_dram), 627 'f_used_iram_diff': current.used_iram - reference.used_iram, 628 'f_iram_avail_diff': current.total_iram - current.used_iram - (reference.total_iram - 629 reference.used_iram), 630 'f_used_diram_diff': current.used_diram - reference.used_diram, 631 'f_diram_avail_diff': current.total_diram - current.used_diram - (reference.total_diram - 632 reference.used_diram), 633 'f_flash_code_diff': current.flash_code - reference.flash_code, 634 'f_flash_rodata_diff': current.flash_rodata - reference.flash_rodata, 635 'f_total_size_diff': current.total_size - reference.total_size, 636 } 637 638 lf = '{:70}{:>15}{:>15} {}' 639 output = os.linesep.join([lf.format(a.format(**f_dic), 640 b.format(**f_dic) if diff_en else '', 641 c.format(**f_dic) if (diff_en and 642 not c.format(**f_dic).startswith('+0')) else '', 643 d.format(**f_dic) if diff_en else '' 644 ).rstrip() for a, b, c, d in rows]) 645 output += os.linesep # last line need to be terminated because it won't be printed otherwise 646 647 return output 648 649 650class StructureForDetailedSizes(object): 651 652 @staticmethod 653 def sizes_by_key(sections, key): 654 """ Takes a dict of sections (from load_sections) and returns 655 a dict keyed by 'key' with aggregate output size information. 656 657 Key can be either "archive" (for per-archive data) or "file" (for per-file data) in the result. 658 """ 659 result = {} 660 for _, section in iteritems(sections): 661 for s in section['sources']: 662 if not s[key] in result: 663 result[s[key]] = {} 664 archive = result[s[key]] 665 if not section['name'] in archive: 666 archive[section['name']] = 0 667 archive[section['name']] += s['size'] 668 return result 669 670 @staticmethod 671 def get(mem_reg, sections, key): 672 sizes = StructureForDetailedSizes.sizes_by_key(sections, key) 673 674 # these sets are also computed in get_summary() but they are small ones so it should not matter 675 dram_data_names = frozenset([n for n in mem_reg.used_dram_names if n.endswith('.data')]) 676 dram_bss_names = frozenset([n for n in mem_reg.used_dram_names if n.endswith('.bss')]) 677 dram_other_names = mem_reg.used_dram_names - dram_data_names - dram_bss_names 678 679 diram_data_names = frozenset([n for n in mem_reg.used_diram_names if n.endswith('.data')]) 680 diram_bss_names = frozenset([n for n in mem_reg.used_diram_names if n.endswith('.bss')]) 681 682 s = [] 683 for k, v in iteritems(sizes): 684 r = [('data', sum(v.get(n, 0) for n in dram_data_names | diram_data_names)), 685 ('bss', sum(v.get(n, 0) for n in dram_bss_names | diram_bss_names)), 686 ('other', sum(v.get(n, 0) for n in dram_other_names)), 687 ('iram', sum(t for (s,t) in iteritems(v) if s in mem_reg.used_iram_names)), 688 ('diram', sum(t for (s,t) in iteritems(v) if s in mem_reg.used_diram_names)), 689 ('flash_text', v.get('.flash.text', 0)), 690 ('flash_rodata', v.get('.flash.rodata', 0))] 691 r.append(('total', sum([value for _, value in r]))) 692 s.append((k, collections.OrderedDict(r))) 693 694 s = sorted(s, key=lambda elem: elem[0]) 695 # do a secondary sort in order to have consistent order (for diff-ing the output) 696 s = sorted(s, key=lambda elem: elem[1]['total'], reverse=True) 697 698 return collections.OrderedDict(s) 699 700 701def get_detailed_sizes(mem_reg, sections, key, header, as_json=False, sections_diff=None): 702 703 diff_en = sections_diff is not None 704 current = StructureForDetailedSizes.get(mem_reg, sections, key) 705 reference = StructureForDetailedSizes.get(mem_reg, sections_diff, key) if diff_en else {} 706 707 if as_json: 708 if diff_en: 709 diff_json_dic = collections.OrderedDict() 710 for name in sorted(list(frozenset(current.keys()) | frozenset(reference.keys()))): 711 cur_name_dic = current.get(name, {}) 712 ref_name_dic = reference.get(name, {}) 713 all_keys = sorted(list(frozenset(cur_name_dic.keys()) | frozenset(ref_name_dic.keys()))) 714 diff_json_dic[name] = collections.OrderedDict([(k, 715 cur_name_dic.get(k, 0) - 716 ref_name_dic.get(k, 0)) for k in all_keys]) 717 output = format_json(collections.OrderedDict([('current', current), 718 ('reference', reference), 719 ('diff', diff_json_dic), 720 ])) 721 else: 722 output = format_json(current) 723 else: 724 def _get_output(data, selection): 725 header_format = '{:>24} {:>10} {:>6} {:>7} {:>6} {:>8} {:>10} {:>8} {:>7}' + os.linesep 726 output = header_format.format(header, 727 'DRAM .data', 728 '& .bss', 729 '& other', 730 'IRAM', 731 'D/IRAM', 732 'Flash code', 733 '& rodata', 734 'Total') 735 736 for k, v in iteritems(data): 737 if k not in selection: 738 continue 739 740 try: 741 _, k = k.split(':', 1) 742 # print subheadings for key of format archive:file 743 except ValueError: 744 # k remains the same 745 pass 746 747 output += header_format.format(k[:24], 748 v['data'], 749 v['bss'], 750 v['other'], 751 v['iram'], 752 v['diram'], 753 v['flash_text'], 754 v['flash_rodata'], 755 v['total'], 756 ) 757 return output 758 759 def _get_output_diff(curr, ref): 760 header_format = '{:>24}' + ' {:>23}' * 8 761 output = header_format.format(header, 762 'DRAM .data', 763 'DRAM .bss', 764 'DRAM other', 765 'IRAM', 766 'D/IRAM', 767 'Flash code', 768 'Flash rodata', 769 'Total') + os.linesep 770 f_print = ('-' * 23, '') * 4 771 header_line = header_format.format('', *f_print).rstrip() + os.linesep 772 773 header_format = '{:>24}' + '|{:>7}|{:>7}|{:>7}' * 8 774 f_print = ('<C>', '<R>', '<C>-<R>') * 8 775 output += header_format.format('', *f_print) + os.linesep 776 output += header_line 777 778 for k, v in iteritems(curr): 779 try: 780 v2 = ref[k] 781 except KeyError: 782 continue 783 784 try: 785 _, k = k.split(':', 1) 786 # print subheadings for key of format archive:file 787 except ValueError: 788 # k remains the same 789 pass 790 791 def _get_items(name): 792 a = v[name] 793 b = v2[name] 794 diff = a - b 795 # the sign is added here and not in header_format in order to be able to print empty strings 796 return (a or '', b or '', '' if diff == 0 else '{:+}'.format(diff)) 797 798 v_data, v2_data, diff_data = _get_items('data') 799 v_bss, v2_bss, diff_bss = _get_items('bss') 800 v_other, v2_other, diff_other = _get_items('other') 801 v_iram, v2_iram, diff_iram = _get_items('iram') 802 v_diram, v2_diram, diff_diram = _get_items('diram') 803 v_flash_text, v2_flash_text, diff_flash_text = _get_items('flash_text') 804 v_flash_rodata, v2_flash_rodata, diff_flash_rodata = _get_items('flash_rodata') 805 v_total, v2_total, diff_total = _get_items('total') 806 807 output += header_format.format(k[:24], 808 v_data, v2_data, diff_data, 809 v_bss, v2_bss, diff_bss, 810 v_other, v2_other, diff_other, 811 v_iram, v2_iram, diff_iram, 812 v_diram, v2_diram, diff_diram, 813 v_flash_text, v2_flash_text, diff_flash_text, 814 v_flash_rodata, v2_flash_rodata, diff_flash_rodata, 815 v_total, v2_total, diff_total, 816 ).rstrip() + os.linesep 817 return output 818 819 output = 'Per-{} contributions to ELF file:{}'.format(key, os.linesep) 820 821 if diff_en: 822 output += _get_output_diff(current, reference) 823 824 in_current = frozenset(current.keys()) 825 in_reference = frozenset(reference.keys()) 826 only_in_current = in_current - in_reference 827 only_in_reference = in_reference - in_current 828 829 if len(only_in_current) > 0: 830 output += 'The following entries are present in <CURRENT> only:{}'.format(os.linesep) 831 output += _get_output(current, only_in_current) 832 833 if len(only_in_reference) > 0: 834 output += 'The following entries are present in <REFERENCE> only:{}'.format(os.linesep) 835 output += _get_output(reference, only_in_reference) 836 else: 837 output += _get_output(current, current) 838 839 return output 840 841 842class StructureForArchiveSymbols(object): 843 @staticmethod 844 def get(mem_reg, archive, sections): 845 interested_sections = mem_reg.used_dram_names | mem_reg.used_iram_names | mem_reg.used_diram_names 846 interested_sections |= frozenset(['.flash.text', '.flash.rodata']) 847 result = dict([(t, {}) for t in interested_sections]) 848 for _, section in iteritems(sections): 849 section_name = section['name'] 850 if section_name not in interested_sections: 851 continue 852 for s in section['sources']: 853 if archive != s['archive']: 854 continue 855 s['sym_name'] = re.sub('(.text.|.literal.|.data.|.bss.|.rodata.)', '', s['sym_name']) 856 result[section_name][s['sym_name']] = result[section_name].get(s['sym_name'], 0) + s['size'] 857 858 # build a new ordered dict of each section, where each entry is an ordereddict of symbols to sizes 859 section_symbols = collections.OrderedDict() 860 for t in sorted(list(interested_sections)): 861 s = sorted(list(result[t].items()), key=lambda k_v: k_v[0]) 862 # do a secondary sort in order to have consistent order (for diff-ing the output) 863 s = sorted(s, key=lambda k_v: k_v[1], reverse=True) 864 section_symbols[t] = collections.OrderedDict(s) 865 866 return section_symbols 867 868 869def get_archive_symbols(mem_reg, sections, archive, as_json=False, sections_diff=None): 870 diff_en = sections_diff is not None 871 current = StructureForArchiveSymbols.get(mem_reg, archive, sections) 872 reference = StructureForArchiveSymbols.get(mem_reg, archive, sections_diff) if diff_en else {} 873 874 if as_json: 875 if diff_en: 876 diff_json_dic = collections.OrderedDict() 877 for name in sorted(list(frozenset(current.keys()) | frozenset(reference.keys()))): 878 cur_name_dic = current.get(name, {}) 879 ref_name_dic = reference.get(name, {}) 880 all_keys = sorted(list(frozenset(cur_name_dic.keys()) | frozenset(ref_name_dic.keys()))) 881 diff_json_dic[name] = collections.OrderedDict([(key, 882 cur_name_dic.get(key, 0) - 883 ref_name_dic.get(key, 0)) for key in all_keys]) 884 output = format_json(collections.OrderedDict([('current', current), 885 ('reference', reference), 886 ('diff', diff_json_dic), 887 ])) 888 else: 889 output = format_json(current) 890 else: 891 def _get_item_pairs(name, section): 892 return collections.OrderedDict([(key.replace(name + '.', ''), val) for key, val in iteritems(section)]) 893 894 def _get_output(section_symbols): 895 output = '' 896 for t, s in iteritems(section_symbols): 897 output += '{}Symbols from section: {}{}'.format(os.linesep, t, os.linesep) 898 item_pairs = _get_item_pairs(t, s) 899 output += ' '.join(['{}({})'.format(key, val) for key, val in iteritems(item_pairs)]) 900 section_total = sum([val for _, val in iteritems(item_pairs)]) 901 output += '{}Section total: {}{}'.format(os.linesep if section_total > 0 else '', 902 section_total, 903 os.linesep) 904 return output 905 906 output = 'Symbols within the archive: {} (Not all symbols may be reported){}'.format(archive, os.linesep) 907 if diff_en: 908 909 def _generate_line_tuple(curr, ref, name): 910 cur_val = curr.get(name, 0) 911 ref_val = ref.get(name, 0) 912 diff_val = cur_val - ref_val 913 # string slicing is used just to make sure it will fit into the first column of line_format 914 return ((' ' * 4 + name)[:40], cur_val, ref_val, '' if diff_val == 0 else '{:+}'.format(diff_val)) 915 916 line_format = '{:40} {:>12} {:>12} {:>25}' 917 all_section_names = sorted(list(frozenset(current.keys()) | frozenset(reference.keys()))) 918 for section_name in all_section_names: 919 current_item_pairs = _get_item_pairs(section_name, current.get(section_name, {})) 920 reference_item_pairs = _get_item_pairs(section_name, reference.get(section_name, {})) 921 output += os.linesep + line_format.format(section_name[:40], 922 '<CURRENT>', 923 '<REFERENCE>', 924 '<CURRENT> - <REFERENCE>') + os.linesep 925 current_section_total = sum([val for _, val in iteritems(current_item_pairs)]) 926 reference_section_total = sum([val for _, val in iteritems(reference_item_pairs)]) 927 diff_section_total = current_section_total - reference_section_total 928 all_item_names = sorted(list(frozenset(current_item_pairs.keys()) | 929 frozenset(reference_item_pairs.keys()))) 930 output += os.linesep.join([line_format.format(*_generate_line_tuple(current_item_pairs, 931 reference_item_pairs, 932 n) 933 ).rstrip() for n in all_item_names]) 934 output += os.linesep if current_section_total > 0 or reference_section_total > 0 else '' 935 output += line_format.format('Section total:', 936 current_section_total, 937 reference_section_total, 938 '' if diff_section_total == 0 else '{:+}'.format(diff_section_total) 939 ).rstrip() + os.linesep 940 else: 941 output += _get_output(current) 942 return output 943 944 945if __name__ == '__main__': 946 main() 947