idf_size.py 47 KB

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