generation.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  1. #
  2. # Copyright 2018-2019 Espressif Systems (Shanghai) PTE LTD
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. #
  16. import collections
  17. import itertools
  18. import os
  19. import fnmatch
  20. from fragments import Sections, Scheme, Mapping, Fragment
  21. from pyparsing import Suppress, White, ParseException, Literal, Regex, Group, ZeroOrMore, Word, OneOrMore, nums, alphanums, alphas, Optional
  22. from common import LdGenFailure
  23. class PlacementRule():
  24. """
  25. Encapsulates a generated placement rule placed under a target
  26. """
  27. DEFAULT_SPECIFICITY = 0
  28. ARCHIVE_SPECIFICITY = 1
  29. OBJECT_SPECIFICITY = 2
  30. SYMBOL_SPECIFICITY = 3
  31. class __container():
  32. def __init__(self, content):
  33. self.content = content
  34. __metadata = collections.namedtuple("__metadata", "excludes expansions expanded")
  35. def __init__(self, archive, obj, symbol, sections, target):
  36. if archive == "*":
  37. archive = None
  38. if obj == "*":
  39. obj = None
  40. self.archive = archive
  41. self.obj = obj
  42. self.symbol = symbol
  43. self.target = target
  44. self.sections = dict()
  45. self.specificity = 0
  46. self.specificity += 1 if self.archive else 0
  47. self.specificity += 1 if (self.obj and not self.obj == '*') else 0
  48. self.specificity += 1 if self.symbol else 0
  49. for section in sections:
  50. section_data = Sections.get_section_data_from_entry(section, self.symbol)
  51. if not self.symbol:
  52. for s in section_data:
  53. metadata = self.__metadata(self.__container([]), self.__container([]), self.__container(False))
  54. self.sections[s] = metadata
  55. else:
  56. (section, expansion) = section_data
  57. if expansion:
  58. metadata = self.__metadata(self.__container([]), self.__container([expansion]), self.__container(True))
  59. self.sections[section] = metadata
  60. def get_section_names(self):
  61. return self.sections.keys()
  62. def add_exclusion(self, other, sections_infos=None):
  63. # Utility functions for this method
  64. def do_section_expansion(rule, section):
  65. if section in rule.get_section_names():
  66. sections_in_obj = sections_infos.get_obj_sections(rule.archive, rule.obj)
  67. expansions = fnmatch.filter(sections_in_obj, section)
  68. return expansions
  69. def remove_section_expansions(rule, section, expansions):
  70. existing_expansions = self.sections[section].expansions.content
  71. self.sections[section].expansions.content = [e for e in existing_expansions if e not in expansions]
  72. # Exit immediately if the exclusion to be added is more general than this rule.
  73. if not other.is_more_specific_rule_of(self):
  74. return
  75. for section in self.get_sections_intersection(other):
  76. if(other.specificity == PlacementRule.SYMBOL_SPECIFICITY):
  77. # If this sections has not been expanded previously, expand now and keep track.
  78. previously_expanded = self.sections[section].expanded.content
  79. if not previously_expanded:
  80. expansions = do_section_expansion(self, section)
  81. if expansions:
  82. self.sections[section].expansions.content = expansions
  83. self.sections[section].expanded.content = True
  84. previously_expanded = True
  85. # Remove the sections corresponding to the symbol name
  86. remove_section_expansions(self, section, other.sections[section].expansions.content)
  87. # If it has been expanded previously but now the expansions list is empty,
  88. # it means adding exclusions has exhausted the list. Remove the section entirely.
  89. if previously_expanded and not self.sections[section].expanded.content:
  90. del self.sections[section]
  91. else:
  92. # A rule section can have multiple rule sections excluded from it. Get the
  93. # most specific rule from the list, and if an even more specific rule is found,
  94. # replace it entirely. Otherwise, keep appending.
  95. exclusions = self.sections[section].excludes
  96. exclusions_list = exclusions.content if exclusions.content is not None else []
  97. exclusions_to_remove = filter(lambda r: r.is_more_specific_rule_of(other), exclusions_list)
  98. remaining_exclusions = [e for e in exclusions_list if e not in exclusions_to_remove]
  99. remaining_exclusions.append(other)
  100. self.sections[section].excludes.content = remaining_exclusions
  101. def get_sections_intersection(self, other):
  102. return set(self.sections.keys()).intersection(set(other.sections.keys()))
  103. def is_more_specific_rule_of(self, other):
  104. if (self.specificity <= other.specificity):
  105. return False
  106. # Compare archive, obj and target
  107. for entity_index in range(1, other.specificity + 1):
  108. if self[entity_index] != other[entity_index] and other[entity_index] is not None:
  109. return False
  110. return True
  111. def maps_same_entities_as(self, other):
  112. if self.specificity != other.specificity:
  113. return False
  114. # Compare archive, obj and target
  115. for entity_index in range(1, other.specificity + 1):
  116. if self[entity_index] != other[entity_index] and other[entity_index] is not None:
  117. return False
  118. return True
  119. def __getitem__(self, key):
  120. if key == PlacementRule.ARCHIVE_SPECIFICITY:
  121. return self.archive
  122. elif key == PlacementRule.OBJECT_SPECIFICITY:
  123. return self.obj
  124. elif key == PlacementRule.SYMBOL_SPECIFICITY:
  125. return self.symbol
  126. else:
  127. return None
  128. def __str__(self):
  129. sorted_sections = sorted(self.get_section_names())
  130. sections_string = list()
  131. for section in sorted_sections:
  132. exclusions = self.sections[section].excludes.content
  133. exclusion_string = None
  134. if exclusions:
  135. exclusion_string = " ".join(map(lambda e: "*" + e.archive + (":" + e.obj + ".*" if e.obj else ""), exclusions))
  136. exclusion_string = "EXCLUDE_FILE(" + exclusion_string + ")"
  137. else:
  138. exclusion_string = ""
  139. section_string = None
  140. exclusion_section_string = None
  141. section_expansions = self.sections[section].expansions.content
  142. section_expanded = self.sections[section].expanded.content
  143. if section_expansions and section_expanded:
  144. section_string = " ".join(section_expansions)
  145. exclusion_section_string = section_string
  146. else:
  147. section_string = section
  148. exclusion_section_string = exclusion_string + " " + section_string
  149. sections_string.append(exclusion_section_string)
  150. sections_string = " ".join(sections_string)
  151. archive = str(self.archive) if self.archive else ""
  152. obj = (str(self.obj) + (".*" if self.obj else "")) if self.obj else ""
  153. # Handle output string generation based on information available
  154. if self.specificity == PlacementRule.DEFAULT_SPECIFICITY:
  155. rule_string = "*(%s)" % (sections_string)
  156. elif self.specificity == PlacementRule.ARCHIVE_SPECIFICITY:
  157. rule_string = "*%s:(%s)" % (archive, sections_string)
  158. else:
  159. rule_string = "*%s:%s(%s)" % (archive, obj, sections_string)
  160. return rule_string
  161. def __eq__(self, other):
  162. if id(self) == id(other):
  163. return True
  164. def exclusions_set(exclusions):
  165. exclusions_set = {(e.archive, e.obj, e.symbol, e.target) for e in exclusions}
  166. return exclusions_set
  167. if self.archive != other.archive:
  168. return False
  169. if self.obj != other.obj:
  170. return False
  171. if self.symbol != other.symbol:
  172. return False
  173. if set(self.sections.keys()) != set(other.sections.keys()):
  174. return False
  175. for (section, metadata) in self.sections.items():
  176. self_meta = metadata
  177. other_meta = other.sections[section]
  178. if exclusions_set(self_meta.excludes.content) != exclusions_set(other_meta.excludes.content):
  179. return False
  180. if set(self_meta.expansions.content) != set(other_meta.expansions.content):
  181. return False
  182. return True
  183. def __ne__(self, other):
  184. return not self.__eq__(other)
  185. def __iter__(self):
  186. yield self.archive
  187. yield self.obj
  188. yield self.symbol
  189. raise StopIteration
  190. class GenerationModel:
  191. """
  192. Implements generation of placement rules based on collected sections, scheme and mapping fragment.
  193. """
  194. DEFAULT_SCHEME = "default"
  195. def __init__(self):
  196. self.schemes = {}
  197. self.sections = {}
  198. self.mappings = {}
  199. def _add_mapping_rules(self, archive, obj, symbol, scheme_name, scheme_dict, rules):
  200. # Use an ordinary dictionary to raise exception on non-existing keys
  201. temp_dict = dict(scheme_dict)
  202. sections_bucket = temp_dict[scheme_name]
  203. for (target, sections) in sections_bucket.items():
  204. section_entries = []
  205. for section in sections:
  206. section_entries.extend(section.entries)
  207. rule = PlacementRule(archive, obj, symbol, section_entries, target)
  208. if rule not in rules:
  209. rules.append(rule)
  210. def _build_scheme_dictionary(self):
  211. scheme_dictionary = collections.defaultdict(dict)
  212. # Collect sections into buckets based on target name
  213. for scheme in self.schemes.values():
  214. sections_bucket = collections.defaultdict(list)
  215. for (sections_name, target_name) in scheme.entries:
  216. # Get the sections under the bucket 'target_name'. If this bucket does not exist
  217. # is is created automatically
  218. sections_in_bucket = sections_bucket[target_name]
  219. try:
  220. sections = self.sections[sections_name]
  221. except KeyError:
  222. message = GenerationException.UNDEFINED_REFERENCE + " to sections '" + sections + "'."
  223. raise GenerationException(message, scheme)
  224. sections_in_bucket.append(sections)
  225. scheme_dictionary[scheme.name] = sections_bucket
  226. # Search for and raise exception on first instance of sections mapped to multiple targets
  227. for (scheme_name, sections_bucket) in scheme_dictionary.items():
  228. for sections_a, sections_b in itertools.combinations(sections_bucket.values(), 2):
  229. set_a = set()
  230. set_b = set()
  231. for sections in sections_a:
  232. set_a.update(sections.entries)
  233. for sections in sections_b:
  234. set_b.update(sections.entries)
  235. intersection = set_a.intersection(set_b)
  236. # If the intersection is a non-empty set, it means sections are mapped to multiple
  237. # targets. Raise exception.
  238. if intersection:
  239. scheme = self.schemes[scheme_name]
  240. message = "Sections " + str(intersection) + " mapped to multiple targets."
  241. raise GenerationException(message, scheme)
  242. return scheme_dictionary
  243. def generate_rules(self, sdkconfig, sections_infos):
  244. placement_rules = collections.defaultdict(list)
  245. scheme_dictionary = self._build_scheme_dictionary()
  246. # Generate default rules
  247. default_rules = list()
  248. self._add_mapping_rules(None, None, None, GenerationModel.DEFAULT_SCHEME, scheme_dictionary, default_rules)
  249. all_mapping_rules = collections.defaultdict(list)
  250. # Generate rules based on mapping fragments
  251. for mapping in self.mappings.values():
  252. for (condition, entries) in mapping.entries:
  253. condition_true = False
  254. # Only non-default condition are evaluated agains sdkconfig model
  255. if condition != Mapping.DEFAULT_CONDITION:
  256. try:
  257. condition_true = sdkconfig.evaluate_expression(condition)
  258. except Exception as e:
  259. raise GenerationException(e.message, mapping)
  260. else:
  261. condition_true = True
  262. if condition_true:
  263. mapping_rules = list()
  264. archive = mapping.archive
  265. for (obj, symbol, scheme_name) in entries:
  266. try:
  267. self._add_mapping_rules(archive, obj, symbol, scheme_name, scheme_dictionary, mapping_rules)
  268. except KeyError:
  269. message = GenerationException.UNDEFINED_REFERENCE + " to scheme '" + scheme_name + "'."
  270. raise GenerationException(message, mapping)
  271. all_mapping_rules[mapping.name] = mapping_rules
  272. break # Exit on first condition that evaluates to true
  273. # Detect rule conflicts
  274. for mapping_rules in all_mapping_rules.items():
  275. self._detect_conflicts(mapping_rules)
  276. # Add exclusions
  277. for mapping_rules in all_mapping_rules.values():
  278. self._create_exclusions(mapping_rules, default_rules, sections_infos)
  279. # Add the default rules grouped by target
  280. for default_rule in default_rules:
  281. existing_rules = placement_rules[default_rule.target]
  282. if default_rule.get_section_names():
  283. existing_rules.append(default_rule)
  284. for mapping_rules in all_mapping_rules.values():
  285. # Add the mapping rules grouped by target
  286. for mapping_rule in mapping_rules:
  287. existing_rules = placement_rules[mapping_rule.target]
  288. if mapping_rule.get_section_names():
  289. existing_rules.append(mapping_rule)
  290. return placement_rules
  291. def _detect_conflicts(self, rules):
  292. (archive, rules_list) = rules
  293. for specificity in range(0, PlacementRule.OBJECT_SPECIFICITY + 1):
  294. rules_with_specificity = filter(lambda r: r.specificity == specificity, rules_list)
  295. for rule_a, rule_b in itertools.combinations(rules_with_specificity, 2):
  296. intersections = rule_a.get_sections_intersection(rule_b)
  297. if intersections and rule_a.maps_same_entities_as(rule_b):
  298. rules_string = str([str(rule_a), str(rule_b)])
  299. message = "Rules " + rules_string + " map sections " + str(list(intersections)) + " into multiple targets."
  300. mapping = self.mappings[Mapping.get_mapping_name_from_archive(archive)]
  301. raise GenerationException(message, mapping)
  302. def _create_extra_rules(self, rules):
  303. # This function generates extra rules for symbol specific rules. The reason for generating extra rules is to isolate,
  304. # as much as possible, rules that require expansion. Particularly, object specific extra rules are generated.
  305. rules_to_process = sorted(rules, key=lambda r: r.specificity)
  306. symbol_specific_rules = list(filter(lambda r: r.specificity == PlacementRule.SYMBOL_SPECIFICITY, rules_to_process))
  307. extra_rules = dict()
  308. for symbol_specific_rule in symbol_specific_rules:
  309. extra_rule_candidate = {s: None for s in symbol_specific_rule.get_section_names()}
  310. super_rules = filter(lambda r: symbol_specific_rule.is_more_specific_rule_of(r), rules_to_process)
  311. # Take a look at the existing rules that are more general than the current symbol-specific rule.
  312. # Only generate an extra rule if there is no existing object specific rule for that section
  313. for super_rule in super_rules:
  314. intersections = symbol_specific_rule.get_sections_intersection(super_rule)
  315. for intersection in intersections:
  316. if super_rule.specificity != PlacementRule.OBJECT_SPECIFICITY:
  317. extra_rule_candidate[intersection] = super_rule
  318. else:
  319. extra_rule_candidate[intersection] = None
  320. # Generate the extra rules for the symbol specific rule section, keeping track of the generated extra rules
  321. for (section, section_rule) in extra_rule_candidate.items():
  322. if section_rule:
  323. extra_rule = None
  324. extra_rules_key = (symbol_specific_rule.archive, symbol_specific_rule.obj, section_rule.target)
  325. try:
  326. extra_rule = extra_rules[extra_rules_key]
  327. if section not in extra_rule.get_section_names():
  328. new_rule = PlacementRule(extra_rule.archive, extra_rule.obj, extra_rule.symbol,
  329. list(extra_rule.get_section_names()) + [section], extra_rule.target)
  330. extra_rules[extra_rules_key] = new_rule
  331. except KeyError:
  332. extra_rule = PlacementRule(symbol_specific_rule.archive, symbol_specific_rule.obj, None, [section], section_rule.target)
  333. extra_rules[extra_rules_key] = extra_rule
  334. return extra_rules.values()
  335. def _create_exclusions(self, mapping_rules, default_rules, sections_info):
  336. rules = list(default_rules)
  337. rules.extend(mapping_rules)
  338. extra_rules = self._create_extra_rules(rules)
  339. mapping_rules.extend(extra_rules)
  340. rules.extend(extra_rules)
  341. # Sort the rules by means of how specific they are. Sort by specificity from lowest to highest
  342. # * -> lib:* -> lib:obj -> lib:obj:symbol
  343. sorted_rules = sorted(rules, key=lambda r: r.specificity)
  344. # Now that the rules have been sorted, loop through each rule, and then loop
  345. # through rules below it (higher indeces), adding exclusions whenever appropriate.
  346. for general_rule in sorted_rules:
  347. for specific_rule in reversed(sorted_rules):
  348. if (specific_rule.specificity > general_rule.specificity and
  349. specific_rule.specificity != PlacementRule.SYMBOL_SPECIFICITY) or \
  350. (specific_rule.specificity == PlacementRule.SYMBOL_SPECIFICITY and
  351. general_rule.specificity == PlacementRule.OBJECT_SPECIFICITY):
  352. general_rule.add_exclusion(specific_rule, sections_info)
  353. def add_fragments_from_file(self, fragment_file):
  354. for fragment in fragment_file.fragments:
  355. dict_to_append_to = None
  356. if isinstance(fragment, Scheme):
  357. dict_to_append_to = self.schemes
  358. elif isinstance(fragment, Sections):
  359. dict_to_append_to = self.sections
  360. else:
  361. dict_to_append_to = self.mappings
  362. # Raise exception when the fragment of the same type is already in the stored fragments
  363. if fragment.name in dict_to_append_to.keys():
  364. stored = dict_to_append_to[fragment.name].path
  365. new = fragment.path
  366. message = "Duplicate definition of fragment '%s' found in %s and %s." % (fragment.name, stored, new)
  367. raise GenerationException(message)
  368. dict_to_append_to[fragment.name] = fragment
  369. class TemplateModel:
  370. """
  371. Encapsulates a linker script template file. Finds marker syntax and handles replacement to generate the
  372. final output.
  373. """
  374. Marker = collections.namedtuple("Marker", "target indent rules")
  375. def __init__(self, template_file):
  376. self.members = []
  377. self.file = os.path.realpath(template_file.name)
  378. self._generate_members(template_file)
  379. def _generate_members(self, template_file):
  380. lines = template_file.readlines()
  381. target = Fragment.IDENTIFIER
  382. reference = Suppress("mapping") + Suppress("[") + target.setResultsName("target") + Suppress("]")
  383. pattern = White(" \t").setResultsName("indent") + reference
  384. # Find the markers in the template file line by line. If line does not match marker grammar,
  385. # set it as a literal to be copied as is to the output file.
  386. for line in lines:
  387. try:
  388. parsed = pattern.parseString(line)
  389. indent = parsed.indent
  390. target = parsed.target
  391. marker = TemplateModel.Marker(target, indent, [])
  392. self.members.append(marker)
  393. except ParseException:
  394. # Does not match marker syntax
  395. self.members.append(line)
  396. def fill(self, mapping_rules, sdkconfig):
  397. for member in self.members:
  398. target = None
  399. try:
  400. target = member.target
  401. rules = member.rules
  402. del rules[:]
  403. rules.extend(mapping_rules[target])
  404. except KeyError:
  405. message = GenerationException.UNDEFINED_REFERENCE + " to target '" + target + "'."
  406. raise GenerationException(message)
  407. except AttributeError:
  408. pass
  409. def write(self, output_file):
  410. # Add information that this is a generated file.
  411. output_file.write("/* Automatically generated file; DO NOT EDIT */\n")
  412. output_file.write("/* Espressif IoT Development Framework Linker Script */\n")
  413. output_file.write("/* Generated from: %s */\n" % self.file)
  414. output_file.write("\n")
  415. # Do the text replacement
  416. for member in self.members:
  417. try:
  418. indent = member.indent
  419. rules = member.rules
  420. for rule in rules:
  421. generated_line = "".join([indent, str(rule), '\n'])
  422. output_file.write(generated_line)
  423. except AttributeError:
  424. output_file.write(member)
  425. class GenerationException(LdGenFailure):
  426. """
  427. Exception for linker script generation failures such as undefined references/ failure to
  428. evaluate conditions, duplicate mappings, etc.
  429. """
  430. UNDEFINED_REFERENCE = "Undefined reference"
  431. def __init__(self, message, fragment=None):
  432. self.fragment = fragment
  433. self.message = message
  434. def __str__(self):
  435. if self.fragment:
  436. return "%s\nIn fragment '%s' defined in '%s'." % (self.message, self.fragment.name, self.fragment.path)
  437. else:
  438. return self.message
  439. class SectionsInfo(dict):
  440. """
  441. Encapsulates an output of objdump. Contains information about the static library sections
  442. and names
  443. """
  444. __info = collections.namedtuple("__info", "filename content")
  445. def __init__(self):
  446. self.sections = dict()
  447. def add_sections_info(self, sections_info_file):
  448. first_line = sections_info_file.readline()
  449. archive_path = Literal("In archive").suppress() + Regex(r"[^:]+").setResultsName("archive_path") + Literal(":").suppress()
  450. parser = archive_path
  451. results = None
  452. try:
  453. results = parser.parseString(first_line)
  454. except ParseException as p:
  455. raise ParseException("File " + sections_info_file.name + " is not a valid sections info file. " + p.message)
  456. archive = os.path.basename(results.archive_path)
  457. self.sections[archive] = SectionsInfo.__info(sections_info_file.name, sections_info_file.read())
  458. def _get_infos_from_file(self, info):
  459. # Object file line: '{object}: file format elf32-xtensa-le'
  460. object = Fragment.ENTITY.setResultsName("object") + Literal(":").suppress() + Literal("file format elf32-xtensa-le").suppress()
  461. # Sections table
  462. header = Suppress(Literal("Sections:") + Literal("Idx") + Literal("Name") + Literal("Size") + Literal("VMA") +
  463. Literal("LMA") + Literal("File off") + Literal("Algn"))
  464. entry = Word(nums).suppress() + Fragment.ENTITY + Suppress(OneOrMore(Word(alphanums, exact=8)) +
  465. Word(nums + "*") + ZeroOrMore(Word(alphas.upper()) +
  466. Optional(Literal(","))))
  467. # Content is object file line + sections table
  468. content = Group(object + header + Group(ZeroOrMore(entry)).setResultsName("sections"))
  469. parser = Group(ZeroOrMore(content)).setResultsName("contents")
  470. sections_info_text = info.content
  471. results = None
  472. try:
  473. results = parser.parseString(sections_info_text)
  474. except ParseException as p:
  475. raise ParseException("Unable to parse section info file " + info.filename + ". " + p.message)
  476. return results
  477. def get_obj_sections(self, archive, obj):
  478. stored = self.sections[archive]
  479. # Parse the contents of the sections file
  480. if not isinstance(stored, dict):
  481. parsed = self._get_infos_from_file(stored)
  482. stored = dict()
  483. for content in parsed.contents:
  484. sections = list(map(lambda s: s, content.sections))
  485. stored[content.object] = sections
  486. self.sections[archive] = stored
  487. for obj_key in stored.keys():
  488. if obj_key == obj + ".o" or obj_key == obj + ".c.obj":
  489. return stored[obj_key]