From a41a56b5b0f6cf1e777f17602e5acd799842f0bc Mon Sep 17 00:00:00 2001 From: Renz Bagaporo Date: Wed, 27 Jan 2021 16:00:19 +0800 Subject: [PATCH 1/4] ldgen: refactor generation internals --- tools/ldgen/entity.py | 222 ++++++++++ tools/ldgen/generation.py | 759 ++++++++++++----------------------- tools/ldgen/ldgen.py | 10 +- tools/ldgen/linker_script.py | 95 +++++ 4 files changed, 582 insertions(+), 504 deletions(-) create mode 100644 tools/ldgen/entity.py create mode 100644 tools/ldgen/linker_script.py diff --git a/tools/ldgen/entity.py b/tools/ldgen/entity.py new file mode 100644 index 0000000000..38e526b460 --- /dev/null +++ b/tools/ldgen/entity.py @@ -0,0 +1,222 @@ +# +# Copyright 2018-2019 Espressif Systems (Shanghai) PTE LTD +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import collections +import fnmatch +import os +from enum import Enum +from functools import total_ordering + +from pyparsing import (Group, Literal, OneOrMore, ParseException, SkipTo, Suppress, White, Word, ZeroOrMore, alphas, + nums, restOfLine) + + +@total_ordering +class Entity(): + """ + Definition of an entity which can be placed or excluded + from placement. + """ + + ALL = '*' + + class Specificity(Enum): + NONE = 0 + ARCHIVE = 1 + OBJ = 2 + SYMBOL = 3 + + def __init__(self, archive=None, obj=None, symbol=None): + archive_spec = archive and archive != Entity.ALL + obj_spec = obj and obj != Entity.ALL + symbol_spec = symbol and symbol != Entity.ALL + + if not archive_spec and not obj_spec and not symbol_spec: + self.specificity = Entity.Specificity.NONE + elif archive_spec and not obj_spec and not symbol_spec: + self.specificity = Entity.Specificity.ARCHIVE + elif archive_spec and obj_spec and not symbol_spec: + self.specificity = Entity.Specificity.OBJ + elif archive_spec and obj_spec and symbol_spec: + self.specificity = Entity.Specificity.SYMBOL + else: + raise ValueError("Invalid arguments '(%s, %s, %s)'" % (archive, obj, symbol)) + + self.archive = archive + self.obj = obj + self.symbol = symbol + + def __eq__(self, other): + return (self.specificity.value == other.specificity.value and + self.archive == other.archive and + self.obj == other.obj and + self.symbol == other.symbol) + + def __lt__(self, other): + res = False + if self.specificity.value < other.specificity.value: + res = True + elif self.specificity == other.specificity: + for s in Entity.Specificity: + a = self[s] if self[s] else '' + b = other[s] if other[s] else '' + + if a != b: + res = a < b + break + else: + res = False + return res + + def __hash__(self): + return hash(self.__repr__()) + + def __str__(self): + return '%s:%s %s' % self.__repr__() + + def __repr__(self): + return (self.archive, self.obj, self.symbol) + + def __getitem__(self, spec): + res = None + if spec == Entity.Specificity.ARCHIVE: + res = self.archive + elif spec == Entity.Specificity.OBJ: + res = self.obj + elif spec == Entity.Specificity.SYMBOL: + res = self.symbol + else: + res = None + return res + + +class EntityDB(): + """ + Encapsulates an output of objdump. Contains information about the static library sections + and names + """ + + __info = collections.namedtuple('__info', 'filename content') + + def __init__(self): + self.sections = dict() + + def add_sections_info(self, sections_info_dump): + first_line = sections_info_dump.readline() + + archive_path = (Literal('In archive').suppress() + + White().suppress() + + # trim the colon and line ending characters from archive_path + restOfLine.setResultsName('archive_path').setParseAction(lambda s, loc, toks: s.rstrip(':\n\r '))) + parser = archive_path + + results = None + + try: + results = parser.parseString(first_line, parseAll=True) + except ParseException as p: + raise ParseException('Parsing sections info for library ' + sections_info_dump.name + ' failed. ' + p.msg) + + archive = os.path.basename(results.archive_path) + self.sections[archive] = EntityDB.__info(sections_info_dump.name, sections_info_dump.read()) + + def _get_infos_from_file(self, info): + # {object}: file format elf32-xtensa-le + object_line = SkipTo(':').setResultsName('object') + Suppress(restOfLine) + + # Sections: + # Idx Name ... + section_start = Suppress(Literal('Sections:')) + section_header = Suppress(OneOrMore(Word(alphas))) + + # 00 {section} 0000000 ... + # CONTENTS, ALLOC, .... + section_entry = Suppress(Word(nums)) + SkipTo(' ') + Suppress(restOfLine) + \ + Suppress(ZeroOrMore(Word(alphas) + Literal(',')) + Word(alphas)) + + content = Group(object_line + section_start + section_header + Group(OneOrMore(section_entry)).setResultsName('sections')) + parser = Group(ZeroOrMore(content)).setResultsName('contents') + + results = None + + try: + results = parser.parseString(info.content, parseAll=True) + except ParseException as p: + raise ParseException('Unable to parse section info file ' + info.filename + '. ' + p.msg) + + return results + + def _process_archive(self, archive): + stored = self.sections[archive] + + # Parse the contents of the sections file on-demand, + # save the result for later + if not isinstance(stored, dict): + parsed = self._get_infos_from_file(stored) + stored = dict() + for content in parsed.contents: + sections = list(map(lambda s: s, content.sections)) + stored[content.object] = sections + self.sections[archive] = stored + + def get_archives(self): + return self.sections.keys() + + def get_objects(self, archive): + try: + self._process_archive(archive) + except KeyError: + return [] + + return self.sections[archive].keys() + + def _match_obj(self, archive, obj): + objs = self.get_objects(archive) + match_objs = fnmatch.filter(objs, obj + '.o') + fnmatch.filter(objs, obj + '.*.obj') + fnmatch.filter(objs, obj + '.obj') + + if len(match_objs) > 1: + raise ValueError("Multiple matches for object: '%s: %s': %s" % (archive, obj, str(match_objs))) + + try: + return match_objs[0] + except IndexError: + return None + + def get_sections(self, archive, obj): + obj = self._match_obj(archive, obj) + res = [] + if obj: + res = self.sections[archive][obj] + return res + + def _match_symbol(self, archive, obj, symbol): + sections = self.get_sections(archive, obj) + return [s for s in sections if s.endswith(symbol)] + + def check_exists(self, entity): + res = True + + if entity.specificity != Entity.Specificity.NONE: + if entity.specificity == Entity.Specificity.ARCHIVE: + res = entity.archive in self.get_archives() + elif entity.specificity == Entity.Specificity.OBJ: + res = self._match_obj(entity.archive, entity.obj) is not None + elif entity.specificity == Entity.Specificity.SYMBOL: + res = len(self._match_symbol(entity.archive, entity.obj, entity.symbol)) > 0 + else: + res = False + + return res diff --git a/tools/ldgen/generation.py b/tools/ldgen/generation.py index a2d0267fb6..d27f40e442 100644 --- a/tools/ldgen/generation.py +++ b/tools/ldgen/generation.py @@ -17,242 +17,256 @@ import collections import fnmatch import itertools -import os +from collections import namedtuple -from fragments import Fragment, Mapping, Scheme, Sections +from entity import Entity +from fragments import Mapping, Scheme, Sections from ldgen_common import LdGenFailure -from pyparsing import (Group, Literal, OneOrMore, ParseException, SkipTo, Suppress, White, Word, ZeroOrMore, alphas, - nums, restOfLine) +from output_commands import InputSectionDesc -class PlacementRule(): - """ - Encapsulates a generated placement rule placed under a target - """ +class RuleNode(): - DEFAULT_SPECIFICITY = 0 - ARCHIVE_SPECIFICITY = 1 - OBJECT_SPECIFICITY = 2 - SYMBOL_SPECIFICITY = 3 + class Section(): - class __container(): - def __init__(self, content): - self.content = content + def __init__(self, target, exclusions, explicit=False): + self.target = target + self.exclusions = set(exclusions) - __metadata = collections.namedtuple('__metadata', 'excludes expansions expanded') + # Indicate whether this node has been created explicitly from a mapping, + # or simply just to create a path to the explicitly created node. + # + # For example, + # + # lib.a + # obj:sym (scheme) + # + # Nodes for lib.a and obj will be created, but only the node for + # sym will have been created explicitly. + # + # This is used in deciding whether or not an output command should + # be emitted for this node, or for exclusion rule generation. + self.explicit = explicit - def __init__(self, archive, obj, symbol, sections, target): - if archive == '*': - archive = None + def __init__(self, parent, name, sections): + self.children = [] + self.parent = parent + self.name = name + self.child_node = None + self.entity = None - if obj == '*': - obj = None - - self.archive = archive - self.obj = obj - self.symbol = symbol - self.target = target self.sections = dict() - self.specificity = 0 - self.specificity += 1 if self.archive else 0 - self.specificity += 1 if (self.obj and not self.obj == '*') else 0 - self.specificity += 1 if self.symbol else 0 + # A node inherits the section -> target entries from + # its parent. This is to simplify logic, avoiding + # going up the parental chain to try a 'basis' rule + # in creating exclusions. This relies on the fact that + # the mappings must be inserted from least to most specific. + # This sort is done in generate_rules(). + if sections: + for (s, v) in sections.items(): + self.sections[s] = RuleNode.Section(v.target, [], []) - for section in sections: - section_data = Sections.get_section_data_from_entry(section, self.symbol) + def add_exclusion(self, sections, exclusion): + self.sections[sections].exclusions.add(exclusion) - if not self.symbol: - for s in section_data: - metadata = self.__metadata(self.__container([]), self.__container([]), self.__container(False)) - self.sections[s] = metadata + # Recursively create exclusions in parents + if self.parent: + self.exclude_from_parent(sections) + + def add_sections(self, sections, target): + try: + _sections = self.sections[sections] + if not _sections.explicit: + _sections.target = target + _sections.explicit = True else: - (section, expansion) = section_data - if expansion: - metadata = self.__metadata(self.__container([]), self.__container([expansion]), self.__container(True)) - self.sections[section] = metadata + if target != _sections.target: + raise GenerationException('Sections mapped to multiple targets') + except KeyError: + self.sections[sections] = RuleNode.Section(target, [], True) - def get_section_names(self): - return self.sections.keys() + def exclude_from_parent(self, sections): + self.parent.add_exclusion(sections, self.entity) - def add_exclusion(self, other, sections_infos=None): - # Utility functions for this method - def do_section_expansion(rule, section): - if section in rule.get_section_names(): - sections_in_obj = sections_infos.get_obj_sections(rule.archive, rule.obj) - expansions = fnmatch.filter(sections_in_obj, section) - return expansions + def add_child(self, entity): + child_specificity = self.entity.specificity.value + 1 + assert(child_specificity <= Entity.Specificity.SYMBOL.value) + name = entity[Entity.Specificity(child_specificity)] + assert(name and name != Entity.ALL) - def remove_section_expansions(rule, section, expansions): - existing_expansions = self.sections[section].expansions.content - self.sections[section].expansions.content = [e for e in existing_expansions if e not in expansions] + child = [c for c in self.children if c.name == name] + assert(len(child) <= 1) - # Exit immediately if the exclusion to be added is more general than this rule. - if not other.is_more_specific_rule_of(self): - return - - for section in self.get_sections_intersection(other): - if(other.specificity == PlacementRule.SYMBOL_SPECIFICITY): - # If this sections has not been expanded previously, expand now and keep track. - previously_expanded = self.sections[section].expanded.content - if not previously_expanded: - expansions = do_section_expansion(self, section) - if expansions: - self.sections[section].expansions.content = expansions - self.sections[section].expanded.content = True - previously_expanded = True - - # Remove the sections corresponding to the symbol name - remove_section_expansions(self, section, other.sections[section].expansions.content) - - # If it has been expanded previously but now the expansions list is empty, - # it means adding exclusions has exhausted the list. Remove the section entirely. - if previously_expanded and not self.sections[section].expanded.content: - del self.sections[section] - else: - # A rule section can have multiple rule sections excluded from it. Get the - # most specific rule from the list, and if an even more specific rule is found, - # replace it entirely. Otherwise, keep appending. - exclusions = self.sections[section].excludes - exclusions_list = exclusions.content if exclusions.content is not None else [] - exclusions_to_remove = filter(lambda r: r.is_more_specific_rule_of(other), exclusions_list) - - remaining_exclusions = [e for e in exclusions_list if e not in exclusions_to_remove] - remaining_exclusions.append(other) - - self.sections[section].excludes.content = remaining_exclusions - - def get_sections_intersection(self, other): - return set(self.sections.keys()).intersection(set(other.sections.keys())) - - def is_more_specific_rule_of(self, other): - if (self.specificity <= other.specificity): - return False - - # Compare archive, obj and target - for entity_index in range(1, other.specificity + 1): - if self[entity_index] != other[entity_index] and other[entity_index] is not None: - return False - - return True - - def maps_same_entities_as(self, other): - if self.specificity != other.specificity: - return False - - # Compare archive, obj and target - for entity_index in range(1, other.specificity + 1): - if self[entity_index] != other[entity_index] and other[entity_index] is not None: - return False - - return True - - def __getitem__(self, key): - if key == PlacementRule.ARCHIVE_SPECIFICITY: - return self.archive - elif key == PlacementRule.OBJECT_SPECIFICITY: - return self.obj - elif key == PlacementRule.SYMBOL_SPECIFICITY: - return self.symbol + if not child: + child = self.child_node(self, name, self.sections) + self.children.append(child) else: - return None + child = child[0] - def __str__(self): - sorted_sections = sorted(self.get_section_names()) + return child - sections_string = list() + def get_output_commands(self): + commands = collections.defaultdict(list) - for section in sorted_sections: - exclusions = self.sections[section].excludes.content + def process_commands(cmds): + for (target, commands_list) in cmds.items(): + commands[target].extend(commands_list) - exclusion_string = None + # Process the commands generated from this node + node_commands = self.get_node_output_commands() + process_commands(node_commands) - if exclusions: - exclusion_string = ' '.join(map(lambda e: '*' + e.archive + (':' + e.obj + '.*' if e.obj else ''), exclusions)) - exclusion_string = 'EXCLUDE_FILE(' + exclusion_string + ')' - else: - exclusion_string = '' + # Process the commands generated from this node's children + # recursively + for child in sorted(self.children, key=lambda c: c.name): + children_commands = child.get_output_commands() + process_commands(children_commands) - section_string = None - exclusion_section_string = None + return commands - section_expansions = self.sections[section].expansions.content - section_expanded = self.sections[section].expanded.content + def add_node_child(self, entity, sections, target, sections_db): + child = self.add_child(entity) + child.insert(entity, sections, target, sections_db) - if section_expansions and section_expanded: - section_string = ' '.join(section_expansions) - exclusion_section_string = section_string - else: - section_string = section - exclusion_section_string = exclusion_string + ' ' + section_string + def get_node_output_commands(self): + commands = collections.defaultdict(list) - sections_string.append(exclusion_section_string) + for sections in self.get_section_keys(): + info = self.sections[sections] + if info.exclusions or info.explicit: + command = InputSectionDesc(self.entity, sections, info.exclusions) + commands[info.target].append(command) - sections_string = ' '.join(sections_string) + return commands - archive = str(self.archive) if self.archive else '' - obj = (str(self.obj) + ('.*' if self.obj else '')) if self.obj else '' - - # Handle output string generation based on information available - if self.specificity == PlacementRule.DEFAULT_SPECIFICITY: - rule_string = '*(%s)' % (sections_string) - elif self.specificity == PlacementRule.ARCHIVE_SPECIFICITY: - rule_string = '*%s:(%s)' % (archive, sections_string) + def insert(self, entity, sections, target, sections_db): + if self.entity.specificity == entity.specificity: + if self.parent.sections[sections].target != target: + self.add_sections(sections, target) + self.exclude_from_parent(sections) else: - rule_string = '*%s:%s(%s)' % (archive, obj, sections_string) + self.add_node_child(entity, sections, target, sections_db) - return rule_string - - def __eq__(self, other): - if id(self) == id(other): - return True - - def exclusions_set(exclusions): - exclusions_set = {(e.archive, e.obj, e.symbol, e.target) for e in exclusions} - return exclusions_set - - if self.archive != other.archive: - return False - - if self.obj != other.obj: - return False - - if self.symbol != other.symbol: - return False - - if set(self.sections.keys()) != set(other.sections.keys()): - return False - - for (section, metadata) in self.sections.items(): - - self_meta = metadata - other_meta = other.sections[section] - - if exclusions_set(self_meta.excludes.content) != exclusions_set(other_meta.excludes.content): - return False - - if set(self_meta.expansions.content) != set(other_meta.expansions.content): - return False - - return True - - def __ne__(self, other): - return not self.__eq__(other) - - def __iter__(self): - yield self.archive - yield self.obj - yield self.symbol - raise StopIteration + def get_section_keys(self): + return sorted(self.sections.keys(), key=' '.join) -class GenerationModel: +class SymbolNode(RuleNode): + + def __init__(self, parent, name, sections): + RuleNode.__init__(self, parent, name, sections) + self.entity = Entity(self.parent.parent.name, self.parent.name, self.name) + + def insert(self, entity, sections, target, sections_db): + self.add_sections(sections, target) + + def get_node_output_commands(self): + commands = collections.defaultdict(list) + + for sections in self.get_section_keys(): + info = self.sections[sections] + if info.explicit: + command = InputSectionDesc(Entity(self.parent.parent.name, self.parent.name), sections, []) + commands[info.target].append(command) + + return commands + + +class ObjectNode(RuleNode): + + def __init__(self, parent, name, sections): + RuleNode.__init__(self, parent, name, sections) + self.child_node = SymbolNode + self.expanded_sections = dict() + self.entity = Entity(self.parent.name, self.name) + + def add_node_child(self, entity, sections, target, sections_db): + if self.sections[sections].target != target: + symbol = entity.symbol + match_sections = None + + obj_sections = sections_db.get_sections(self.parent.name, self.name) + + try: + match_sections = self.expanded_sections[sections] + except KeyError: + match_sections = [] + for s in sections: + match_sections.extend(fnmatch.filter(obj_sections, s)) + + if match_sections: + remove_sections = [s.replace('.*', '.%s' % symbol) for s in sections if '.*' in s] + filtered_sections = [s for s in match_sections if s not in remove_sections] + + if set(filtered_sections) != set(match_sections): # some sections removed + child = self.add_child(entity) + child.insert(entity, frozenset(remove_sections), target, obj_sections) + + # Remember the result for node command generation + self.expanded_sections[sections] = filtered_sections + self.exclude_from_parent(sections) + + def get_node_output_commands(self): + commands = collections.defaultdict(list) + + for sections in self.get_section_keys(): + info = self.sections[sections] + + try: + match_sections = self.expanded_sections[sections] + except KeyError: + match_sections = [] + + if match_sections or info.explicit: + command_sections = match_sections if match_sections else sections + command = InputSectionDesc(self.entity, command_sections, []) + commands[info.target].append(command) + + return commands + + def exclude_from_parent(self, sections): + # Check if there is an explicit emmission for the parent node, which is an archive node. + # If there is, make the exclusion there. If not, make the exclusion on the root node. + # This is to avoid emitting unecessary command and exclusions for the archive node and + # from the root node, respectively. + if self.parent.sections[sections].explicit: + self.parent.add_exclusion(sections, self.entity) + else: + self.parent.parent.add_exclusion(sections, self.entity) + + +class ArchiveNode(RuleNode): + + def __init__(self, parent, name, sections): + RuleNode.__init__(self, parent, name, sections) + self.child_node = ObjectNode + self.entity = Entity(self.name) + + +class RootNode(RuleNode): + def __init__(self): + RuleNode.__init__(self, None, Entity.ALL, None) + self.child_node = ArchiveNode + self.entity = Entity('*') + + def insert(self, entity, sections, target, sections_db): + if self.entity.specificity == entity.specificity: + self.add_sections(sections, target) + else: + self.add_node_child(entity, sections, target, sections_db) + + +class Generation: """ Implements generation of placement rules based on collected sections, scheme and mapping fragment. """ DEFAULT_SCHEME = 'default' + # Processed mapping, scheme and section entries + EntityMapping = namedtuple('EntityMapping', 'entity sections_group target') + def __init__(self, check_mappings=False, check_mapping_exceptions=None): self.schemes = {} self.sections = {} @@ -265,23 +279,6 @@ class GenerationModel: else: self.check_mapping_exceptions = [] - def _add_mapping_rules(self, archive, obj, symbol, scheme_name, scheme_dict, rules): - # Use an ordinary dictionary to raise exception on non-existing keys - temp_dict = dict(scheme_dict) - - sections_bucket = temp_dict[scheme_name] - - for (target, sections) in sections_bucket.items(): - section_entries = [] - - for section in sections: - section_entries.extend(section.entries) - - rule = PlacementRule(archive, obj, symbol, section_entries, target) - - if rule not in rules: - rules.append(rule) - def _build_scheme_dictionary(self): scheme_dictionary = collections.defaultdict(dict) @@ -297,7 +294,7 @@ class GenerationModel: try: sections = self.sections[sections_name] except KeyError: - message = GenerationException.UNDEFINED_REFERENCE + " to sections '" + sections + "'." + message = GenerationException.UNDEFINED_REFERENCE + " to sections '" + sections_name + "'." raise GenerationException(message, scheme) sections_in_bucket.append(sections) @@ -327,147 +324,69 @@ class GenerationModel: return scheme_dictionary - def generate_rules(self, sections_infos): - scheme_dictionary = self._build_scheme_dictionary() + def get_section_strs(self, section): + s_list = [Sections.get_section_data_from_entry(s) for s in section.entries] + return frozenset([item for sublist in s_list for item in sublist]) - # Generate default rules - default_rules = list() - self._add_mapping_rules(None, None, None, GenerationModel.DEFAULT_SCHEME, scheme_dictionary, default_rules) + def _generate_entity_mappings(self, scheme_dictionary, entities): + entity_mappings = [] - all_mapping_rules = collections.defaultdict(list) - - # Generate rules based on mapping fragments for mapping in self.mappings.values(): archive = mapping.archive - mapping_rules = all_mapping_rules[archive] + for (obj, symbol, scheme_name) in mapping.entries: - try: - if not (obj == Mapping.MAPPING_ALL_OBJECTS and symbol is None and - scheme_name == GenerationModel.DEFAULT_SCHEME): - if self.check_mappings and mapping.name not in self.check_mapping_exceptions: - if not obj == Mapping.MAPPING_ALL_OBJECTS: - obj_sections = sections_infos.get_obj_sections(archive, obj) - if not obj_sections: - message = "'%s:%s' not found" % (archive, obj) - raise GenerationException(message, mapping) + entity = Entity(archive, obj, symbol) - if symbol: - obj_sym = fnmatch.filter(obj_sections, '*%s' % symbol) - if not obj_sym: - message = "'%s:%s %s' not found" % (archive, obj, symbol) - raise GenerationException(message, mapping) + # Check the entity exists + if (self.check_mappings and + entity.specificity.value > Entity.Specificity.ARCHIVE.value and + mapping.name not in self.check_mapping_exceptions): + if not entities.check_exists(entity): + message = "'%s' not found" % str(entity) + raise GenerationException(message, mapping) - self._add_mapping_rules(archive, obj, symbol, scheme_name, scheme_dictionary, mapping_rules) - except KeyError: - message = GenerationException.UNDEFINED_REFERENCE + " to scheme '" + scheme_name + "'." - raise GenerationException(message, mapping) + # Create placement rule for each 'section -> target' in the scheme. + # + # For example. for the mapping entry: + # + # obj (scheme) + # + # The enumrated to: + # + # obj (section1 -> target1) + # obj (section2 -> target2) + # ... + for (target, sections) in scheme_dictionary[scheme_name].items(): + for section in sections: + entity_mappings.append(Generation.EntityMapping(entity, self.get_section_strs(section), target)) - # Detect rule conflicts - for mapping_rules in all_mapping_rules.items(): - self._detect_conflicts(mapping_rules) + return entity_mappings - # Add exclusions - for mapping_rules in all_mapping_rules.values(): - self._create_exclusions(mapping_rules, default_rules, sections_infos) + def generate_rules(self, entities): + scheme_dictionary = self._build_scheme_dictionary() - placement_rules = collections.defaultdict(list) + entity_mappings = self._generate_entity_mappings(scheme_dictionary, entities) - # Add the default rules grouped by target - for default_rule in default_rules: - existing_rules = placement_rules[default_rule.target] - if default_rule.get_section_names(): - existing_rules.append(default_rule) + entity_mappings.sort(key=lambda m: m.entity) - archives = sorted(all_mapping_rules.keys()) + # Create root nodes dictionary for the default scheme, whose + # key is the target name and value is a list of the root nodes for that target. + root_node = RootNode() + for (target, sections) in scheme_dictionary['default'].items(): + for section in sections: + root_node.insert(Entity(), self.get_section_strs(section), target, entities) - for archive in archives: - # Add the mapping rules grouped by target - mapping_rules = sorted(all_mapping_rules[archive], key=lambda m: (m.specificity, str(m))) - for mapping_rule in mapping_rules: - existing_rules = placement_rules[mapping_rule.target] - if mapping_rule.get_section_names(): - existing_rules.append(mapping_rule) + for mapping in entity_mappings: + (entity, sections, target) = mapping + try: + root_node.insert(entity, sections, target, entities) + except ValueError as e: + raise GenerationException(str(e)) - return placement_rules + # Traverse the tree, creating the rules + commands = root_node.get_output_commands() - def _detect_conflicts(self, rules): - (archive, rules_list) = rules - - for specificity in range(0, PlacementRule.OBJECT_SPECIFICITY + 1): - rules_with_specificity = filter(lambda r: r.specificity == specificity, rules_list) - - for rule_a, rule_b in itertools.combinations(rules_with_specificity, 2): - intersections = rule_a.get_sections_intersection(rule_b) - - if intersections and rule_a.maps_same_entities_as(rule_b): - rules_string = str([str(rule_a), str(rule_b)]) - message = 'Rules ' + rules_string + ' map sections ' + str(list(intersections)) + ' into multiple targets.' - raise GenerationException(message) - - def _create_extra_rules(self, rules): - # This function generates extra rules for symbol specific rules. The reason for generating extra rules is to isolate, - # as much as possible, rules that require expansion. Particularly, object specific extra rules are generated. - rules_to_process = sorted(rules, key=lambda r: r.specificity) - symbol_specific_rules = list(filter(lambda r: r.specificity == PlacementRule.SYMBOL_SPECIFICITY, rules_to_process)) - - extra_rules = dict() - - for symbol_specific_rule in symbol_specific_rules: - extra_rule_candidate = {s: None for s in symbol_specific_rule.get_section_names()} - - super_rules = filter(lambda r: symbol_specific_rule.is_more_specific_rule_of(r), rules_to_process) - - # Take a look at the existing rules that are more general than the current symbol-specific rule. - # Only generate an extra rule if there is no existing object specific rule for that section - for super_rule in super_rules: - intersections = symbol_specific_rule.get_sections_intersection(super_rule) - for intersection in intersections: - if super_rule.specificity != PlacementRule.OBJECT_SPECIFICITY: - extra_rule_candidate[intersection] = super_rule - else: - extra_rule_candidate[intersection] = None - - # Generate the extra rules for the symbol specific rule section, keeping track of the generated extra rules - for (section, section_rule) in extra_rule_candidate.items(): - if section_rule: - extra_rule = None - extra_rules_key = (symbol_specific_rule.archive, symbol_specific_rule.obj, section_rule.target) - - try: - extra_rule = extra_rules[extra_rules_key] - - if section not in extra_rule.get_section_names(): - new_rule = PlacementRule(extra_rule.archive, extra_rule.obj, extra_rule.symbol, - list(extra_rule.get_section_names()) + [section], extra_rule.target) - extra_rules[extra_rules_key] = new_rule - except KeyError: - extra_rule = PlacementRule(symbol_specific_rule.archive, symbol_specific_rule.obj, None, [section], section_rule.target) - extra_rules[extra_rules_key] = extra_rule - - return extra_rules.values() - - def _create_exclusions(self, mapping_rules, default_rules, sections_info): - rules = list(default_rules) - rules.extend(mapping_rules) - - extra_rules = self._create_extra_rules(rules) - - mapping_rules.extend(extra_rules) - rules.extend(extra_rules) - - # Sort the rules by means of how specific they are. Sort by specificity from lowest to highest - # * -> lib:* -> lib:obj -> lib:obj:symbol - sorted_rules = sorted(rules, key=lambda r: r.specificity) - - # Now that the rules have been sorted, loop through each rule, and then loop - # through rules below it (higher indeces), adding exclusions whenever appropriate. - for general_rule in sorted_rules: - for specific_rule in reversed(sorted_rules): - if (specific_rule.specificity > general_rule.specificity and - specific_rule.specificity != PlacementRule.SYMBOL_SPECIFICITY) or \ - (specific_rule.specificity == PlacementRule.SYMBOL_SPECIFICITY and - general_rule.specificity == PlacementRule.OBJECT_SPECIFICITY): - general_rule.add_exclusion(specific_rule, sections_info) + return commands def add_fragments_from_file(self, fragment_file): for fragment in fragment_file.fragments: @@ -493,79 +412,6 @@ class GenerationModel: dict_to_append_to[fragment.name] = fragment -class TemplateModel: - """ - Encapsulates a linker script template file. Finds marker syntax and handles replacement to generate the - final output. - """ - - Marker = collections.namedtuple('Marker', 'target indent rules') - - def __init__(self, template_file): - self.members = [] - self.file = os.path.realpath(template_file.name) - - self._generate_members(template_file) - - def _generate_members(self, template_file): - lines = template_file.readlines() - - target = Fragment.IDENTIFIER - reference = Suppress('mapping') + Suppress('[') + target.setResultsName('target') + Suppress(']') - pattern = White(' \t').setResultsName('indent') + reference - - # Find the markers in the template file line by line. If line does not match marker grammar, - # set it as a literal to be copied as is to the output file. - for line in lines: - try: - parsed = pattern.parseString(line) - - indent = parsed.indent - target = parsed.target - - marker = TemplateModel.Marker(target, indent, []) - - self.members.append(marker) - except ParseException: - # Does not match marker syntax - self.members.append(line) - - def fill(self, mapping_rules): - for member in self.members: - target = None - try: - target = member.target - rules = member.rules - - del rules[:] - - rules.extend(mapping_rules[target]) - except KeyError: - message = GenerationException.UNDEFINED_REFERENCE + " to target '" + target + "'." - raise GenerationException(message) - except AttributeError: - pass - - def write(self, output_file): - # Add information that this is a generated file. - output_file.write('/* Automatically generated file; DO NOT EDIT */\n') - output_file.write('/* Espressif IoT Development Framework Linker Script */\n') - output_file.write('/* Generated from: %s */\n' % self.file) - output_file.write('\n') - - # Do the text replacement - for member in self.members: - try: - indent = member.indent - rules = member.rules - - for rule in rules: - generated_line = ''.join([indent, str(rule), '\n']) - output_file.write(generated_line) - except AttributeError: - output_file.write(member) - - class GenerationException(LdGenFailure): """ Exception for linker script generation failures such as undefined references/ failure to @@ -583,90 +429,3 @@ class GenerationException(LdGenFailure): return "%s\nIn fragment '%s' defined in '%s'." % (self.message, self.fragment.name, self.fragment.path) else: return self.message - - -class SectionsInfo(dict): - """ - Encapsulates an output of objdump. Contains information about the static library sections - and names - """ - - __info = collections.namedtuple('__info', 'filename content') - - def __init__(self): - self.sections = dict() - - def add_sections_info(self, sections_info_dump): - first_line = sections_info_dump.readline() - - archive_path = (Literal('In archive').suppress() + - White().suppress() + - # trim the colon and line ending characters from archive_path - restOfLine.setResultsName('archive_path').setParseAction(lambda s, loc, toks: s.rstrip(':\n\r '))) - parser = archive_path - - results = None - - try: - results = parser.parseString(first_line, parseAll=True) - except ParseException as p: - raise ParseException('Parsing sections info for library ' + sections_info_dump.name + ' failed. ' + p.msg) - - archive = os.path.basename(results.archive_path) - self.sections[archive] = SectionsInfo.__info(sections_info_dump.name, sections_info_dump.read()) - - def _get_infos_from_file(self, info): - # {object}: file format elf32-xtensa-le - object_line = SkipTo(':').setResultsName('object') + Suppress(restOfLine) - - # Sections: - # Idx Name ... - section_start = Suppress(Literal('Sections:')) - section_header = Suppress(OneOrMore(Word(alphas))) - - # 00 {section} 0000000 ... - # CONTENTS, ALLOC, .... - section_entry = Suppress(Word(nums)) + SkipTo(' ') + Suppress(restOfLine) + \ - Suppress(ZeroOrMore(Word(alphas) + Literal(',')) + Word(alphas)) - - content = Group(object_line + section_start + section_header + Group(OneOrMore(section_entry)).setResultsName('sections')) - parser = Group(ZeroOrMore(content)).setResultsName('contents') - - results = None - - try: - results = parser.parseString(info.content, parseAll=True) - except ParseException as p: - raise ParseException('Unable to parse section info file ' + info.filename + '. ' + p.msg) - - return results - - def get_obj_sections(self, archive, obj): - res = [] - try: - stored = self.sections[archive] - - # Parse the contents of the sections file on-demand, - # save the result for later - if not isinstance(stored, dict): - parsed = self._get_infos_from_file(stored) - stored = dict() - for content in parsed.contents: - sections = list(map(lambda s: s, content.sections)) - stored[content.object] = sections - self.sections[archive] = stored - - try: - res = stored[obj + '.o'] - except KeyError: - try: - res = stored[obj + '.c.obj'] - except KeyError: - try: - res = stored[obj + '.cpp.obj'] - except KeyError: - res = stored[obj + '.S.obj'] - except KeyError: - pass - - return res diff --git a/tools/ldgen/ldgen.py b/tools/ldgen/ldgen.py index df1a74af92..85e99ea59a 100755 --- a/tools/ldgen/ldgen.py +++ b/tools/ldgen/ldgen.py @@ -24,9 +24,11 @@ import sys import tempfile from io import StringIO +from entity import EntityDB from fragments import FragmentFile -from generation import GenerationModel, SectionsInfo, TemplateModel +from generation import Generation from ldgen_common import LdGenFailure +from linker_script import LinkerScript from pyparsing import ParseException, ParseFatalException from sdkconfig import SDKConfig @@ -125,7 +127,7 @@ def main(): check_mapping_exceptions = None try: - sections_infos = SectionsInfo() + sections_infos = EntityDB() for library in libraries_file: library = library.strip() if library: @@ -133,7 +135,7 @@ def main(): dump.name = library sections_infos.add_sections_info(dump) - generation_model = GenerationModel(check_mapping, check_mapping_exceptions) + generation_model = Generation(check_mapping, check_mapping_exceptions) _update_environment(args) # assign args.env and args.env_file to os.environ @@ -151,7 +153,7 @@ def main(): mapping_rules = generation_model.generate_rules(sections_infos) - script_model = TemplateModel(input_file) + script_model = LinkerScript(input_file) script_model.fill(mapping_rules) with tempfile.TemporaryFile('w+') as output: diff --git a/tools/ldgen/linker_script.py b/tools/ldgen/linker_script.py new file mode 100644 index 0000000000..d7d3dfc7e3 --- /dev/null +++ b/tools/ldgen/linker_script.py @@ -0,0 +1,95 @@ +# +# Copyright 2018-2019 Espressif Systems (Shanghai) PTE LTD +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import collections +import os + +from fragments import Fragment +from generation import GenerationException +from pyparsing import ParseException, Suppress, White + + +class LinkerScript: + """ + Encapsulates a linker script template file. Finds marker syntax and handles replacement to generate the + final output. + """ + + Marker = collections.namedtuple('Marker', 'target indent rules') + + def __init__(self, template_file): + self.members = [] + self.file = os.path.realpath(template_file.name) + + self._generate_members(template_file) + + def _generate_members(self, template_file): + lines = template_file.readlines() + + target = Fragment.IDENTIFIER + reference = Suppress('mapping') + Suppress('[') + target.setResultsName('target') + Suppress(']') + pattern = White(' \t').setResultsName('indent') + reference + + # Find the markers in the template file line by line. If line does not match marker grammar, + # set it as a literal to be copied as is to the output file. + for line in lines: + try: + parsed = pattern.parseString(line) + + indent = parsed.indent + target = parsed.target + + marker = LinkerScript.Marker(target, indent, []) + + self.members.append(marker) + except ParseException: + # Does not match marker syntax + self.members.append(line) + + def fill(self, mapping_rules): + for member in self.members: + target = None + try: + target = member.target + rules = member.rules + + del rules[:] + + rules.extend(mapping_rules[target]) + except KeyError: + message = GenerationException.UNDEFINED_REFERENCE + " to target '" + target + "'." + raise GenerationException(message) + except AttributeError: + pass + + def write(self, output_file): + # Add information that this is a generated file. + output_file.write('/* Automatically generated file; DO NOT EDIT */\n') + output_file.write('/* Espressif IoT Development Framework Linker Script */\n') + output_file.write('/* Generated from: %s */\n' % self.file) + output_file.write('\n') + + # Do the text replacement + for member in self.members: + try: + indent = member.indent + rules = member.rules + + for rule in rules: + generated_line = ''.join([indent, str(rule), '\n']) + output_file.write(generated_line) + except AttributeError: + output_file.write(member) From 0142676cbfa84cc35765959effa0ed34c4910331 Mon Sep 17 00:00:00 2001 From: Renz Bagaporo Date: Wed, 27 Jan 2021 16:02:44 +0800 Subject: [PATCH 2/4] ldgen: additional tests for generation support classes --- tools/ci/config/host-test.yml | 6 +- tools/ci/executable-list.txt | 2 + tools/ldgen/output_commands.py | 78 ++++++ tools/ldgen/test/data/{sample.lf => base.lf} | 0 .../data/{sections.info => libfreertos.a.txt} | 105 ++++++++ .../data/{template.ld => linker_script.ld} | 0 .../test/data/test_entity/libfreertos.a.txt | 39 +++ .../parse_test.txt} | 6 +- tools/ldgen/test/test_entity.py | 251 ++++++++++++++++++ tools/ldgen/test/test_output_commands.py | 141 ++++++++++ 10 files changed, 623 insertions(+), 5 deletions(-) create mode 100644 tools/ldgen/output_commands.py rename tools/ldgen/test/data/{sample.lf => base.lf} (100%) rename tools/ldgen/test/data/{sections.info => libfreertos.a.txt} (93%) rename tools/ldgen/test/data/{template.ld => linker_script.ld} (100%) create mode 100644 tools/ldgen/test/data/test_entity/libfreertos.a.txt rename tools/ldgen/test/data/{sections_parse.info => test_entity/parse_test.txt} (71%) create mode 100755 tools/ldgen/test/test_entity.py create mode 100755 tools/ldgen/test/test_output_commands.py diff --git a/tools/ci/config/host-test.yml b/tools/ci/config/host-test.yml index 645aeb306b..bd25368edf 100644 --- a/tools/ci/config/host-test.yml +++ b/tools/ci/config/host-test.yml @@ -72,8 +72,10 @@ test_ldgen_on_host: extends: .host_test_template script: - cd tools/ldgen/test - - ./test_fragments.py - - ./test_generation.py + - ${IDF_PATH}/tools/ci/multirun_with_pyenv.sh ./test_fragments.py + - ${IDF_PATH}/tools/ci/multirun_with_pyenv.sh ./test_generation.py + - ${IDF_PATH}/tools/ci/multirun_with_pyenv.sh ./test_entity.py + - ${IDF_PATH}/tools/ci/multirun_with_pyenv.sh ./test_output_commands.py variables: LC_ALL: C.UTF-8 diff --git a/tools/ci/executable-list.txt b/tools/ci/executable-list.txt index 1ce46dea38..1c446afa3e 100644 --- a/tools/ci/executable-list.txt +++ b/tools/ci/executable-list.txt @@ -88,8 +88,10 @@ tools/kconfig_new/test/confserver/test_confserver.py tools/kconfig_new/test/gen_kconfig_doc/test_kconfig_out.py tools/kconfig_new/test/gen_kconfig_doc/test_target_visibility.py tools/ldgen/ldgen.py +tools/ldgen/test/test_entity.py tools/ldgen/test/test_fragments.py tools/ldgen/test/test_generation.py +tools/ldgen/test/test_output_commands.py tools/mass_mfg/mfg_gen.py tools/mkdfu.py tools/mkuf2.py diff --git a/tools/ldgen/output_commands.py b/tools/ldgen/output_commands.py new file mode 100644 index 0000000000..93722f8497 --- /dev/null +++ b/tools/ldgen/output_commands.py @@ -0,0 +1,78 @@ +# +# Copyright 2018-2019 Espressif Systems (Shanghai) PTE LTD +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from entity import Entity + + +class InputSectionDesc(): + + def __init__(self, entity, sections, exclusions=None): + assert(entity.specificity != Entity.Specificity.SYMBOL) + + self.entity = entity + self.sections = set(sections) + + self.exclusions = set() + + if exclusions: + assert(not [e for e in exclusions if e.specificity == Entity.Specificity.SYMBOL or + e.specificity == Entity.Specificity.NONE]) + self.exclusions = set(exclusions) + else: + self.exclusions = set() + + def __str__(self): + if self.sections: + exclusion_strings = [] + + for exc in sorted(self.exclusions): + if exc.specificity == Entity.Specificity.ARCHIVE: + exc_string = '*%s' % (exc.archive) + else: + exc_string = '*%s:%s.*' % (exc.archive, exc.obj) + + exclusion_strings.append(exc_string) + + section_strings = [] + + if exclusion_strings: + exclusion_string = 'EXCLUDE_FILE(%s)' % ' '.join(exclusion_strings) + + for section in sorted(self.sections): + section_strings.append('%s %s' % (exclusion_string, section)) + else: + for section in sorted(self.sections): + section_strings.append(section) + + sections_string = '(%s)' % ' '.join(section_strings) + else: + sections_string = '( )' + + command = None + + if self.entity.specificity == Entity.Specificity.NONE: + command = '*%s' % (sections_string) + elif self.entity.specificity == Entity.Specificity.ARCHIVE: + command = '*%s:%s' % (self.entity.archive, sections_string) + else: + command = '*%s:%s.*%s' % (self.entity.archive, self.entity.obj, sections_string) + + return command + + def __eq__(self, other): + return (self.entity == other.entity and + self.sections == other.sections and + self.exclusions == other.exclusions) diff --git a/tools/ldgen/test/data/sample.lf b/tools/ldgen/test/data/base.lf similarity index 100% rename from tools/ldgen/test/data/sample.lf rename to tools/ldgen/test/data/base.lf diff --git a/tools/ldgen/test/data/sections.info b/tools/ldgen/test/data/libfreertos.a.txt similarity index 93% rename from tools/ldgen/test/data/sections.info rename to tools/ldgen/test/data/libfreertos.a.txt index 75c699702d..9f7821da91 100644 --- a/tools/ldgen/test/data/sections.info +++ b/tools/ldgen/test/data/libfreertos.a.txt @@ -343,6 +343,111 @@ Idx Name Size VMA LMA File off Algn 49 .xt.prop 00000408 00000000 00000000 00002f3e 2**0 CONTENTS, RELOC, READONLY +port.cpp.obj: file format elf32-xtensa-le + +Sections: +Idx Name Size VMA LMA File off Algn + 0 .literal.pxPortInitialiseStack 00000018 00000000 00000000 00000034 2**2 + CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE + 1 .literal.xPortStartScheduler 00000014 00000000 00000000 0000004c 2**2 + CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE + 2 .literal.xPortSysTickHandler 00000008 00000000 00000000 00000060 2**2 + CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE + 3 .literal.vPortYieldOtherCore 00000004 00000000 00000000 00000068 2**2 + CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE + 4 .literal.vPortReleaseTaskMPUSettings 00000004 00000000 00000000 0000006c 2**2 + CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE + 5 .literal.xPortInIsrContext 00000008 00000000 00000000 00000070 2**2 + CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE + 6 .iram1.literal 00000004 00000000 00000000 00000078 2**2 + CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE + 7 .literal.vPortAssertIfInISR 00000018 00000000 00000000 0000007c 2**2 + CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE + 8 .literal.vPortCPUInitializeMutex 00000004 00000000 00000000 00000094 2**2 + CONTENTS, ALLOC, LOAD, READONLY, CODE + 9 .literal.vPortCPUAcquireMutex 00000030 00000000 00000000 00000098 2**2 + CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE + 10 .literal.vPortCPUAcquireMutexTimeout 00000030 00000000 00000000 000000c8 2**2 + CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE + 11 .literal.vPortCPUReleaseMutex 00000028 00000000 00000000 000000f8 2**2 + CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE + 12 .literal.vPortSetStackWatchpoint 00000008 00000000 00000000 00000120 2**2 + CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE + 13 .text 00000000 00000000 00000000 00000128 2**0 + CONTENTS, ALLOC, LOAD, READONLY, CODE + 14 .data 00000000 00000000 00000000 00000128 2**0 + CONTENTS, ALLOC, LOAD, DATA + 15 .bss 00000000 00000000 00000000 00000128 2**0 + ALLOC + 16 .text.pxPortInitialiseStack 00000086 00000000 00000000 00000128 2**2 + CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE + 17 .text.vPortEndScheduler 00000005 00000000 00000000 000001b0 2**2 + CONTENTS, ALLOC, LOAD, READONLY, CODE + 18 .text.xPortStartScheduler 0000002e 00000000 00000000 000001b8 2**2 + CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE + 19 .text.xPortSysTickHandler 00000016 00000000 00000000 000001e8 2**2 + CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE + 20 .text.vPortYieldOtherCore 0000000e 00000000 00000000 00000200 2**2 + CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE + 21 .text.vPortStoreTaskMPUSettings 00000013 00000000 00000000 00000210 2**2 + CONTENTS, ALLOC, LOAD, READONLY, CODE + 22 .text.vPortReleaseTaskMPUSettings 0000000e 00000000 00000000 00000224 2**2 + CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE + 23 .text.xPortInIsrContext 00000026 00000000 00000000 00000234 2**2 + CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE + 24 .iram1 0000001a 00000000 00000000 0000025c 2**2 + CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE + 25 .rodata.str1.4 0000013b 00000000 00000000 00000278 2**2 + CONTENTS, ALLOC, LOAD, READONLY, DATA + 26 .text.vPortAssertIfInISR 00000025 00000000 00000000 000003b4 2**2 + CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE + 27 .text.vPortCPUInitializeMutex 0000000e 00000000 00000000 000003dc 2**2 + CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE + 28 .text.vPortCPUAcquireMutex 00000088 00000000 00000000 000003ec 2**2 + CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE + 29 .text.vPortCPUAcquireMutexTimeout 000000ab 00000000 00000000 00000474 2**2 + CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE + 30 .text.vPortCPUReleaseMutex 00000061 00000000 00000000 00000520 2**2 + CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE + 31 .text.vPortSetStackWatchpoint 0000001a 00000000 00000000 00000584 2**2 + CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE + 32 .text.xPortGetTickRateHz 00000008 00000000 00000000 000005a0 2**2 + CONTENTS, ALLOC, LOAD, READONLY, CODE + 33 .rodata.__func__$5264 00000029 00000000 00000000 000005a8 2**2 + CONTENTS, ALLOC, LOAD, READONLY, DATA + 34 .rodata.__func__$5259 00000029 00000000 00000000 000005d4 2**2 + CONTENTS, ALLOC, LOAD, READONLY, DATA + 35 .rodata.__FUNCTION__$5243 00000013 00000000 00000000 00000600 2**2 + CONTENTS, ALLOC, LOAD, READONLY, DATA + 36 .bss.port_interruptNesting 00000008 00000000 00000000 00000614 2**2 + ALLOC + 37 .bss.port_xSchedulerRunning 00000008 00000000 00000000 00000614 2**2 + ALLOC + 38 .debug_frame 00000190 00000000 00000000 00000614 2**2 + CONTENTS, RELOC, READONLY, DEBUGGING + 39 .debug_info 00000e78 00000000 00000000 000007a4 2**0 + CONTENTS, RELOC, READONLY, DEBUGGING + 40 .debug_abbrev 00000404 00000000 00000000 0000161c 2**0 + CONTENTS, READONLY, DEBUGGING + 41 .debug_loc 000005f1 00000000 00000000 00001a20 2**0 + CONTENTS, RELOC, READONLY, DEBUGGING + 42 .debug_aranges 00000098 00000000 00000000 00002011 2**0 + CONTENTS, RELOC, READONLY, DEBUGGING + 43 .debug_ranges 000000a0 00000000 00000000 000020a9 2**0 + CONTENTS, RELOC, READONLY, DEBUGGING + 44 .debug_line 000005fb 00000000 00000000 00002149 2**0 + CONTENTS, RELOC, READONLY, DEBUGGING + 45 .debug_str 0000071f 00000000 00000000 00002744 2**0 + CONTENTS, READONLY, DEBUGGING + 46 .comment 0000003b 00000000 00000000 00002e63 2**0 + CONTENTS, READONLY + 47 .xtensa.info 00000038 00000000 00000000 00002e9e 2**0 + CONTENTS, READONLY + 48 .xt.lit 00000068 00000000 00000000 00002ed6 2**0 + CONTENTS, RELOC, READONLY + 49 .xt.prop 00000408 00000000 00000000 00002f3e 2**0 + CONTENTS, RELOC, READONLY + portasm.S.obj: file format elf32-xtensa-le Sections: diff --git a/tools/ldgen/test/data/template.ld b/tools/ldgen/test/data/linker_script.ld similarity index 100% rename from tools/ldgen/test/data/template.ld rename to tools/ldgen/test/data/linker_script.ld diff --git a/tools/ldgen/test/data/test_entity/libfreertos.a.txt b/tools/ldgen/test/data/test_entity/libfreertos.a.txt new file mode 100644 index 0000000000..bc107f53b9 --- /dev/null +++ b/tools/ldgen/test/data/test_entity/libfreertos.a.txt @@ -0,0 +1,39 @@ +In archive /home/user/build/esp-idf/freertos/libfreertos.a: + +croutine.c.obj: file format elf32-xtensa-le + +Sections: +Idx Name Size VMA LMA File off Algn + 0 .literal.prvCheckPendingReadyList 00000018 00000000 00000000 00000034 2**2 + CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE + 1 .literal.prvCheckDelayedList 0000002c 00000000 00000000 0000004c 2**2 + CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE + +croutine.cpp.obj: file format elf32-xtensa-le + +Sections: +Idx Name Size VMA LMA File off Algn + 9 .text.prvCheckPendingReadyList 00000056 00000000 00000000 000000d8 2**2 + CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE + 10 .text.prvCheckDelayedList 000000ac 00000000 00000000 00000130 2**2 + CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE + +croutine.S.obj: file format elf32-xtensa-le + +Sections: +Idx Name Size VMA LMA File off Algn + 26 .debug_frame 000000a0 00000000 00000000 00000394 2**2 + CONTENTS, RELOC, READONLY, DEBUGGING + 27 .debug_info 000006b8 00000000 00000000 00000434 2**0 + CONTENTS, RELOC, READONLY, DEBUGGING + 28 .debug_abbrev 00000233 00000000 00000000 00000aec 2**0 + CONTENTS, READONLY, DEBUGGING + +timers.o: file format elf32-xtensa-le + +Sections: +Idx Name Size VMA LMA File off Algn + 0 .literal.prvGetNextExpireTime 00000004 00000000 00000000 00000034 2**2 + CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE + 1 .literal.prvInsertTimerInActiveList 00000010 00000000 00000000 00000038 2**2 + CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE diff --git a/tools/ldgen/test/data/sections_parse.info b/tools/ldgen/test/data/test_entity/parse_test.txt similarity index 71% rename from tools/ldgen/test/data/sections_parse.info rename to tools/ldgen/test/data/test_entity/parse_test.txt index cd2f137e88..4357f02032 100644 --- a/tools/ldgen/test/data/sections_parse.info +++ b/tools/ldgen/test/data/test_entity/parse_test.txt @@ -1,4 +1,4 @@ -In archive /home/user/ãóç+ěščřžýáíé/build/esp-idf/freertos/libsections_parse.a: +In archive /home/user/ãóç+ěščřžýáíé/build/esp-idf/freertos/ěščřžýáíé.a: croutine.c.obj: file format elf32-littleriscv @@ -11,9 +11,9 @@ Idx Name Size VMA LMA File off Algn 2 .bss 00000000 00000000 00000000 00000034 2**0 ALLOC -FreeRTOS-openocd.c.obj: file format elf32-xtensa-le // 'F' should not get included in match for 'CONTENTS, ALLOC, LOAD ...' prior +FreeRTOS-ěščřžýáíé.c.obj: file format elf32-xtensa-le // 'F' should not get included in match for 'CONTENTS, ALLOC, LOAD ...' prior Sections: Idx Name Size VMA LMA File off Algn - 0 .literal.prvCheckPendingReadyList 00000018 00000000 00000000 00000034 2**2 + 0 .literal.ěščřžýáíé 00000018 00000000 00000000 00000034 2**2 CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE diff --git a/tools/ldgen/test/test_entity.py b/tools/ldgen/test/test_entity.py new file mode 100755 index 0000000000..a7c2db157c --- /dev/null +++ b/tools/ldgen/test/test_entity.py @@ -0,0 +1,251 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright 2018-2020 Espressif Systems (Shanghai) PTE LTD +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import sys +import unittest + +try: + from entity import Entity, EntityDB +except ImportError: + sys.path.append('../') + from entity import Entity, EntityDB + + +class EntityTest(unittest.TestCase): + + def test_create_none(self): + entity = Entity(Entity.ALL) + self.assertEqual(Entity.Specificity.NONE, entity.specificity) + + entity = Entity(None) + self.assertEqual(Entity.Specificity.NONE, entity.specificity) + + entity = Entity() + self.assertEqual(Entity.Specificity.NONE, entity.specificity) + + def test_create_archive(self): + entity = Entity('libfreertos.a') + self.assertEqual(Entity.Specificity.ARCHIVE, entity.specificity) + + entity = Entity('libfreertos.a', Entity.ALL, Entity.ALL) + self.assertEqual(Entity.Specificity.ARCHIVE, entity.specificity) + + entity = Entity('libfreertos.a', None, None) + self.assertEqual(Entity.Specificity.ARCHIVE, entity.specificity) + + entity = Entity('libfreertos.a', Entity.ALL, None) + self.assertEqual(Entity.Specificity.ARCHIVE, entity.specificity) + + entity = Entity('libfreertos.a', None, Entity.ALL) + self.assertEqual(Entity.Specificity.ARCHIVE, entity.specificity) + + def test_create_obj(self): + entity = Entity('libfreertos.a', 'croutine') + self.assertEqual(Entity.Specificity.OBJ, entity.specificity) + + entity = Entity('libfreertos.a', 'croutine', Entity.ALL) + self.assertEqual(Entity.Specificity.OBJ, entity.specificity) + + entity = Entity('libfreertos.a', 'croutine', None) + self.assertEqual(Entity.Specificity.OBJ, entity.specificity) + + def test_create_symbol(self): + entity = Entity('libfreertos.a', 'croutine', 'prvCheckPendingReadyList') + self.assertEqual(Entity.Specificity.SYMBOL, entity.specificity) + + def test_create_invalid(self): + with self.assertRaises(ValueError): + Entity(None, 'croutine') + + with self.assertRaises(ValueError): + Entity(Entity.ALL, 'croutine') + + with self.assertRaises(ValueError): + Entity(None, None, 'prvCheckPendingReadyList') + + with self.assertRaises(ValueError): + Entity(Entity.ALL, Entity.ALL, 'prvCheckPendingReadyList') + + with self.assertRaises(ValueError): + Entity(None, Entity.ALL, 'prvCheckPendingReadyList') + + with self.assertRaises(ValueError): + Entity(Entity.ALL, None, 'prvCheckPendingReadyList') + + with self.assertRaises(ValueError): + Entity('libfreertos.a', None, 'prvCheckPendingReadyList') + + with self.assertRaises(ValueError): + Entity('libfreertos.a', Entity.ALL, 'prvCheckPendingReadyList') + + def test_compare_different_specificity(self): + # Different specificity: NONE < ARCHIVE < OBJ < SYMBOL + entity_a = Entity() + entity_b = Entity('libfreertos.a') + self.assertLess(entity_a, entity_b) + + entity_a = Entity('libfreertos.a') + entity_b = Entity('libfreertos.a', 'croutine') + self.assertLess(entity_a, entity_b) + + entity_a = Entity('libfreertos.a', 'croutine') + entity_b = Entity('libfreertos.a', 'croutine', 'prvCheckPendingReadyList') + self.assertLess(entity_a, entity_b) + + entity_a = Entity(Entity.ALL) + entity_b = Entity('libfreertos.a') + self.assertLess(entity_a, entity_b) + + entity_a = Entity('libfreertos.a', Entity.ALL) + entity_b = Entity('libfreertos.a', 'croutine') + self.assertLess(entity_a, entity_b) + + entity_a = Entity('libfreertos.a', 'croutine', Entity.ALL) + entity_b = Entity('libfreertos.a', 'croutine', 'prvCheckPendingReadyList') + self.assertLess(entity_a, entity_b) + + def test_compare_equal(self): + # Compare equal specificities and members + entity_a = Entity() + entity_b = Entity() + self.assertEqual(entity_a, entity_b) + + entity_a = Entity('libfreertos.a') + entity_b = Entity('libfreertos.a') + self.assertEqual(entity_a, entity_b) + + entity_a = Entity('libfreertos.a', 'croutine') + entity_b = Entity('libfreertos.a', 'croutine') + self.assertEqual(entity_a, entity_b) + + entity_a = Entity('libfreertos.a', 'croutine', 'prvCheckPendingReadyList') + entity_b = Entity('libfreertos.a', 'croutine', 'prvCheckPendingReadyList') + self.assertEqual(entity_a, entity_b) + + def test_compare_none_vs_all(self): + # Two entities might have the same specifity whether + # Entity.ALL is used or not specified; the latter is + # considered less than the former. + entity_a = Entity() + entity_b = Entity(Entity.ALL) + self.assertLess(entity_a, entity_b) + + entity_a = Entity('libfreertos.a') + entity_b = Entity('libfreertos.a', Entity.ALL, Entity.ALL) + self.assertLess(entity_a, entity_b) + + entity_a = Entity('libfreertos.a', 'croutine') + entity_b = Entity('libfreertos.a', 'croutine', Entity.ALL) + self.assertLess(entity_a, entity_b) + + def test_compare_same_specificity(self): + # Test that entities will be compared alphabetically + # when the specificities are the same. + entity_a = Entity('libfreertos_a.a') + entity_b = Entity('libfreertos_b.a') + self.assertLess(entity_a, entity_b) + + entity_a = Entity('libfreertos_b.a', 'croutine_a') + entity_b = Entity('libfreertos_a.a', 'croutine_b') + self.assertLess(entity_b, entity_a) + + entity_a = Entity('libfreertos.a', 'croutine', 'prvCheckPendingReadyList_a') + entity_b = Entity('libfreertos.a', 'croutine', 'prvCheckPendingReadyList_b') + self.assertLess(entity_a, entity_b) + + entity_a = Entity('libfreertos.a', 'croutine_b', 'prvCheckPendingReadyList_a') + entity_b = Entity('libfreertos.a', 'croutine_a', 'prvCheckPendingReadyList_b') + self.assertLess(entity_b, entity_a) + + entity_a = Entity('libfreertos_a.a', 'croutine_b', 'prvCheckPendingReadyList_a') + entity_b = Entity('libfreertos_b.a', 'croutine_a', 'prvCheckPendingReadyList_b') + self.assertLess(entity_a, entity_b) + + def test_compare_all_non_character(self): + # Test that Entity.ALL is not treated as an + # ordinary character in comparisons. + entity_a = Entity(Entity.ALL) + entity_b = Entity(chr(ord(Entity.ALL[0]) - 1)) + + self.assertLess(entity_a, entity_b) + + entity_a = Entity('libfreertos.a', Entity.ALL) + entity_b = Entity('libfreertos.a', chr(ord(Entity.ALL[0]) - 1)) + + self.assertLess(entity_a, entity_b) + + entity_a = Entity('libfreertos.a', 'croutine', '*') + entity_b = Entity('libfreertos.a', 'croutine', chr(ord(Entity.ALL[0]) - 1)) + + self.assertLess(entity_a, entity_b) + + +class EntityDBTest(unittest.TestCase): + + def setUp(self): + self.entities = EntityDB() + + with open('data/test_entity/libfreertos.a.txt') as objdump: + self.entities.add_sections_info(objdump) + + def test_get_archives(self): + archives = self.entities.get_archives() + self.assertEqual(set(archives), set(['libfreertos.a'])) + + def test_get_objs(self): + objs = self.entities.get_objects('libfreertos.a') + self.assertEqual(set(objs), set(['croutine.S.obj', 'croutine.c.obj', 'croutine.cpp.obj', 'timers.o'])) + + def test_get_sections(self): + # Needs disambugation between possible matches: croutine.S, croutine.c, croutine.cpp + with self.assertRaises(ValueError): + self.entities.get_sections('libfreertos.a', 'croutine') + + # Test disambugation works + sections = self.entities.get_sections('libfreertos.a', 'croutine.c') + expected = set(['.literal.prvCheckPendingReadyList', '.literal.prvCheckDelayedList']) + self.assertEqual(set(sections), expected) + + sections = self.entities.get_sections('libfreertos.a', 'croutine.S') + expected = set(['.debug_frame', '.debug_info', '.debug_abbrev']) + self.assertEqual(set(sections), expected) + + # Test .o extension works + sections = self.entities.get_sections('libfreertos.a', 'timers') + expected = set(['.literal.prvGetNextExpireTime', '.literal.prvInsertTimerInActiveList']) + self.assertEqual(set(sections), expected) + + def test_parsing(self): + # Tests parsing objdump with the following: + # + # - non-ascii characters + # - different architecture string + # - different column entries for each sections + # - unexpected 'comments' + with open('data/test_entity/parse_test.txt') as objdump: + self.entities.add_sections_info(objdump) + + sections = self.entities.get_sections('ěščřžýáíé.a', 'croutine') + self.assertEqual(set(sections), set(['.text', '.data', '.bss'])) + + sections = self.entities.get_sections('ěščřžýáíé.a', 'FreeRTOS-ěščřžýáíé') + self.assertEqual(set(sections), set(['.literal.ěščřžýáíé'])) + + +if __name__ == '__main__': + unittest.main() diff --git a/tools/ldgen/test/test_output_commands.py b/tools/ldgen/test/test_output_commands.py new file mode 100755 index 0000000000..fd5a5fb08e --- /dev/null +++ b/tools/ldgen/test/test_output_commands.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python +# +# Copyright 2018-2019 Espressif Systems (Shanghai) PTE LTD +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import sys +import unittest + +try: + from output_commands import InputSectionDesc +except ImportError: + sys.path.append('../') + from output_commands import InputSectionDesc + +from entity import Entity + +SECTIONS = ['.text', '.text.*', '.literal', '.literal.*'] + +FREERTOS = Entity('libfreertos.a') +CROUTINE = Entity('libfreertos.a', 'croutine') + + +class InputSectionDescTest(unittest.TestCase): + + def test_output_00(self): + # Test default (catch-all) command + expected = '*(.literal .literal.* .text .text.*)' + + desc = InputSectionDesc(Entity(), SECTIONS) + self.assertEqual(expected, str(desc)) + + desc = InputSectionDesc(Entity(Entity.ALL), SECTIONS) + self.assertEqual(expected, str(desc)) + + def test_output_01(self): + # Test library placement command + expected = '*libfreertos.a:(.literal .literal.* .text .text.*)' + + desc = InputSectionDesc(Entity('libfreertos.a'), SECTIONS) + self.assertEqual(expected, str(desc)) + + desc = InputSectionDesc(Entity('libfreertos.a', Entity.ALL), SECTIONS) + self.assertEqual(expected, str(desc)) + + desc = InputSectionDesc(Entity('libfreertos.a', None, Entity.ALL), SECTIONS) + self.assertEqual(expected, str(desc)) + + desc = InputSectionDesc(Entity('libfreertos.a', Entity.ALL, Entity.ALL), SECTIONS) + self.assertEqual(expected, str(desc)) + + def test_output_02(self): + # Test object placement command + expected = '*libfreertos.a:croutine.*(.literal .literal.* .text .text.*)' + + desc = InputSectionDesc(Entity('libfreertos.a', 'croutine'), SECTIONS) + self.assertEqual(expected, str(desc)) + + desc = InputSectionDesc(Entity('libfreertos.a', 'croutine'), SECTIONS) + self.assertEqual(expected, str(desc)) + + desc = InputSectionDesc(Entity('libfreertos.a', 'croutine', Entity.ALL), SECTIONS) + self.assertEqual(expected, str(desc)) + + # Disambugated placement + expected = '*libfreertos.a:croutine.c.*(.literal .literal.* .text .text.*)' + + desc = InputSectionDesc(Entity('libfreertos.a', 'croutine.c'), SECTIONS) + self.assertEqual(expected, str(desc)) + + def test_output_03(self): + # Invalid entity specification + with self.assertRaises(AssertionError): + InputSectionDesc(Entity('libfreertos.a', 'croutine', 'prvCheckPendingReadyList'), SECTIONS) + + with self.assertRaises(AssertionError): + InputSectionDesc(Entity('libfreertos.a', 'croutine'), SECTIONS, [Entity()]) + + with self.assertRaises(AssertionError): + InputSectionDesc(Entity('libfreertos.a', 'croutine'), SECTIONS, [Entity('libfreertos.a', 'croutine', 'prvCheckPendingReadyList')]) + + def test_output_04(self): + # Test exclusions + + # Library + expected = ('*libfreertos.a:croutine.*' + '(EXCLUDE_FILE(*libfreertos.a) ' + '.literal EXCLUDE_FILE(*libfreertos.a) ' + '.literal.* EXCLUDE_FILE(*libfreertos.a) ' + '.text EXCLUDE_FILE(*libfreertos.a) .text.*)') + desc = InputSectionDesc(CROUTINE, SECTIONS, [FREERTOS]) + self.assertEqual(expected, str(desc)) + + # Object + expected = ('*libfreertos.a:croutine.*' + '(EXCLUDE_FILE(*libfreertos.a:croutine.*) ' + '.literal EXCLUDE_FILE(*libfreertos.a:croutine.*) ' + '.literal.* EXCLUDE_FILE(*libfreertos.a:croutine.*) ' + '.text EXCLUDE_FILE(*libfreertos.a:croutine.*) .text.*)') + desc = InputSectionDesc(CROUTINE, SECTIONS, [CROUTINE]) + self.assertEqual(expected, str(desc)) + + # Multiple exclusions + expected = ('*libfreertos.a:croutine.*' + '(EXCLUDE_FILE(*libfreertos.a *libfreertos.a:croutine.*) ' + '.literal EXCLUDE_FILE(*libfreertos.a *libfreertos.a:croutine.*) ' + '.literal.* EXCLUDE_FILE(*libfreertos.a *libfreertos.a:croutine.*) ' + '.text EXCLUDE_FILE(*libfreertos.a *libfreertos.a:croutine.*) .text.*)') + desc = InputSectionDesc(CROUTINE, SECTIONS, [FREERTOS, CROUTINE]) + self.assertEqual(expected, str(desc)) + + # Disambugated exclusion + expected = ('*libfreertos.a:croutine.*' + '(EXCLUDE_FILE(*libfreertos.a:croutine.c.*) ' + '.literal EXCLUDE_FILE(*libfreertos.a:croutine.c.*) ' + '.literal.* EXCLUDE_FILE(*libfreertos.a:croutine.c.*) ' + '.text EXCLUDE_FILE(*libfreertos.a:croutine.c.*) .text.*)') + desc = InputSectionDesc(CROUTINE, SECTIONS, [Entity('libfreertos.a', 'croutine.c')]) + self.assertEqual(expected, str(desc)) + + def test_output_05(self): + # Test empty sections + expected = '*libfreertos.a:croutine.*( )' + + desc = InputSectionDesc(Entity('libfreertos.a', 'croutine'), []) + self.assertEqual(expected, str(desc)) + + +if __name__ == '__main__': + unittest.main() From 7f18c948dcc2f73a39e7489aa478ea69a84f0226 Mon Sep 17 00:00:00 2001 From: Renz Bagaporo Date: Wed, 27 Jan 2021 16:03:24 +0800 Subject: [PATCH 3/4] ldgen: refactor generation tests and description addition --- tools/ldgen/entity.py | 2 +- tools/ldgen/fragments.py | 2 +- tools/ldgen/generation.py | 2 +- tools/ldgen/ldgen.py | 2 +- tools/ldgen/ldgen_common.py | 2 +- tools/ldgen/linker_script.py | 2 +- tools/ldgen/output_commands.py | 2 +- tools/ldgen/sdkconfig.py | 2 +- tools/ldgen/test/test_entity.py | 2 +- tools/ldgen/test/test_fragments.py | 2 +- tools/ldgen/test/test_generation.py | 2018 ++++++++++------------ tools/ldgen/test/test_output_commands.py | 2 +- 12 files changed, 945 insertions(+), 1095 deletions(-) diff --git a/tools/ldgen/entity.py b/tools/ldgen/entity.py index 38e526b460..fde5b47d7d 100644 --- a/tools/ldgen/entity.py +++ b/tools/ldgen/entity.py @@ -1,5 +1,5 @@ # -# Copyright 2018-2019 Espressif Systems (Shanghai) PTE LTD +# Copyright 2021 Espressif Systems (Shanghai) CO LTD # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/tools/ldgen/fragments.py b/tools/ldgen/fragments.py index 7183d5ed64..adfff653c9 100644 --- a/tools/ldgen/fragments.py +++ b/tools/ldgen/fragments.py @@ -1,5 +1,5 @@ # -# Copyright 2018-2019 Espressif Systems (Shanghai) PTE LTD +# Copyright 2021 Espressif Systems (Shanghai) CO LTD # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/tools/ldgen/generation.py b/tools/ldgen/generation.py index d27f40e442..7c3c81bc34 100644 --- a/tools/ldgen/generation.py +++ b/tools/ldgen/generation.py @@ -1,5 +1,5 @@ # -# Copyright 2018-2019 Espressif Systems (Shanghai) PTE LTD +# Copyright 2021 Espressif Systems (Shanghai) CO LTD # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/tools/ldgen/ldgen.py b/tools/ldgen/ldgen.py index 85e99ea59a..381d7f53b9 100755 --- a/tools/ldgen/ldgen.py +++ b/tools/ldgen/ldgen.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright 2018-2019 Espressif Systems (Shanghai) PTE LTD +# Copyright 2021 Espressif Systems (Shanghai) CO LTD # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/tools/ldgen/ldgen_common.py b/tools/ldgen/ldgen_common.py index 55a2a74bc9..ebeb404b36 100644 --- a/tools/ldgen/ldgen_common.py +++ b/tools/ldgen/ldgen_common.py @@ -1,5 +1,5 @@ # -# Copyright 2018-2019 Espressif Systems (Shanghai) PTE LTD +# Copyright 2021 Espressif Systems (Shanghai) CO LTD # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/tools/ldgen/linker_script.py b/tools/ldgen/linker_script.py index d7d3dfc7e3..f82747bed4 100644 --- a/tools/ldgen/linker_script.py +++ b/tools/ldgen/linker_script.py @@ -1,5 +1,5 @@ # -# Copyright 2018-2019 Espressif Systems (Shanghai) PTE LTD +# Copyright 2021 Espressif Systems (Shanghai) CO LTD # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/tools/ldgen/output_commands.py b/tools/ldgen/output_commands.py index 93722f8497..effc94c106 100644 --- a/tools/ldgen/output_commands.py +++ b/tools/ldgen/output_commands.py @@ -1,5 +1,5 @@ # -# Copyright 2018-2019 Espressif Systems (Shanghai) PTE LTD +# Copyright 2021 Espressif Systems (Shanghai) CO LTD # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/tools/ldgen/sdkconfig.py b/tools/ldgen/sdkconfig.py index d41a94f089..b05a6e3b83 100644 --- a/tools/ldgen/sdkconfig.py +++ b/tools/ldgen/sdkconfig.py @@ -1,5 +1,5 @@ # -# Copyright 2018-2019 Espressif Systems (Shanghai) PTE LTD +# Copyright 2021 Espressif Systems (Shanghai) CO LTD # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/tools/ldgen/test/test_entity.py b/tools/ldgen/test/test_entity.py index a7c2db157c..97c1a449d3 100755 --- a/tools/ldgen/test/test_entity.py +++ b/tools/ldgen/test/test_entity.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # coding=utf-8 # -# Copyright 2018-2020 Espressif Systems (Shanghai) PTE LTD +# Copyright 2018-2020 Espressif Systems (Shanghai) CO LTD # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/tools/ldgen/test/test_fragments.py b/tools/ldgen/test/test_fragments.py index 169af566a2..f9f19ce4a7 100755 --- a/tools/ldgen/test/test_fragments.py +++ b/tools/ldgen/test/test_fragments.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright 2018-2019 Espressif Systems (Shanghai) PTE LTD +# Copyright 2021 Espressif Systems (Shanghai) CO LTD # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/tools/ldgen/test/test_generation.py b/tools/ldgen/test/test_generation.py index d544fa43c3..575105a848 100755 --- a/tools/ldgen/test/test_generation.py +++ b/tools/ldgen/test/test_generation.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright 2018-2019 Espressif Systems (Shanghai) PTE LTD +# Copyright 2021 Espressif Systems (Shanghai) CO LTD # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,30 +15,42 @@ # limitations under the License. # +import collections +import fnmatch import os import sys import tempfile import unittest try: - from generation import PlacementRule + from generation import Generation, GenerationException except ImportError: sys.path.append('../') - from generation import PlacementRule + from generation import Generation, GenerationException from io import StringIO +from entity import Entity, EntityDB from fragments import FragmentFile -from generation import GenerationException, GenerationModel, SectionsInfo, TemplateModel +from linker_script import LinkerScript +from output_commands import InputSectionDesc from sdkconfig import SDKConfig +ROOT = Entity('*') -class GenerationModelTest(unittest.TestCase): +FREERTOS = Entity('libfreertos.a') +CROUTINE = Entity('libfreertos.a', 'croutine') +TIMERS = Entity('libfreertos.a', 'timers') + +FREERTOS2 = Entity('libfreertos2.a') + + +class GenerationTest(unittest.TestCase): def setUp(self): - self.model = GenerationModel() - self.sections_info = None - self.script_model = None + self.generation = Generation() + self.entities = None + self.linker_script = None with tempfile.NamedTemporaryFile(delete=False) as f: self.kconfigs_source_file = os.path.join(tempfile.gettempdir(), f.name) @@ -57,17 +69,17 @@ class GenerationModelTest(unittest.TestCase): self.sdkconfig = SDKConfig('data/Kconfig', 'data/sdkconfig') - with open('data/sample.lf') as fragment_file_obj: + with open('data/base.lf') as fragment_file_obj: fragment_file = FragmentFile(fragment_file_obj, self.sdkconfig) - self.model.add_fragments_from_file(fragment_file) + self.generation.add_fragments_from_file(fragment_file) - self.sections_info = SectionsInfo() + self.entities = EntityDB() - with open('data/sections.info') as sections_info_file_obj: - self.sections_info.add_sections_info(sections_info_file_obj) + with open('data/libfreertos.a.txt') as objdump: + self.entities.add_sections_info(objdump) - with open('data/template.ld') as template_file_obj: - self.script_model = TemplateModel(template_file_obj) + with open('data/linker_script.ld') as linker_script: + self.linker_script = LinkerScript(linker_script) @staticmethod def create_fragment_file(contents, name='test_fragment.lf'): @@ -78,1106 +90,1009 @@ class GenerationModelTest(unittest.TestCase): def add_fragments(self, text): fragment_file = self.create_fragment_file(text) fragment_file = FragmentFile(fragment_file, self.sdkconfig) - self.model.add_fragments_from_file(fragment_file) + self.generation.add_fragments_from_file(fragment_file) def write(self, expected, actual): - self.script_model.fill(expected) - self.script_model.write(open('expected.ld', 'w')) + self.linker_script.fill(expected) + self.linker_script.write(open('expected.ld', 'w')) - self.script_model.fill(actual) - self.script_model.write(open('actual.ld', 'w')) + self.linker_script.fill(actual) + self.linker_script.write(open('actual.ld', 'w')) def generate_default_rules(self): - rules = dict() + rules = collections.defaultdict(list) - # flash_text - placement_rules = list() - rule = PlacementRule(None, None, None, self.model.sections['text'].entries, 'flash_text') - placement_rules.append(rule) - rules['flash_text'] = placement_rules - - # flash_rodata - placement_rules = list() - rule = PlacementRule(None, None, None, self.model.sections['rodata'].entries, 'flash_rodata') - placement_rules.append(rule) - rules['flash_rodata'] = placement_rules - - # dram0_data - placement_rules = list() - rule = PlacementRule(None, None, None, self.model.sections['data'].entries | self.model.sections['dram'].entries, 'dram0_data') - placement_rules.append(rule) - rules['dram0_data'] = placement_rules - - # dram0_bss - placement_rules = list() - rule = PlacementRule(None, None, None, self.model.sections['bss'].entries | self.model.sections['common'].entries, 'dram0_bss') - placement_rules.append(rule) - rules['dram0_bss'] = placement_rules - - # iram0_text - placement_rules = list() - rule = PlacementRule(None, None, None, self.model.sections['iram'].entries, 'iram0_text') - placement_rules.append(rule) - rules['iram0_text'] = placement_rules - - # rtc_text - placement_rules = list() - rule = PlacementRule(None, None, None, self.model.sections['rtc_text'].entries, 'rtc_text') - placement_rules.append(rule) - rules['rtc_text'] = placement_rules - - # rtc_data - placement_rules = list() - rule = PlacementRule(None, None, None, self.model.sections['rtc_data'].entries | self.model.sections['rtc_rodata'].entries, 'rtc_data') - placement_rules.append(rule) - rules['rtc_data'] = placement_rules - - # rtc_bss - placement_rules = list() - rule = PlacementRule(None, None, None, self.model.sections['rtc_bss'].entries, 'rtc_bss') - placement_rules.append(rule) - rules['rtc_bss'] = placement_rules + rules['flash_text'].append(InputSectionDesc(ROOT, ['.literal', '.literal.*', '.text', '.text.*'], [])) + rules['flash_rodata'].append(InputSectionDesc(ROOT, ['.rodata', '.rodata.*'], [])) + rules['dram0_data'].append(InputSectionDesc(ROOT, ['.data', '.data.*'], [])) + rules['dram0_data'].append(InputSectionDesc(ROOT, ['.dram', '.dram.*'], [])) + rules['dram0_bss'].append(InputSectionDesc(ROOT, ['.bss', '.bss.*'], [])) + rules['dram0_bss'].append(InputSectionDesc(ROOT, ['COMMON'], [])) + rules['iram0_text'].append(InputSectionDesc(ROOT, ['.iram', '.iram.*'], [])) + rules['rtc_text'].append(InputSectionDesc(ROOT, ['.rtc.text', '.rtc.literal'], [])) + rules['rtc_data'].append(InputSectionDesc(ROOT, ['.rtc.data'], [])) + rules['rtc_data'].append(InputSectionDesc(ROOT, ['.rtc.rodata'], [])) + rules['rtc_bss'].append(InputSectionDesc(ROOT, ['.rtc.bss'], [])) return rules def compare_rules(self, expected, actual): self.assertEqual(set(expected.keys()), set(actual.keys())) - for (target, rules) in actual.items(): + for target in sorted(actual.keys()): + a_cmds = actual[target] + e_cmds = expected[target] - message = 'target: ' + target + self.assertEqual(len(a_cmds), len(e_cmds)) - actual_target_rules = rules - expected_target_rules = expected[target] - - self.assertEqual(len(actual_target_rules), len(expected_target_rules)) - - for actual_target_rule in actual_target_rules: - self.assertTrue(actual_target_rule in expected_target_rules, message + str(actual_target_rule)) - - for expected_target_rule in expected_target_rules: - self.assertTrue(expected_target_rule in actual_target_rules, message + str(expected_target_rule)) + for a, e in zip(a_cmds, e_cmds): + self.assertEqual(a, e) def get_default(self, target, rules): return rules[target][0] def test_rule_generation_default(self): - normal = u""" + # Checks that default rules are generated from + # the default scheme properly and even if no mappings + # are defined. + actual = self.generation.generate_rules(self.entities) + expected = self.generate_default_rules() + + self.compare_rules(expected, actual) + + +class DefaultMappingTest(GenerationTest): + + def test_default_mapping_lib(self): + # Mapping a library with default mapping. This should not emit additional rules, + # other than the default ones. + mapping = u""" [mapping:test] archive: libfreertos.a entries: * (default) """ + self.add_fragments(mapping) + self.test_rule_generation_default() - self.add_fragments(normal) - actual = self.model.generate_rules(self.sections_info) - expected = self.generate_default_rules() - - self.compare_rules(expected, actual) - - def test_rule_generation_nominal_1(self): - normal = u""" + def test_default_mapping_obj(self): + # Mapping an object with default mapping. This should not emit additional rules, + # other than the default ones. + mapping = u""" [mapping:test] archive: libfreertos.a entries: - * (noflash) + croutine (default) """ - self.add_fragments(normal) + self.add_fragments(mapping) + self.test_rule_generation_default() - actual = self.model.generate_rules(self.sections_info) - - expected = self.generate_default_rules() - - flash_text_default = self.get_default('flash_text', expected) - flash_rodata_default = self.get_default('flash_rodata', expected) - - iram0_text_E1 = PlacementRule('libfreertos.a', '*', None, self.model.sections['text'].entries, 'iram0_text') - dram0_data_E1 = PlacementRule('libfreertos.a', '*', None, self.model.sections['rodata'].entries, 'dram0_data') - - # Add the exclusions - flash_text_default.add_exclusion(iram0_text_E1) - flash_rodata_default.add_exclusion(dram0_data_E1) - - # Add to the placement rules list - expected['iram0_text'].append(iram0_text_E1) - expected['dram0_data'].append(dram0_data_E1) - - self.compare_rules(expected, actual) - - def test_rule_generation_nominal_2(self): - normal = u""" + def test_default_mapping_symbol(self): + # Mapping a symbol with default mapping. This should not emit additional rules, + # other than the default ones. + mapping = u""" [mapping:test] archive: libfreertos.a entries: - timers (rtc) + croutine:prvCheckPendingReadyList (default) #1 """ + self.add_fragments(mapping) + self.test_rule_generation_default() - self.add_fragments(normal) - - actual = self.model.generate_rules(self.sections_info) - - expected = self.generate_default_rules() - - flash_text_default = self.get_default('flash_text', expected) - flash_rodata_default = self.get_default('flash_rodata', expected) - dram0_data_default = self.get_default('dram0_data', expected) - dram0_bss_default = self.get_default('dram0_bss', expected) - - rtc_text_E1 = PlacementRule('libfreertos.a', 'timers', None, self.model.sections['text'].entries, 'rtc_text') - rtc_data_E1 = PlacementRule('libfreertos.a', 'timers', None, self.model.sections['data'].entries | self.model.sections['rodata'].entries, 'rtc_data') - rtc_bss_E1 = PlacementRule('libfreertos.a', 'timers', None, self.model.sections['bss'].entries | self.model.sections['common'].entries, 'rtc_bss') - - # Add the exclusions - flash_text_default.add_exclusion(rtc_text_E1) - flash_rodata_default.add_exclusion(rtc_data_E1) - dram0_data_default.add_exclusion(rtc_data_E1) - dram0_bss_default.add_exclusion(rtc_bss_E1) - - # Add the rules - expected['rtc_text'].append(rtc_text_E1) - expected['rtc_data'].append(rtc_data_E1) - expected['rtc_bss'].append(rtc_bss_E1) - - self.compare_rules(expected, actual) - - def test_rule_generation_nominal_3(self): - normal = u""" + def test_default_mapping_all(self): + # Mapping a library, object, and symbol with default mapping. This should not emit additional rules, + # other than the default ones. + mapping = u""" [mapping:test] archive: libfreertos.a entries: - timers (rtc) - * (noflash) + * (default) #1 + croutine (default) #2 + croutine:prvCheckPendingReadyList (default) #3 """ + self.add_fragments(mapping) + self.test_rule_generation_default() - self.add_fragments(normal) - - actual = self.model.generate_rules(self.sections_info) - - expected = self.generate_default_rules() - - flash_text_default = self.get_default('flash_text', expected) - flash_rodata_default = self.get_default('flash_rodata', expected) - dram0_data_default = self.get_default('dram0_data', expected) - dram0_bss_default = self.get_default('dram0_bss', expected) - - rtc_text_E1 = PlacementRule('libfreertos.a', 'timers', None, self.model.sections['text'].entries, 'rtc_text') - rtc_data_E1 = PlacementRule('libfreertos.a', 'timers', None, self.model.sections['data'].entries | self.model.sections['rodata'].entries, 'rtc_data') - rtc_bss_E1 = PlacementRule('libfreertos.a', 'timers', None, self.model.sections['bss'].entries | self.model.sections['common'].entries, 'rtc_bss') - - iram0_text_E2 = PlacementRule('libfreertos.a', '*', None, self.model.sections['text'].entries, 'iram0_text') - dram0_data_E2 = PlacementRule('libfreertos.a', '*', None, self.model.sections['rodata'].entries, 'dram0_data') - - # Add the exclusions - flash_text_default.add_exclusion(iram0_text_E2) - flash_rodata_default.add_exclusion(dram0_data_E2) - - dram0_data_default.add_exclusion(rtc_data_E1) - dram0_bss_default.add_exclusion(rtc_bss_E1) - - iram0_text_E2.add_exclusion(rtc_text_E1) - dram0_data_E2.add_exclusion(rtc_data_E1) - - # Add the rules - expected['iram0_text'].append(iram0_text_E2) - expected['dram0_data'].append(dram0_data_E2) - - expected['rtc_text'].append(rtc_text_E1) - expected['rtc_data'].append(rtc_data_E1) - expected['rtc_bss'].append(rtc_bss_E1) - - self.compare_rules(expected, actual) - - def test_rule_generation_nominal_4(self): - normal = u""" + def test_default_mapping_lib_symbol(self): + # Mapping a library, and symbol with default mapping. This should not emit additional rules, + # other than the default ones. + # + # This is a check needed to make sure generation does not generate + # intermediate commands due to presence of symbol mapping. + mapping = u""" [mapping:test] archive: libfreertos.a entries: - croutine (rtc) - event_groups (noflash) - timers (rtc) + * (default) #1 + croutine:prvCheckPendingReadyList (default) #2 """ + self.add_fragments(mapping) + self.test_rule_generation_default() - self.add_fragments(normal) - - actual = self.model.generate_rules(self.sections_info) - - expected = self.generate_default_rules() - - flash_text_default = self.get_default('flash_text', expected) - flash_rodata_default = self.get_default('flash_rodata', expected) - dram0_data_default = self.get_default('dram0_data', expected) - dram0_bss_default = self.get_default('dram0_bss', expected) - - rtc_text_E1 = PlacementRule('libfreertos.a', 'croutine', None, self.model.sections['text'].entries, 'rtc_text') - rtc_data_E1 = PlacementRule('libfreertos.a', 'croutine', None, self.model.sections['data'].entries | self.model.sections['rodata'].entries, 'rtc_data') - rtc_bss_E1 = PlacementRule('libfreertos.a', 'croutine', None, self.model.sections['bss'].entries | self.model.sections['common'].entries, 'rtc_bss') - - iram0_text_E2 = PlacementRule('libfreertos.a', 'event_groups', None, self.model.sections['text'].entries, 'iram0_text') - dram0_data_E2 = PlacementRule('libfreertos.a', 'event_groups', None, self.model.sections['rodata'].entries, 'dram0_data') - - rtc_text_E3 = PlacementRule('libfreertos.a', 'timers', None, self.model.sections['text'].entries, 'rtc_text') - rtc_data_E3 = PlacementRule('libfreertos.a', 'timers', None, self.model.sections['data'].entries | self.model.sections['rodata'].entries, 'rtc_data') - rtc_bss_E3 = PlacementRule('libfreertos.a', 'timers', None, self.model.sections['bss'].entries | self.model.sections['common'].entries, 'rtc_bss') - - # Add the exclusions - flash_text_default.add_exclusion(rtc_text_E3) - flash_rodata_default.add_exclusion(rtc_data_E3) - dram0_data_default.add_exclusion(rtc_data_E3) - dram0_bss_default.add_exclusion(rtc_bss_E3) - - flash_text_default.add_exclusion(iram0_text_E2) - flash_rodata_default.add_exclusion(dram0_data_E2) - - flash_text_default.add_exclusion(rtc_text_E1) - flash_rodata_default.add_exclusion(rtc_data_E1) - dram0_data_default.add_exclusion(rtc_data_E1) - dram0_bss_default.add_exclusion(rtc_bss_E1) - - # Add the rules - expected['rtc_text'].append(rtc_text_E3) - expected['rtc_data'].append(rtc_data_E3) - expected['rtc_bss'].append(rtc_bss_E3) - - expected['iram0_text'].append(iram0_text_E2) - expected['dram0_data'].append(dram0_data_E2) - - expected['rtc_text'].append(rtc_text_E1) - expected['rtc_data'].append(rtc_data_E1) - expected['rtc_bss'].append(rtc_bss_E1) - - self.compare_rules(expected, actual) - - def test_rule_generation_nominal_5(self): - normal = u""" + def test_default_mapping_obj_symbol(self): + # Mapping a library, and symbol with default mapping. This should not emit additional rules, + # other than the default ones. + # + # This is a check needed to make sure generation does not generate + # intermediate commands due to presence of symbol mapping. + mapping = u""" [mapping:test] archive: libfreertos.a entries: - croutine (rtc) - event_groups (noflash) - timers (rtc) - * (noflash) + croutine (default) #1 + croutine:prvCheckPendingReadyList (default) #2 """ + self.add_fragments(mapping) + self.test_rule_generation_default() - self.add_fragments(normal) - actual = self.model.generate_rules(self.sections_info) +class BasicTest(GenerationTest): + # Test basic and fundamental interactions between typical + # entries. - expected = self.generate_default_rules() - - flash_text_default = self.get_default('flash_text', expected) - flash_rodata_default = self.get_default('flash_rodata', expected) - dram0_data_default = self.get_default('dram0_data', expected) - dram0_bss_default = self.get_default('dram0_bss', expected) - - rtc_text_E1 = PlacementRule('libfreertos.a', 'croutine', None, self.model.sections['text'].entries, 'rtc_text') - rtc_data_E1 = PlacementRule('libfreertos.a', 'croutine', None, self.model.sections['data'].entries | self.model.sections['rodata'].entries, 'rtc_data') - rtc_bss_E1 = PlacementRule('libfreertos.a', 'croutine', None, self.model.sections['bss'].entries | self.model.sections['common'].entries, 'rtc_bss') - - iram0_text_E2 = PlacementRule('libfreertos.a', 'event_groups', None, self.model.sections['text'].entries, 'iram0_text') - dram0_data_E2 = PlacementRule('libfreertos.a', 'event_groups', None, self.model.sections['rodata'].entries, 'dram0_data') - - rtc_text_E3 = PlacementRule('libfreertos.a', 'timers', None, self.model.sections['text'].entries, 'rtc_text') - rtc_data_E3 = PlacementRule('libfreertos.a', 'timers', None, self.model.sections['data'].entries | self.model.sections['rodata'].entries, 'rtc_data') - rtc_bss_E3 = PlacementRule('libfreertos.a', 'timers', None, self.model.sections['bss'].entries | self.model.sections['common'].entries, 'rtc_bss') - - iram0_text_E4 = PlacementRule('libfreertos.a', '*', None, self.model.sections['text'].entries, 'iram0_text') - dram0_data_E4 = PlacementRule('libfreertos.a', '*', None, self.model.sections['rodata'].entries, 'dram0_data') - - # Add the exclusions - flash_text_default.add_exclusion(iram0_text_E4) - flash_rodata_default.add_exclusion(dram0_data_E4) - - iram0_text_E4.add_exclusion(rtc_text_E3) - dram0_data_E4.add_exclusion(rtc_data_E3) - dram0_data_default.add_exclusion(rtc_data_E3) - dram0_bss_default.add_exclusion(rtc_bss_E3) - - iram0_text_E4.add_exclusion(iram0_text_E2) - dram0_data_E4.add_exclusion(dram0_data_E2) - - iram0_text_E4.add_exclusion(rtc_text_E1) - dram0_data_E4.add_exclusion(rtc_data_E1) - dram0_data_default.add_exclusion(rtc_data_E1) - dram0_bss_default.add_exclusion(rtc_bss_E1) - - # Add the rules - expected['iram0_text'].append(iram0_text_E4) - expected['dram0_data'].append(dram0_data_E4) - - expected['rtc_text'].append(rtc_text_E3) - expected['rtc_data'].append(rtc_data_E3) - expected['rtc_bss'].append(rtc_bss_E3) - - expected['iram0_text'].append(iram0_text_E2) - expected['dram0_data'].append(dram0_data_E2) - - expected['rtc_text'].append(rtc_text_E1) - expected['rtc_data'].append(rtc_data_E1) - expected['rtc_bss'].append(rtc_bss_E1) - - self.compare_rules(expected, actual) - - def test_rule_generation_nominal_6(self): - normal = u""" + def test_nondefault_mapping_lib(self, alt=None): + # Test mapping entry different from default for a library. + # There should be exclusions in the default commands for flash_text and flash_rodata: + # + # flash_text + # *((EXCLUDE_FILE(libfreertos.a)) .literal ...) A + # + # Commands placing the entire library in iram, dram should be generated: + # + # iram0_text + # *(.iram ...) + # *libfreertos.a(.literal ...) B + mapping = u""" [mapping:test] archive: libfreertos.a entries: - croutine:prvCheckPendingReadyList (noflash) + * (noflash) #1 """ - - self.add_fragments(normal) - - actual = self.model.generate_rules(self.sections_info) - + self.add_fragments(alt if alt else mapping) + actual = self.generation.generate_rules(self.entities) expected = self.generate_default_rules() - flash_text_default = self.get_default('flash_text', expected) - flash_rodata_default = self.get_default('flash_rodata', expected) + flash_text = expected['flash_text'] + flash_rodata = expected['flash_rodata'] + iram0_text = expected['iram0_text'] + dram0_data = expected['dram0_data'] - iram0_text_E1 = PlacementRule('libfreertos.a', 'croutine', 'prvCheckPendingReadyList', self.model.sections['text'].entries, 'iram0_text') - dram0_data_E1 = PlacementRule('libfreertos.a', 'croutine', 'prvCheckPendingReadyList', self.model.sections['rodata'].entries, 'dram0_data') + # Generate exclusions in flash_text and flash_rodata A + flash_text[0].exclusions.add(FREERTOS) + flash_rodata[0].exclusions.add(FREERTOS) - iram0_text_E1_extra = PlacementRule('libfreertos.a', 'croutine', None, ['.text.*', '.literal.*'], 'flash_text') - dram0_data_E1_extra = PlacementRule('libfreertos.a', 'croutine', None, ['.rodata.*'], 'flash_rodata') - - # Add the exclusions - flash_text_default.add_exclusion(iram0_text_E1_extra, self.sections_info) - flash_rodata_default.add_exclusion(dram0_data_E1_extra, self.sections_info) - - iram0_text_E1_extra.add_exclusion(iram0_text_E1, self.sections_info) - dram0_data_E1_extra.add_exclusion(dram0_data_E1, self.sections_info) - - # Add the rules - expected['flash_text'].append(iram0_text_E1_extra) - expected['flash_rodata'].append(dram0_data_E1_extra) - - expected['iram0_text'].append(iram0_text_E1) - expected['dram0_data'].append(dram0_data_E1) + # Input section commands in iram_text and dram0_data for #1 B + iram0_text.append(InputSectionDesc(FREERTOS, flash_text[0].sections, [])) + dram0_data.append(InputSectionDesc(FREERTOS, flash_rodata[0].sections, [])) self.compare_rules(expected, actual) - def test_rule_generation_nominal_7(self): - normal = u""" + def test_nondefault_mapping_obj(self, alt=None): + # Test mapping entry different from default for an object. + # There should be exclusions in the default commands for flash_text and flash_rodata: + # + # flash_text + # *((EXCLUDE_FILE(libfreertos.a:croutine)) .literal ...) A + # + # Commands placing the entire library in iram, dram should be generated: + # + # iram0_text + # *(.iram ...) + # *libfreertos.a:croutine(.literal ...) B + mapping = u""" [mapping:test] archive: libfreertos.a entries: - croutine:prvCheckPendingReadyList (noflash) - croutine:prvCheckDelayedList (noflash) - croutine:xCoRoutineCreate (noflash) + croutine (noflash) #1 """ - self.add_fragments(normal) - - actual = self.model.generate_rules(self.sections_info) - + self.add_fragments(alt if alt else mapping) + actual = self.generation.generate_rules(self.entities) expected = self.generate_default_rules() - flash_text_default = self.get_default('flash_text', expected) - flash_rodata_default = self.get_default('flash_rodata', expected) + flash_text = expected['flash_text'] + flash_rodata = expected['flash_rodata'] + iram0_text = expected['iram0_text'] + dram0_data = expected['dram0_data'] - iram0_text_E1 = PlacementRule('libfreertos.a', 'croutine', 'prvCheckPendingReadyList', self.model.sections['text'].entries, 'iram0_text') - dram0_data_E1 = PlacementRule('libfreertos.a', 'croutine', 'prvCheckPendingReadyList', self.model.sections['rodata'].entries, 'dram0_data') + # Generate exclusions in flash_text and flash_rodata A + flash_text[0].exclusions.add(CROUTINE) + flash_rodata[0].exclusions.add(CROUTINE) - iram0_text_E2 = PlacementRule('libfreertos.a', 'croutine', 'prvCheckDelayedList', self.model.sections['text'].entries, 'iram0_text') - dram0_data_E2 = PlacementRule('libfreertos.a', 'croutine', 'prvCheckDelayedList', self.model.sections['rodata'].entries, 'dram0_data') - - iram0_text_E3 = PlacementRule('libfreertos.a', 'croutine', 'xCoRoutineCreate', self.model.sections['text'].entries, 'iram0_text') - dram0_data_E3 = PlacementRule('libfreertos.a', 'croutine', 'xCoRoutineCreate', self.model.sections['rodata'].entries, 'dram0_data') - - flash_text_extra = PlacementRule('libfreertos.a', 'croutine', None, ['.text.*', '.literal.*'], 'flash_text') - flash_rodata_extra = PlacementRule('libfreertos.a', 'croutine', None, ['.rodata.*'], 'flash_rodata') - - # Add the exclusions - flash_text_default.add_exclusion(flash_text_extra, self.sections_info) - flash_rodata_default.add_exclusion(flash_rodata_extra, self.sections_info) - - flash_text_extra.add_exclusion(iram0_text_E1, self.sections_info) - flash_rodata_extra.add_exclusion(dram0_data_E1, self.sections_info) - - flash_text_extra.add_exclusion(iram0_text_E2, self.sections_info) - flash_rodata_extra.add_exclusion(dram0_data_E2, self.sections_info) - - flash_text_extra.add_exclusion(iram0_text_E3, self.sections_info) - flash_rodata_extra.add_exclusion(dram0_data_E3, self.sections_info) - - # Add the rules - expected['flash_text'].append(flash_text_extra) - expected['flash_rodata'].append(flash_rodata_extra) - - expected['iram0_text'].append(iram0_text_E1) - expected['dram0_data'].append(dram0_data_E1) - - expected['iram0_text'].append(iram0_text_E2) - expected['dram0_data'].append(dram0_data_E2) - - expected['iram0_text'].append(iram0_text_E3) - expected['dram0_data'].append(dram0_data_E3) + # Input section commands in iram_text and dram0_data for #1 B + iram0_text.append(InputSectionDesc(CROUTINE, flash_text[0].sections, [])) + dram0_data.append(InputSectionDesc(CROUTINE, flash_rodata[0].sections, [])) self.compare_rules(expected, actual) - def test_rule_generation_nominal_8(self): - normal = u""" + def test_nondefault_mapping_symbol(self): + # Test mapping entry different from default for symbol. + # There should be exclusions in the default commands for flash_text, as well as the implicit intermediate object command + # with an exclusion from default: + # + # flash_text + # *((EXCLUDE_FILE(libfreertos.a:croutine)) .literal ...) A + # *libfreertos.a:croutine(.literal .literal.prvCheckDelayedList ...) B + # + # Commands placing the entire library in iram should be generated: + # + # iram0_text + # *(.iram ...) + # *libfreertos.a:croutine(.text.prvCheckPendingReadyList .literal.prvCheckPendingReadyList) C + mapping = u""" [mapping:test] archive: libfreertos.a entries: - croutine:prvCheckPendingReadyList (noflash) - croutine:prvCheckDelayedList (rtc) - croutine:xCoRoutineCreate (noflash) + croutine:prvCheckPendingReadyList (noflash) #1 """ - - self.add_fragments(normal) - - actual = self.model.generate_rules(self.sections_info) - + self.add_fragments(mapping) + actual = self.generation.generate_rules(self.entities) expected = self.generate_default_rules() - flash_text_default = self.get_default('flash_text', expected) - flash_rodata_default = self.get_default('flash_rodata', expected) - dram0_data_default = self.get_default('dram0_data', expected) - dram0_bss_default = self.get_default('dram0_bss', expected) + flash_text = expected['flash_text'] + iram0_text = expected['iram0_text'] - iram0_text_E1 = PlacementRule('libfreertos.a', 'croutine', 'prvCheckPendingReadyList', self.model.sections['text'].entries, 'iram0_text') - dram0_data_E1 = PlacementRule('libfreertos.a', 'croutine', 'prvCheckPendingReadyList', self.model.sections['rodata'].entries, 'dram0_data') + # Generate exclusion in flash_text A + flash_text[0].exclusions.add(CROUTINE) - rtc_text_E2 = PlacementRule('libfreertos.a', 'croutine', 'prvCheckDelayedList', self.model.sections['text'].entries, 'rtc_text') - rtc_data_E2 = PlacementRule('libfreertos.a', 'croutine', 'prvCheckDelayedList', - self.model.sections['data'].entries | self.model.sections['rodata'].entries, 'rtc_data') - rtc_bss_E2 = PlacementRule('libfreertos.a', 'croutine', 'prvCheckDelayedList', - self.model.sections['bss'].entries | self.model.sections['common'].entries, 'rtc_bss') + # Generate intermediate command B + # List all relevant sections except the symbol + # being mapped + croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine') + filtered_sections = fnmatch.filter(croutine_sections, '.literal.*') + filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*')) - iram0_text_E3 = PlacementRule('libfreertos.a', 'croutine', 'xCoRoutineCreate', self.model.sections['text'].entries, 'iram0_text') - dram0_data_E3 = PlacementRule('libfreertos.a', 'croutine', 'xCoRoutineCreate', self.model.sections['rodata'].entries, 'dram0_data') + filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')] + filtered_sections.append('.text') - flash_text_extra = PlacementRule('libfreertos.a', 'croutine', None, ['.text.*', '.literal.*'], 'flash_text') - flash_rodata_extra = PlacementRule('libfreertos.a', 'croutine', None, ['.rodata.*'], 'flash_rodata') - dram0_data_extra = PlacementRule('libfreertos.a', 'croutine', None, ['.data.*'], 'dram0_data') - dram0_bss_extra = PlacementRule('libfreertos.a', 'croutine', None, ['.bss.*'], 'dram0_bss') + flash_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), [])) - # Add the exclusions - flash_text_default.add_exclusion(flash_text_extra, self.sections_info) - flash_rodata_default.add_exclusion(flash_rodata_extra, self.sections_info) - dram0_data_default.add_exclusion(dram0_data_extra, self.sections_info) - dram0_bss_default.add_exclusion(dram0_bss_extra, self.sections_info) - - flash_text_extra.add_exclusion(iram0_text_E1, self.sections_info) - flash_rodata_extra.add_exclusion(dram0_data_E1, self.sections_info) - - flash_text_extra.add_exclusion(rtc_text_E2, self.sections_info) - dram0_data_extra.add_exclusion(rtc_data_E2, self.sections_info) - flash_rodata_extra.add_exclusion(rtc_data_E2, self.sections_info) - dram0_bss_extra.add_exclusion(rtc_bss_E2, self.sections_info) - - flash_text_extra.add_exclusion(iram0_text_E3, self.sections_info) - flash_rodata_extra.add_exclusion(dram0_data_E3, self.sections_info) - - # Add the rules - expected['flash_text'].append(flash_text_extra) - expected['flash_rodata'].append(flash_rodata_extra) - expected['dram0_data'].append(dram0_data_extra) - expected['dram0_bss'].append(dram0_bss_extra) - - expected['iram0_text'].append(iram0_text_E1) - expected['dram0_data'].append(dram0_data_E1) - - expected['rtc_text'].append(rtc_text_E2) - expected['rtc_data'].append(rtc_data_E2) - expected['rtc_bss'].append(rtc_bss_E2) - - expected['iram0_text'].append(iram0_text_E3) - expected['dram0_data'].append(dram0_data_E3) + # Input section commands in iram_text for #1 C + iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), [])) self.compare_rules(expected, actual) - def test_rule_generation_nominal_9(self): - normal = u""" + def test_default_symbol_nondefault_lib(self): + # Test default symbol mapping with different lib mapping. This should create an implicit intermediate object command. + # The significant targets are flash_text, flash_rodata, iram0_text, dram0_data. + # + # flash_text + # *(EXCLUDE_FILE(libfreertos.a) .text ...) A + # libfreertos.a:croutine (.text.prvCheckPendingReadyList .literal.prvCheckPendingReadyList) B + # + # flash_rodata + # *(EXCLUDE_FILE(libfreertos.a) .rodata ...) A + # + # iram0_text + # * ( .iram ...) + # libfreertos.a (EXCLUDE_FILE(libfreertos:croutine) .text ...) C.1 + # *libfreertos.a:croutine(.literal .literal.prvCheckDelayedList ...) D + # + # dram0_data + # * ( .dram ...) + # libfreertos.a ( .rodata ...) C.2 + # + # Only default commands are in the other targets. + mapping = u""" [mapping:test] archive: libfreertos.a entries: - croutine:prvCheckDelayedList (rtc) - croutine (noflash) + * (noflash) #1 + croutine:prvCheckPendingReadyList (default) #2 """ - self.add_fragments(normal) - - actual = self.model.generate_rules(self.sections_info) - + self.add_fragments(mapping) + actual = self.generation.generate_rules(self.entities) expected = self.generate_default_rules() - flash_text_default = self.get_default('flash_text', expected) - flash_rodata_default = self.get_default('flash_rodata', expected) - dram0_data_default = self.get_default('dram0_data', expected) - dram0_bss_default = self.get_default('dram0_bss', expected) + flash_text = expected['flash_text'] + flash_rodata = expected['flash_rodata'] + iram0_text = expected['iram0_text'] + dram0_data = expected['dram0_data'] - rtc_text_E1 = PlacementRule('libfreertos.a', 'croutine', 'prvCheckDelayedList', self.model.sections['text'].entries, 'rtc_text') - rtc_data_E1 = PlacementRule('libfreertos.a', 'croutine', 'prvCheckDelayedList', - self.model.sections['data'].entries | self.model.sections['rodata'].entries, 'rtc_data') - rtc_bss_E1 = PlacementRule('libfreertos.a', 'croutine', 'prvCheckDelayedList', - self.model.sections['bss'].entries | self.model.sections['common'].entries, 'rtc_bss') + # Exclusions for #1 A + flash_text[0].exclusions.add(FREERTOS) + flash_rodata[0].exclusions.add(FREERTOS) - iram0_text_E2 = PlacementRule('libfreertos.a', 'croutine', None, self.model.sections['text'].entries, 'iram0_text') - dram0_data_E2 = PlacementRule('libfreertos.a', 'croutine', None, self.model.sections['rodata'].entries, 'dram0_data') + # Commands for #1 C.1 & C.2 + # C.1 excludes intermediate command for #2 + iram0_text.append(InputSectionDesc(FREERTOS, flash_text[0].sections, [CROUTINE])) + dram0_data.append(InputSectionDesc(FREERTOS, flash_rodata[0].sections, [])) - dram0_data_extra = PlacementRule('libfreertos.a', 'croutine', None, ['.data.*'], 'dram0_data') - dram0_bss_extra = PlacementRule('libfreertos.a', 'croutine', None, ['.bss.*'], 'dram0_bss') + # Intermediate command for excluding #2 D + croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine') + filtered_sections = fnmatch.filter(croutine_sections, '.literal.*') + filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*')) - # Add the exclusions - flash_text_default.add_exclusion(iram0_text_E2, self.sections_info) - flash_rodata_default.add_exclusion(dram0_data_E2, self.sections_info) + filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')] + filtered_sections.append('.text') - dram0_data_default.add_exclusion(dram0_data_extra, self.sections_info) - dram0_bss_default.add_exclusion(dram0_bss_extra, self.sections_info) + iram0_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), [])) - dram0_data_extra.add_exclusion(rtc_data_E1, self.sections_info) - dram0_bss_extra.add_exclusion(rtc_bss_E1, self.sections_info) - - iram0_text_E2.add_exclusion(rtc_text_E1, self.sections_info) - dram0_data_E2.add_exclusion(rtc_data_E1, self.sections_info) - - # Add the rules - expected['dram0_data'].append(dram0_data_extra) - expected['dram0_bss'].append(dram0_bss_extra) - - expected['iram0_text'].append(iram0_text_E2) - expected['dram0_data'].append(dram0_data_E2) - - expected['rtc_text'].append(rtc_text_E1) - expected['rtc_data'].append(rtc_data_E1) - expected['rtc_bss'].append(rtc_bss_E1) + # Command for #2 B + flash_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), [])) self.compare_rules(expected, actual) - def test_rule_generation_nominal_10(self): - normal = u""" + def test_default_symbol_nondefault_obj(self): + # Test default symbol mapping with different obj mapping. Since there is an explicit entry for the object, + # the sections for that object should just be expanded and the symbol section subtracted, to be placed + # using another command. + # + # flash_text + # *(EXCLUDE_FILE(libfreertos.a:croutine) .text ...) A + # libfreertos.a:croutine (.text.prvCheckPendingReadyList .literal.prvCheckPendingReadyList) B + # + # flash_rodata + # *(EXCLUDE_FILE(libfreertos.a:croutine) .rodata ...) A + # + # iram0_text + # *( .iram ...) + # *libfreertos.a:croutine(.literal .literal.prvCheckDelayedList ...) C.1 + # + # dram0_data + # *(.data ..) + # *libfreertos.a:croutine(.rodata ....) C.2 + # + # Only default commands are in the other targets + mapping = u""" [mapping:test] archive: libfreertos.a entries: - croutine:prvCheckDelayedList (rtc) - * (noflash) + croutine (noflash) #1 + croutine:prvCheckPendingReadyList (default) #2 """ - - self.add_fragments(normal) - - actual = self.model.generate_rules(self.sections_info) - + self.add_fragments(mapping) + actual = self.generation.generate_rules(self.entities) expected = self.generate_default_rules() - flash_text_default = self.get_default('flash_text', expected) - flash_rodata_default = self.get_default('flash_rodata', expected) - iram0_text_default = self.get_default('iram0_text', expected) - dram0_data_default = self.get_default('dram0_data', expected) - dram0_bss_default = self.get_default('dram0_bss', expected) + flash_text = expected['flash_text'] + flash_rodata = expected['flash_rodata'] + iram0_text = expected['iram0_text'] + dram0_data = expected['dram0_data'] - rtc_text_E1 = PlacementRule('libfreertos.a', 'croutine', 'prvCheckDelayedList', self.model.sections['text'].entries, 'rtc_text') - rtc_data_E1 = PlacementRule('libfreertos.a', 'croutine', 'prvCheckDelayedList', - self.model.sections['data'].entries | self.model.sections['rodata'].entries, 'rtc_data') - rtc_bss_E1 = PlacementRule('libfreertos.a', 'croutine', 'prvCheckDelayedList', - self.model.sections['bss'].entries | self.model.sections['common'].entries, 'rtc_bss') + # Exclusions for #1 A + flash_text[0].exclusions.add(CROUTINE) + flash_rodata[0].exclusions.add(CROUTINE) - iram0_text_E2 = PlacementRule('libfreertos.a', None, None, self.model.sections['text'].entries, 'iram0_text') - dram0_data_E2 = PlacementRule('libfreertos.a', None, None, self.model.sections['rodata'].entries, 'dram0_data') + # Commands for #1 C.1 & C.2 + # C.1 list relevant sections for libfreertos.a:croutine to + # exclude symbol to map + croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine') + filtered_sections = fnmatch.filter(croutine_sections, '.literal.*') + filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*')) - iram0_text_extra = PlacementRule('libfreertos.a', 'croutine', None, ['.text.*', '.literal.*'], 'iram0_text') - dram0_data_extra = PlacementRule('libfreertos.a', 'croutine', None, ['.data.*', '.rodata.*'], 'dram0_data') - dram0_bss_extra = PlacementRule('libfreertos.a', 'croutine', None, ['.bss.*'], 'dram0_bss') + filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')] + filtered_sections.append('.text') - # Add the exclusions - flash_text_default.add_exclusion(iram0_text_E2, self.sections_info) - flash_rodata_default.add_exclusion(dram0_data_E2, self.sections_info) + iram0_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), [])) + dram0_data.append(InputSectionDesc(CROUTINE, flash_rodata[0].sections, [])) - iram0_text_default.add_exclusion(iram0_text_extra, self.sections_info) - dram0_data_default.add_exclusion(dram0_data_extra, self.sections_info) - dram0_bss_default.add_exclusion(dram0_bss_extra, self.sections_info) - - iram0_text_E2.add_exclusion(iram0_text_extra, self.sections_info) - dram0_data_E2.add_exclusion(dram0_data_extra, self.sections_info) - - iram0_text_extra.add_exclusion(rtc_text_E1, self.sections_info) - dram0_data_extra.add_exclusion(rtc_data_E1, self.sections_info) - dram0_bss_extra.add_exclusion(rtc_bss_E1, self.sections_info) - - # Add the rules - expected['iram0_text'].append(iram0_text_extra) - expected['dram0_data'].append(dram0_data_extra) - expected['dram0_bss'].append(dram0_bss_extra) - - expected['iram0_text'].append(iram0_text_E2) - expected['dram0_data'].append(dram0_data_E2) - - expected['rtc_text'].append(rtc_text_E1) - expected['rtc_data'].append(rtc_data_E1) - expected['rtc_bss'].append(rtc_bss_E1) + # Command for #2 B + flash_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), [])) self.compare_rules(expected, actual) - def test_rule_generation_nominal_11(self): - normal = u""" + def test_default_nondefault_alternating(self): + # Here, each of the entries map sections to something different + # than its one-level-up entry. + # + # * text -> flash, rodata -> flash + # libfreertos.a text -> iram, rodata -> dram + # libfreertos.a:croutine text -> flash, rodata -> flash + # croutine:prvCheckPendingReadyList text -> iram + # + # The significant targets are flash_text, flash_rodata, iram0_text, and dram0_data. + # + # flash_text + # *(EXCLUDE_FILE(libfreertos.a) .text ...) A + # *libfreertos.a:croutine(.literal .literal.prvCheckDelayedList ...) B.1 + # + # flash_rodata + # *(EXCLUDE_FILE(libfreertos.a) .rodata ...) A + # *libfreertos.a:croutine(.rodata .rodata.*) B.2 + # + # iram0_text + # * ( .iram ...) + # libfreertos.a (EXCLUDE_FILE(libfreertos:croutine) .text ...) C + # libfreertos.a:croutine (.text.prvCheckPendingReadyList .literal.prvCheckPendingReadyList) D + # + # dram0_data + # * ( .dram ...) + # libfreertos.a (EXCLUDE_FILE(libfreertos:croutine) .rodata ...) C + # + # For the other targets only the default commands should be present. + mapping = u""" [mapping:test] archive: libfreertos.a entries: - croutine:prvCheckDelayedList (noflash) - croutine (rtc) - * (noflash) + * (noflash) #1 + croutine (default) #2 + croutine:prvCheckPendingReadyList (noflash) #3 """ - self.add_fragments(normal) - - actual = self.model.generate_rules(self.sections_info) - + self.add_fragments(mapping) + actual = self.generation.generate_rules(self.entities) expected = self.generate_default_rules() - flash_text_default = self.get_default('flash_text', expected) - flash_rodata_default = self.get_default('flash_rodata', expected) - iram0_text_default = self.get_default('iram0_text', expected) - dram0_data_default = self.get_default('dram0_data', expected) - dram0_bss_default = self.get_default('dram0_bss', expected) + flash_text = expected['flash_text'] + flash_rodata = expected['flash_rodata'] + iram0_text = expected['iram0_text'] + dram0_data = expected['dram0_data'] - iram0_text_E1 = PlacementRule('libfreertos.a', 'croutine', 'prvCheckDelayedList', self.model.sections['text'].entries, 'iram0_text') - dram0_data_E1 = PlacementRule('libfreertos.a', 'croutine', 'prvCheckDelayedList', self.model.sections['rodata'].entries, 'dram0_data') + # Exclusions for #1 A + # Only for flash_text and flash_rodata + flash_text[0].exclusions.add(FREERTOS) + flash_rodata[0].exclusions.add(FREERTOS) - rtc_text_E2 = PlacementRule('libfreertos.a', 'croutine', None, self.model.sections['text'].entries, 'rtc_text') - rtc_data_E2 = PlacementRule('libfreertos.a', 'croutine', None, self.model.sections['data'].entries | self.model.sections['rodata'].entries, 'rtc_data') - rtc_bss_E2 = PlacementRule('libfreertos.a', 'croutine', None, self.model.sections['bss'].entries | self.model.sections['common'].entries, 'rtc_bss') + # Commands for #1 C + # with exclusions for #2 + iram0_text.append(InputSectionDesc(FREERTOS, flash_text[0].sections, [CROUTINE])) + dram0_data.append(InputSectionDesc(FREERTOS, flash_rodata[0].sections, [CROUTINE])) - iram0_text_E3 = PlacementRule('libfreertos.a', None, None, self.model.sections['text'].entries, 'iram0_text') - dram0_data_E3 = PlacementRule('libfreertos.a', None, None, self.model.sections['rodata'].entries, 'dram0_data') + # Commands for #2 B.1 + flash_rodata.append(InputSectionDesc(CROUTINE, flash_rodata[0].sections, [])) - # Add the exclusions - flash_text_default.add_exclusion(iram0_text_E3, self.sections_info) - flash_rodata_default.add_exclusion(dram0_data_E3, self.sections_info) - iram0_text_default.add_exclusion(rtc_text_E2, self.sections_info) - dram0_data_default.add_exclusion(rtc_data_E2, self.sections_info) - dram0_bss_default.add_exclusion(rtc_bss_E2, self.sections_info) + # List all relevant sections in case of flash_text B.2 + # as exclusion for #3 + croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine') + filtered_sections = fnmatch.filter(croutine_sections, '.literal.*') + filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*')) - iram0_text_E3.add_exclusion(rtc_text_E2, self.sections_info) - dram0_data_E3.add_exclusion(rtc_data_E2, self.sections_info) + filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')] + filtered_sections.append('.text') - rtc_text_E2.add_exclusion(iram0_text_E1, self.sections_info) - rtc_data_E2.add_exclusion(dram0_data_E1, self.sections_info) + flash_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), [])) - # Add the rules - expected['iram0_text'].append(iram0_text_E1) - expected['dram0_data'].append(dram0_data_E1) - - expected['rtc_text'].append(rtc_text_E2) - expected['rtc_data'].append(rtc_data_E2) - expected['rtc_bss'].append(rtc_bss_E2) - - expected['iram0_text'].append(iram0_text_E3) - expected['dram0_data'].append(dram0_data_E3) + # Command for #3 D + iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), [])) self.compare_rules(expected, actual) - def test_rule_generation_nominal_12(self): - normal = u""" + def test_nondefault_but_same_lib_and_obj(self): + # Extension of DefaultMappingTest. Commands should not be generated for #2, since it does similar mapping + # to #1. Output is similar to test_different_mapping_lib. + mapping = u""" [mapping:test] archive: libfreertos.a entries: - croutine:prvCheckDelayedList (rtc) - croutine (noflash) - * (rtc) + * (noflash) #1 + croutine (noflash) #2 +""" + self.test_nondefault_mapping_lib(mapping) + + def test_nondefault_but_same_lib_and_sym(self): + # Extension of DefaultMappingTest. Commands should not be generated for #2, since it does similar mapping + # to #1. Output is similar to test_different_mapping_lib. + mapping = u""" +[mapping:test] +archive: libfreertos.a +entries: + * (noflash) #1 + croutine:prvCheckPendingReadyList (noflash) #2 +""" + self.test_nondefault_mapping_lib(mapping) + + def test_nondefault_but_same_obj_and_sym(self): + # Commands should not be generated for #2, since it does similar mapping + # to #1. Output is similar to test_different_mapping_obj. + mapping = u""" +[mapping:test] +archive: libfreertos.a +entries: + croutine (noflash) #1 + croutine:prvCheckPendingReadyList (noflash) #2 +""" + self.test_nondefault_mapping_obj(mapping) + + def test_multiple_symbols_excluded_from_intermediate_command(self): + # Test mapping multiple symbols from the same object. + # All these symbols must be succesfully excluded from + # the intermediate command. + # + # flash_text + # * (EXCLUDE_FILE(libfreertos.a:croutine) .text ...) A + # libfreertos:croutine(.text ...) B + # + # iram0_text + # + # + mapping = u""" +[mapping:test] +archive: libfreertos.a +entries: + croutine:prvCheckPendingReadyList (noflash) #1 + croutine:prvCheckDelayedList (noflash) #2 """ - self.add_fragments(normal) - - actual = self.model.generate_rules(self.sections_info) - + self.add_fragments(mapping) + actual = self.generation.generate_rules(self.entities) expected = self.generate_default_rules() - flash_text_default = self.get_default('flash_text', expected) - flash_rodata_default = self.get_default('flash_rodata', expected) - dram0_data_default = self.get_default('dram0_data', expected) - dram0_bss_default = self.get_default('dram0_bss', expected) + flash_text = expected['flash_text'] + iram0_text = expected['iram0_text'] - rtc_text_E1 = PlacementRule('libfreertos.a', 'croutine', 'prvCheckDelayedList', self.model.sections['text'].entries, 'rtc_text') - rtc_data_E1 = PlacementRule('libfreertos.a', 'croutine', 'prvCheckDelayedList', - self.model.sections['data'].entries | self.model.sections['rodata'].entries, 'rtc_data') - rtc_bss_E1 = PlacementRule('libfreertos.a', 'croutine', 'prvCheckDelayedList', - self.model.sections['bss'].entries | self.model.sections['common'].entries, 'rtc_bss') + # Exclusions for #1 & #2 intermediate command A + flash_text[0].exclusions.add(CROUTINE) - iram0_text_E2 = PlacementRule('libfreertos.a', 'croutine', None, self.model.sections['text'].entries, 'iram0_text') - dram0_data_E2 = PlacementRule('libfreertos.a', 'croutine', None, self.model.sections['rodata'].entries, 'dram0_data') + # Intermediate command for #1 & #2 which lists B + # all relevant sections in croutine except prvCheckPendingReadyList + # and prvCheckDelayedList + croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine') + filtered_sections = fnmatch.filter(croutine_sections, '.literal.*') + filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*')) - rtc_text_E3 = PlacementRule('libfreertos.a', None, None, self.model.sections['text'].entries, 'rtc_text') - rtc_data_E3 = PlacementRule('libfreertos.a', None, None, self.model.sections['data'].entries | self.model.sections['rodata'].entries, 'rtc_data') - rtc_bss_E3 = PlacementRule('libfreertos.a', None, None, self.model.sections['bss'].entries | self.model.sections['common'].entries, 'rtc_bss') + filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')] + filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckDelayedList')] + filtered_sections.append('.text') - rtc_data_extra = PlacementRule('libfreertos.a', 'croutine', None, ['.data.*'], 'rtc_data') - rtc_bss_extra = PlacementRule('libfreertos.a', 'croutine', None, ['.bss.*'], 'rtc_bss') + flash_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), [])) - # Add the exclusions - flash_text_default.add_exclusion(rtc_text_E3, self.sections_info) - flash_rodata_default.add_exclusion(rtc_data_E3, self.sections_info) - dram0_data_default.add_exclusion(rtc_data_E3, self.sections_info) - dram0_bss_default.add_exclusion(rtc_bss_E3, self.sections_info) - - rtc_text_E3.add_exclusion(iram0_text_E2, self.sections_info) - rtc_data_E3.add_exclusion(dram0_data_E2, self.sections_info) - rtc_data_E3.add_exclusion(rtc_data_extra, self.sections_info) - rtc_bss_E3.add_exclusion(rtc_bss_extra, self.sections_info) - - rtc_data_extra.add_exclusion(rtc_data_E1, self.sections_info) - rtc_bss_extra.add_exclusion(rtc_bss_E1, self.sections_info) - iram0_text_E2.add_exclusion(rtc_text_E1, self.sections_info) - dram0_data_E2.add_exclusion(rtc_data_E1, self.sections_info) - - # Add the rules - expected['rtc_data'].append(rtc_data_extra) - expected['rtc_bss'].append(rtc_bss_extra) - - expected['rtc_text'].append(rtc_text_E1) - expected['rtc_data'].append(rtc_data_E1) - expected['rtc_bss'].append(rtc_bss_E1) - - expected['iram0_text'].append(iram0_text_E2) - expected['dram0_data'].append(dram0_data_E2) - - expected['rtc_text'].append(rtc_text_E3) - expected['rtc_data'].append(rtc_data_E3) - expected['rtc_bss'].append(rtc_bss_E3) + # Commands for #1 & 2 + iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckDelayedList', '.literal.prvCheckDelayedList']), [])) + iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), [])) self.compare_rules(expected, actual) - def test_rule_generation_nominal_13(self): - normal = u""" + +class AdvancedTest(GenerationTest): + + # Test valid but quirky cases, corner cases, failure cases, and + # cases involving interaction between schemes, other mapping + # fragments. + + def test_same_entity_no_scheme_common(self): + # Test same entity being mapped by schemes that have nothing in common. + # + # noflash_data: rodata -> dram0_data + # noflash_text: text -> iram0_text + # + # This operation should succeed with the following commands: + # + # flash_text + # *(EXCLUDE_FILE(libfreertos.a:croutine) .text ...) A + # + # flash_rodata + # *(EXCLUDE_FILE(libfreertos.a:croutine) .rodata ...) B + # + # iram0_text + # *(.iram ...) + # *libfreertos.a:croutine(.text .text.* ...) C + # + # dram0_data + # *(.data ..) + # *(.dram ...) + # *libfreertos.a:croutine(.rodata .rodata.*) D + mapping = u""" [mapping:test] archive: libfreertos.a entries: - croutine:prvCheckDelayedList (noflash) - event_groups:xEventGroupCreate (noflash) - croutine (rtc) - event_groups (rtc) - * (noflash) + croutine (noflash_text) #1 + croutine (noflash_data) #2 """ - - self.add_fragments(normal) - - actual = self.model.generate_rules(self.sections_info) - + self.add_fragments(mapping) + actual = self.generation.generate_rules(self.entities) expected = self.generate_default_rules() - flash_text_default = self.get_default('flash_text', expected) - flash_rodata_default = self.get_default('flash_rodata', expected) - dram0_data_default = self.get_default('dram0_data', expected) - dram0_bss_default = self.get_default('dram0_bss', expected) + flash_text = expected['flash_text'] + flash_rodata = expected['flash_rodata'] + iram0_text = expected['iram0_text'] + dram0_data = expected['dram0_data'] - iram0_text_E1 = PlacementRule('libfreertos.a', 'croutine', 'prvCheckDelayedList', self.model.sections['text'].entries, 'iram0_text') - dram0_data_E1 = PlacementRule('libfreertos.a', 'croutine', 'prvCheckDelayedList', self.model.sections['rodata'].entries, 'dram0_data') + # Exclusions for #1 A + flash_text[0].exclusions.add(CROUTINE) - iram0_text_E2 = PlacementRule('libfreertos.a', 'event_groups', 'xEventGroupCreate', self.model.sections['text'].entries, 'iram0_text') - dram0_data_E2 = PlacementRule('libfreertos.a', 'event_groups', 'xEventGroupCreate', self.model.sections['rodata'].entries, 'dram0_data') + # Exclusions for #2 B + flash_rodata[0].exclusions.add(CROUTINE) - rtc_text_E3 = PlacementRule('libfreertos.a', 'croutine', None, self.model.sections['text'].entries, 'rtc_text') - rtc_data_E3 = PlacementRule('libfreertos.a', 'croutine', None, self.model.sections['data'].entries | self.model.sections['rodata'].entries, 'rtc_data') - rtc_bss_E3 = PlacementRule('libfreertos.a', 'croutine', None, self.model.sections['bss'].entries | self.model.sections['common'].entries, 'rtc_bss') + # Command for #1 C + iram0_text.append(InputSectionDesc(CROUTINE, flash_text[0].sections, [])) - rtc_text_E4 = PlacementRule('libfreertos.a', 'event_groups', None, self.model.sections['text'].entries, 'rtc_text') - rtc_data_E4 = PlacementRule('libfreertos.a', 'event_groups', None, - self.model.sections['data'].entries | self.model.sections['rodata'].entries, 'rtc_data') - rtc_bss_E4 = PlacementRule('libfreertos.a', 'event_groups', None, - self.model.sections['bss'].entries | self.model.sections['common'].entries, 'rtc_bss') - - iram0_text_E5 = PlacementRule('libfreertos.a', None, None, self.model.sections['text'].entries, 'iram0_text') - dram0_data_E5 = PlacementRule('libfreertos.a', None, None, self.model.sections['rodata'].entries, 'dram0_data') - - # Add the exclusions - flash_text_default.add_exclusion(iram0_text_E5, self.sections_info) - flash_rodata_default.add_exclusion(dram0_data_E5, self.sections_info) - dram0_bss_default.add_exclusion(rtc_bss_E3, self.sections_info) - dram0_data_default.add_exclusion(rtc_data_E3, self.sections_info) - dram0_bss_default.add_exclusion(rtc_bss_E4, self.sections_info) - dram0_data_default.add_exclusion(rtc_data_E4, self.sections_info) - - iram0_text_E5.add_exclusion(rtc_text_E3, self.sections_info) - dram0_data_E5.add_exclusion(rtc_data_E3, self.sections_info) - iram0_text_E5.add_exclusion(rtc_text_E4, self.sections_info) - dram0_data_E5.add_exclusion(rtc_data_E4, self.sections_info) - - rtc_text_E4.add_exclusion(iram0_text_E2, self.sections_info) - rtc_data_E4.add_exclusion(dram0_data_E2, self.sections_info) - - rtc_text_E3.add_exclusion(iram0_text_E1, self.sections_info) - rtc_data_E3.add_exclusion(dram0_data_E1, self.sections_info) - - # Add the rules - expected['iram0_text'].append(iram0_text_E1) - expected['dram0_data'].append(dram0_data_E1) - - expected['iram0_text'].append(iram0_text_E2) - expected['dram0_data'].append(dram0_data_E2) - - expected['rtc_text'].append(rtc_text_E3) - expected['rtc_data'].append(rtc_data_E3) - expected['rtc_bss'].append(rtc_bss_E3) - - expected['rtc_text'].append(rtc_text_E4) - expected['rtc_data'].append(rtc_data_E4) - expected['rtc_bss'].append(rtc_bss_E4) - - expected['iram0_text'].append(iram0_text_E5) - expected['dram0_data'].append(dram0_data_E5) + # Command for #2 D + dram0_data.append(InputSectionDesc(CROUTINE, flash_rodata[0].sections, [])) self.compare_rules(expected, actual) - def test_rule_generation_nominal_14(self): - normal = u""" + def test_same_entity_sub_scheme(self): + # Test same entity being mapped by scheme that is a subset of the other. + # + # noflash: text -> iram0_text, rodata -> dram0_data + # noflash_text: text -> iram0_text + # + # `text -> iram0_text` is common between the two schemes. + # + # This operation should succeed with the following commands: + # + # flash_text + # *(EXCLUDE_FILE(libfreertos.a:croutine) .text ...) A + # + # flash_rodata + # *(EXCLUDE_FILE(libfreertos.a:croutine) .rodata ...) B + # + # iram0_text + # *(.iram ...) + # *libfreertos.a:croutine(.text .text.* ...) C + # + # dram0_data + # *(.data ..) + # *(.dram ...) + # *libfreertos.a:croutine(.rodata .rodata.*) D + mapping = u""" [mapping:test] archive: libfreertos.a entries: - croutine:prvCheckDelayedList (noflash) - event_groups:xEventGroupCreate (rtc) - croutine (rtc) - event_groups (noflash) + croutine (noflash) #1 + croutine (noflash_data) #2 """ - - self.add_fragments(normal) - - actual = self.model.generate_rules(self.sections_info) - + self.add_fragments(mapping) + actual = self.generation.generate_rules(self.entities) expected = self.generate_default_rules() - flash_text_default = self.get_default('flash_text', expected) - flash_rodata_default = self.get_default('flash_rodata', expected) - dram0_data_default = self.get_default('dram0_data', expected) - dram0_bss_default = self.get_default('dram0_bss', expected) + flash_text = expected['flash_text'] + flash_rodata = expected['flash_rodata'] + iram0_text = expected['iram0_text'] + dram0_data = expected['dram0_data'] - iram0_text_E1 = PlacementRule('libfreertos.a', 'croutine', 'prvCheckDelayedList', self.model.sections['text'].entries, 'iram0_text') - dram0_data_E1 = PlacementRule('libfreertos.a', 'croutine', 'prvCheckDelayedList', self.model.sections['rodata'].entries, 'dram0_data') + # Exclusions for #1 A + flash_text[0].exclusions.add(CROUTINE) - rtc_text_E2 = PlacementRule('libfreertos.a', 'event_groups', 'xEventGroupCreate', self.model.sections['text'].entries, 'rtc_text') - rtc_data_E2 = PlacementRule('libfreertos.a', 'event_groups', 'xEventGroupCreate', - self.model.sections['data'].entries | self.model.sections['rodata'].entries, 'rtc_data') - rtc_bss_E2 = PlacementRule('libfreertos.a', 'event_groups', 'xEventGroupCreate', - self.model.sections['bss'].entries | self.model.sections['common'].entries, 'rtc_bss') + # Exclusions for #1 & #2 B + flash_rodata[0].exclusions.add(CROUTINE) - rtc_text_E3 = PlacementRule('libfreertos.a', 'croutine', None, self.model.sections['text'].entries, 'rtc_text') - rtc_data_E3 = PlacementRule('libfreertos.a', 'croutine', None, self.model.sections['data'].entries | self.model.sections['rodata'].entries, 'rtc_data') - rtc_bss_E3 = PlacementRule('libfreertos.a', 'croutine', None, self.model.sections['bss'].entries | self.model.sections['common'].entries, 'rtc_bss') + # Command for #1 C + iram0_text.append(InputSectionDesc(CROUTINE, flash_text[0].sections, [])) - iram0_text_E4 = PlacementRule('libfreertos.a', 'event_groups', None, self.model.sections['text'].entries, 'iram0_text') - dram0_data_E4 = PlacementRule('libfreertos.a', 'event_groups', None, self.model.sections['rodata'].entries, 'dram0_data') - - dram0_data_extra = PlacementRule('libfreertos.a', 'event_groups', None, ['.data.*'], 'dram0_data') - dram0_bss_extra = PlacementRule('libfreertos.a', 'event_groups', None, ['.bss.*'], 'dram0_bss') - - # Add the exclusions - flash_text_default.add_exclusion(iram0_text_E4, self.sections_info) - flash_rodata_default.add_exclusion(dram0_data_E4, self.sections_info) - dram0_data_default.add_exclusion(dram0_data_extra, self.sections_info) - dram0_bss_default.add_exclusion(dram0_bss_extra, self.sections_info) - - flash_text_default.add_exclusion(rtc_text_E3, self.sections_info) - flash_rodata_default.add_exclusion(rtc_data_E3, self.sections_info) - dram0_data_default.add_exclusion(rtc_data_E3, self.sections_info) - dram0_bss_default.add_exclusion(rtc_bss_E3, self.sections_info) - - iram0_text_E4.add_exclusion(rtc_text_E2, self.sections_info) - dram0_data_E4.add_exclusion(rtc_data_E2, self.sections_info) - dram0_data_extra.add_exclusion(rtc_data_E2, self.sections_info) - dram0_bss_extra.add_exclusion(rtc_bss_E2, self.sections_info) - - rtc_text_E3.add_exclusion(iram0_text_E1, self.sections_info) - rtc_data_E3.add_exclusion(dram0_data_E1, self.sections_info) - - # Add the rules - expected['iram0_text'].append(iram0_text_E1) - expected['dram0_data'].append(dram0_data_E1) - - expected['rtc_text'].append(rtc_text_E2) - expected['rtc_data'].append(rtc_data_E2) - expected['rtc_bss'].append(rtc_bss_E2) - - expected['rtc_text'].append(rtc_text_E3) - expected['rtc_data'].append(rtc_data_E3) - expected['rtc_bss'].append(rtc_bss_E3) - - expected['iram0_text'].append(iram0_text_E4) - expected['dram0_data'].append(dram0_data_E4) - - expected['dram0_data'].append(dram0_data_extra) - expected['dram0_bss'].append(dram0_bss_extra) + # Command for #1 & #2 D + dram0_data.append(InputSectionDesc(CROUTINE, flash_rodata[0].sections, [])) self.compare_rules(expected, actual) - def test_rule_generation_nominal_15(self): - normal = u""" + def test_same_entity_conflicting_scheme(self, alt=None): + # Test same entity being mapped by scheme conflicting with another. + # + # rtc = text -> rtc_text, rodata -> rtc_data + # noflash = text -> iram0_text, rodata -> dram0_data + # + # This operation should fail. + mapping = u""" [mapping:test] archive: libfreertos.a entries: - croutine (noflash_data) - croutine (noflash_text) + croutine (noflash) #1 + croutine (rtc) #2 """ - - self.add_fragments(normal) - - actual = self.model.generate_rules(self.sections_info) - - expected = self.generate_default_rules() - - flash_text_default = self.get_default('flash_text', expected) - flash_rodata_default = self.get_default('flash_rodata', expected) - - iram0_text_E1 = PlacementRule('libfreertos.a', 'croutine', None, self.model.sections['text'].entries, 'iram0_text') - dram0_data_E1 = PlacementRule('libfreertos.a', 'croutine', None, self.model.sections['rodata'].entries, 'dram0_data') - - # Add the exclusions - flash_text_default.add_exclusion(iram0_text_E1) - flash_rodata_default.add_exclusion(dram0_data_E1) - - # Add the rules - expected['iram0_text'].append(iram0_text_E1) - expected['dram0_data'].append(dram0_data_E1) - - self.compare_rules(expected, actual) - - def test_rule_generation_nominal_16(self): - normal = u""" -[mapping:test] -archive: libfreertos.a -entries: - croutine (noflash_data) - croutine (noflash) -""" - - self.add_fragments(normal) - - actual = self.model.generate_rules(self.sections_info) - - expected = self.generate_default_rules() - - flash_text_default = self.get_default('flash_text', expected) - flash_rodata_default = self.get_default('flash_rodata', expected) - - iram0_text_E1 = PlacementRule('libfreertos.a', 'croutine', None, self.model.sections['text'].entries, 'iram0_text') - dram0_data_E1 = PlacementRule('libfreertos.a', 'croutine', None, self.model.sections['rodata'].entries, 'dram0_data') - - # Add the exclusions - flash_text_default.add_exclusion(iram0_text_E1) - flash_rodata_default.add_exclusion(dram0_data_E1) - - # Add the rules - expected['iram0_text'].append(iram0_text_E1) - expected['dram0_data'].append(dram0_data_E1) - - self.compare_rules(expected, actual) - - def test_rule_generation_conflict(self): - conflict_mapping = u""" -[mapping:test] -archive: libfreertos.a -entries: - croutine (conflict) - croutine (noflash) - -[scheme:conflict] -entries: - rodata -> dram0_data - bss -> dram0_data -""" - self.add_fragments(conflict_mapping) + self.add_fragments(alt if alt else mapping) with self.assertRaises(GenerationException): - self.model.generate_rules(self.sections_info) + self.generation.generate_rules(self.entities) - def test_rule_generation_condition(self): - generation_with_condition = u""" + def test_complex_mapping_case(self, alt=None): + # Test a complex case where an object is mapped using + # one scheme, but a specific symbol in that object is mapped + # using another. Another object and symbol is mapped the other way around. + # + # flash_text + # *(EXCLUDE_FILE(libfreertos.a:croutine libfreertos.a:timers) .text ...) A, B + # + # flash_rodata + # *(EXCLUDE_FILE(libfreertos.a:croutine libfreertos.a:timers) .rodata ...) A, B + # + # dram0_data + # *(EXCLUDE_FILES(libfreertos.a:timers) .data ..) B + # *(.dram ...) + # *libfreertos.a:croutine(.rodata .rodata.*) C + # *libfreertos.a:timers(.rodata.prvProcessReceivedCommands ...) E + # + # dram0_bss + # *(EXCLUDE_FILE(libfreertos.a:timers) .bss .bss.* ...) B + # *(EXCLUDE_FILE(libfreertos.a:timers) COMMON) B + # + # iram0_text + # *(.iram ...) + # *libfreertos.a:croutine(.literal .literal.prvCheckDelayedList ...) C + # *libfreertos.a:timers(.literal .literal.prvProcessReceivedCommands ...) E + # + # rtc_text + # *(rtc.text .rtc.literal) + # libfreertos.a:croutine (.text.prvCheckPendingReadyList .literal.prvCheckPendingReadyList) F + # libfreertos.a:timers (.text .text.prvCheckForValidListAndQueue ...) D.2 + # + # rtc_data + # *(rtc.data) + # *(rtc.rodata) + # libfreertos.a:timers (.data .data.*) D + # libfreertos.a:timers (.rodata ...) D.2 + # + # rtc_bss + # *(rtc.bss .rtc.bss) + # libfreertos.a:timers (.bss .bss.*) D + # libfreertos.a:timers (COMMON) D + mapping = u""" [mapping:test] -archive: lib.a +archive: libfreertos.a entries: - if PERFORMANCE_LEVEL = 1: - obj1 (noflash) - elif PERFORMANCE_LEVEL = 2: - obj1 (noflash) - obj2 (noflash) - elif PERFORMANCE_LEVEL = 3: - obj1 (noflash) - obj2 (noflash) - obj3 (noflash) - else: # PERFORMANCE_LEVEL = 0 - * (default) + croutine (noflash) #1 + timers (rtc) #2 + timers:prvProcessReceivedCommands (noflash) #3 + croutine:prvCheckPendingReadyList (rtc) #4 """ - for perf_level in range(0, 4): - self.sdkconfig.config.syms['PERFORMANCE_LEVEL'].set_value(str(perf_level)) - - self.model.mappings = {} - self.add_fragments(generation_with_condition) - - actual = self.model.generate_rules(self.sections_info) - expected = self.generate_default_rules() - - if perf_level < 4: - for append_no in range(1, perf_level + 1): - flash_text_default = self.get_default('flash_text', expected) - flash_rodata_default = self.get_default('flash_rodata', expected) - - iram_rule = PlacementRule('lib.a', 'obj' + str(append_no), None, self.model.sections['text'].entries, 'iram0_text') - dram_rule = PlacementRule('lib.a', 'obj' + str(append_no), None, self.model.sections['rodata'].entries, 'dram0_data') - - flash_text_default.add_exclusion(iram_rule) - flash_rodata_default.add_exclusion(dram_rule) - - expected['iram0_text'].append(iram_rule) - expected['dram0_data'].append(dram_rule) - - self.compare_rules(expected, actual) - - def test_rule_generation_empty_entries(self): - normal = u""" -[mapping:test] -archive: lib.a -entries: - if PERFORMANCE_LEVEL >= 1: # is false, generate no special rules - obj.a (noflash) -""" - - self.add_fragments(normal) - actual = self.model.generate_rules(self.sections_info) - expected = self.generate_default_rules() # only default rules - self.compare_rules(expected, actual) - - def test_conditional_sections_1(self): - generation_with_condition = u""" -[sections:cond_text_data] -entries: - if PERFORMANCE_LEVEL >= 1: - .text+ - .literal+ - else: - .data+ - -[scheme:cond_noflash] -entries: - if PERFORMANCE_LEVEL >= 1: - cond_text_data -> iram0_text - else: - cond_text_data -> dram0_data - -[mapping:test] -archive: lib.a -entries: - * (cond_noflash) -""" - - self.sdkconfig.config.syms['PERFORMANCE_LEVEL'].set_value('1') - self.add_fragments(generation_with_condition) - - actual = self.model.generate_rules(self.sections_info) + self.add_fragments(alt if alt else mapping) + actual = self.generation.generate_rules(self.entities) expected = self.generate_default_rules() - flash_text_default = self.get_default('flash_text', expected) + flash_text = expected['flash_text'] + flash_rodata = expected['flash_rodata'] + dram0_data = expected['dram0_data'] + iram0_text = expected['iram0_text'] + dram0_bss = expected['dram0_bss'] + rtc_text = expected['rtc_text'] + rtc_data = expected['rtc_data'] + rtc_bss = expected['rtc_bss'] - iram0_text_E1 = PlacementRule('lib.a', '*', None, self.model.sections['text'].entries, 'iram0_text') + # Exclusions for #1 A + flash_text[0].exclusions.add(CROUTINE) + flash_rodata[0].exclusions.add(CROUTINE) - # Add the exclusions - flash_text_default.add_exclusion(iram0_text_E1) + # Exclusions for #2 B + flash_text[0].exclusions.add(TIMERS) + flash_rodata[0].exclusions.add(TIMERS) + dram0_data[0].exclusions.add(TIMERS) + dram0_bss[0].exclusions.add(TIMERS) + dram0_bss[1].exclusions.add(TIMERS) - # Add to the placement rules list - expected['iram0_text'].append(iram0_text_E1) + # Commands for #1 C + # List all relevant sections excluding #4 for text -> iram0_text + croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine') + filtered_sections = fnmatch.filter(croutine_sections, '.literal.*') + filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*')) + + filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')] + filtered_sections.append('.text') + + iram0_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), [])) + dram0_data.append(InputSectionDesc(CROUTINE, flash_rodata[0].sections, [])) + + # Commands for #4 F + # Processed first due to alphabetical ordering + rtc_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), [])) + + # Commands for #2 D + # List all relevant sections excluding #3 for text -> rtc_text and D.2 + # rodata -> rtc_data + timers_sections = self.entities.get_sections('libfreertos.a', 'timers') + filtered_sections = fnmatch.filter(timers_sections, '.literal.*') + filtered_sections.extend(fnmatch.filter(timers_sections, '.text.*')) + + filtered_sections = [s for s in filtered_sections if not s.endswith('prvProcessReceivedCommands')] + filtered_sections.append('.text') + rtc_text.append(InputSectionDesc(TIMERS, set(filtered_sections), [])) + + rtc_data.append(InputSectionDesc(TIMERS, dram0_data[0].sections, [])) + filtered_sections = fnmatch.filter(timers_sections, '.rodata.*') + filtered_sections = [s for s in filtered_sections if not s.endswith('prvProcessReceivedCommands')] + rtc_data.append(InputSectionDesc(TIMERS, set(filtered_sections), [])) + + rtc_bss.append(InputSectionDesc(TIMERS, dram0_bss[0].sections, [])) + rtc_bss.append(InputSectionDesc(TIMERS, dram0_bss[1].sections, [])) + + # Commands for #3 E + iram0_text.append(InputSectionDesc(TIMERS, set(['.text.prvProcessReceivedCommands', '.literal.prvProcessReceivedCommands']), [])) + dram0_data.append(InputSectionDesc(TIMERS, set(['.rodata.prvProcessReceivedCommands']), [])) self.compare_rules(expected, actual) - def test_conditional_sections_2(self): - generation_with_condition = u""" + def test_multiple_mapping_fragments(self): + # Test mapping multiple fragments succeeds, particularly + # generating exclusions from the default command of archive + # and object specificity. + # + # flash_text + # * (EXCLUDE_FILE(libfreertos.a libfreertos.a:croutine) .text ...) + # + # flash_rodata + # * (EXCLUDE_FILE(libfreertos.a libfreertos.a:croutine) .text ...) + # + # iram0_text + mapping = u""" +[mapping:test_1] +archive: libfreertos.a +entries: + croutine (noflash) #1 + +[mapping:test_2] +archive: libfreertos2.a +entries: + * (noflash) #2 +""" + + self.add_fragments(mapping) + actual = self.generation.generate_rules(self.entities) + expected = self.generate_default_rules() + + flash_text = expected['flash_text'] + flash_rodata = expected['flash_rodata'] + iram0_text = expected['iram0_text'] + dram0_data = expected['dram0_data'] + + # Exclusions for #1 A + flash_text[0].exclusions.add(CROUTINE) + flash_rodata[0].exclusions.add(CROUTINE) + + # Exclusions for #1 & #2 B + flash_text[0].exclusions.add(FREERTOS2) + flash_rodata[0].exclusions.add(FREERTOS2) + + # Command for #1 C + iram0_text.append(InputSectionDesc(CROUTINE, flash_text[0].sections, [])) + dram0_data.append(InputSectionDesc(CROUTINE, flash_rodata[0].sections, [])) + + # Command for #1 & #2 D + iram0_text.append(InputSectionDesc(FREERTOS2, flash_text[0].sections, [])) + dram0_data.append(InputSectionDesc(FREERTOS2, flash_rodata[0].sections, [])) + + self.compare_rules(expected, actual) + + def test_mapping_same_lib_in_multiple_fragments_no_conflict(self): + # Test mapping fragments operating on the same archive. + # In these cases, the entries are taken together. + # + # Uses the same entries as C_05 but spreads them across + # two fragments. The output should still be the same. + mapping = u""" +[mapping:test_1] +archive: libfreertos.a +entries: + croutine (noflash) #1 + timers:prvProcessReceivedCommands (noflash) #3 + +[mapping:test_2] +archive: libfreertos.a +entries: + timers (rtc) #2 + croutine:prvCheckPendingReadyList (rtc) #4 +""" + self.test_complex_mapping_case(mapping) + + def test_mapping_same_lib_in_multiple_fragments_conflict(self): + # Test mapping fragments operating on the same archive + # with conflicting mappings. + mapping = u""" +[mapping:test_1] +archive: libfreertos.a +entries: + croutine (noflash) #1 + +[mapping:test_2] +archive: libfreertos.a +entries: + croutine (rtc) #2 +""" + self.test_same_entity_conflicting_scheme(mapping) + + def test_command_order(self): + # Test command order sorting: the commands should be sorted by specificity, then + # alphabetically. This contributes to deterministic output given + # the same input mapping entries. + # + # This ordering is also tested in other tests as a side-effect. + # + # flash_text + # * (EXCLUDE_FILE(libfreertos.a:croutine libfreertos.a:croutine2)) A + # libfreertos.a:croutine(.text ....) B + # + # iram0_text + # + # * (.iram .iram.*) + # libfreertos:croutine(.text .literal ...) C + # libfreertos:croutine(.text.prvCheckDelayedList .literal.prvCheckDelayedList) F + # libfreertos:croutine(.text.prvCheckPendingReadyList .literal.prvCheckPendingReadyList) G + # libfreertos2:croutine(.text .literal ...) D + # libfreertos2:croutine2(.text .literal ...) E + mapping = u""" +[mapping:freertos2] +archive: libfreertos2.a +entries: + croutine2 (noflash_text) #1 + croutine (noflash_text) #2 + +[mapping:freertos] +archive: libfreertos.a +entries: + croutine:prvCheckPendingReadyList (noflash_text) #3 + croutine:prvCheckDelayedList (noflash_text) #4 +""" + + self.add_fragments(mapping) + actual = self.generation.generate_rules(self.entities) + expected = self.generate_default_rules() + + flash_text = expected['flash_text'] + iram0_text = expected['iram0_text'] + + # Exclusions for #1 A + flash_text[0].exclusions.add(CROUTINE) + flash_text[0].exclusions.add(Entity(FREERTOS2.archive, 'croutine2')) + flash_text[0].exclusions.add(Entity(FREERTOS2.archive, 'croutine')) + + # Intermediate command for #3 and #4 B + croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine') + + filtered_sections = fnmatch.filter(croutine_sections, '.literal.*') + filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*')) + + filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')] + filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckDelayedList')] + filtered_sections.append('.text') + flash_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), [])) + + # Command for + iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckDelayedList', '.literal.prvCheckDelayedList']), [])) + iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), [])) + + iram0_text.append(InputSectionDesc(Entity(FREERTOS2.archive, 'croutine'), flash_text[0].sections, [])) + iram0_text.append(InputSectionDesc(Entity(FREERTOS2.archive, 'croutine2'), flash_text[0].sections, [])) + + self.compare_rules(expected, actual) + + def test_ambigious_obj(self): + # Command generation for ambiguous entry should fail. + mapping = u""" +[mapping:test] +archive: libfreertos.a +entries: + port:xPortGetTickRateHz (noflash) #1 +""" + self.add_fragments(mapping) + + with self.assertRaises(GenerationException): + self.generation.generate_rules(self.entities) + + def test_disambiguated_obj(self): + # Test command generation for disambiguated entry. Should produce similar + # results to test_nondefault_mapping_symbol. + mapping = u""" +[mapping:test] +archive: libfreertos.a +entries: + port.c:xPortGetTickRateHz (noflash) #1 +""" + port = Entity('libfreertos.a', 'port.c') + self.add_fragments(mapping) + actual = self.generation.generate_rules(self.entities) + expected = self.generate_default_rules() + + flash_text = expected['flash_text'] + iram0_text = expected['iram0_text'] + + # Generate exclusion in flash_text A + flash_text[0].exclusions.add(port) + + # Generate intermediate command B + # List all relevant sections except the symbol + # being mapped + port_sections = self.entities.get_sections('libfreertos.a', 'port.c') + filtered_sections = fnmatch.filter(port_sections, '.literal.*') + filtered_sections.extend(fnmatch.filter(port_sections, '.text.*')) + + filtered_sections = [s for s in filtered_sections if not s.endswith('xPortGetTickRateHz')] + filtered_sections.append('.text') + + flash_text.append(InputSectionDesc(port, set(filtered_sections), [])) + + # Input section commands in iram_text for #1 C + iram0_text.append(InputSectionDesc(port, set(['.text.xPortGetTickRateHz', '.literal.xPortGetTickRateHz']), [])) + + self.compare_rules(expected, actual) + + +class ConfigTest(GenerationTest): + # Test command generation with conditions + + def _test_conditional_on_scheme(self, perf, alt=None): + # Test that proper commands are generated if using + # schemes with conditional entries. + scheme = u""" [sections:cond_text_data] entries: if PERFORMANCE_LEVEL >= 1: @@ -1192,33 +1107,107 @@ entries: cond_text_data -> iram0_text else: cond_text_data -> dram0_data +""" + mapping = u""" [mapping:test] archive: lib.a entries: * (cond_noflash) """ + self.sdkconfig.config.syms['PERFORMANCE_LEVEL'].set_value(str(perf)) + self.add_fragments(scheme) + self.add_fragments(alt if alt else mapping) - self.sdkconfig.config.syms['PERFORMANCE_LEVEL'].set_value('0') - self.add_fragments(generation_with_condition) - - actual = self.model.generate_rules(self.sections_info) + actual = self.generation.generate_rules(self.entities) expected = self.generate_default_rules() - flash_rodata_default = self.get_default('flash_rodata', expected) - - dram0_data_E1 = PlacementRule('lib.a', '*', None, self.model.sections['rodata'].entries, 'dram0_data') - - # Add the exclusions - flash_rodata_default.add_exclusion(dram0_data_E1) - - # Add to the placement rules list - expected['dram0_data'].append(dram0_data_E1) + if perf >= 1: + flash_text = expected['flash_text'] + iram0_text = expected['iram0_text'] + flash_text[0].exclusions.add(Entity('lib.a')) + iram0_text.append(InputSectionDesc(Entity('lib.a'), flash_text[0].sections, [])) + else: + flash_rodata = expected['flash_rodata'] + dram0_data = expected['dram0_data'] + flash_rodata[0].exclusions.add(Entity('lib.a')) + dram0_data.append(InputSectionDesc(Entity('lib.a'), flash_rodata[0].sections, [])) self.compare_rules(expected, actual) - def test_rule_generation_condition_with_deprecated_mapping(self): - generation_with_condition = u""" + def test_conditional_on_scheme_00(self): + self._test_conditional_on_scheme(0) + + def test_conditional_on_scheme_01(self): + self._test_conditional_on_scheme(1) + + def test_conditional_mapping(self, alt=None): + # Test that proper commands are generated + # in conditional mapping entries. + mapping = u""" +[mapping:test] +archive: lib.a +entries: + if PERFORMANCE_LEVEL = 1: + obj1 (noflash) + elif PERFORMANCE_LEVEL = 2: + obj1 (noflash) + obj2 (noflash) + elif PERFORMANCE_LEVEL = 3: + obj1 (noflash) + obj2 (noflash) + obj3 (noflash) +""" + + for perf_level in range(0, 4): + self.sdkconfig.config.syms['PERFORMANCE_LEVEL'].set_value(str(perf_level)) + + self.generation.mappings = {} + self.add_fragments(alt if alt else mapping) + + actual = self.generation.generate_rules(self.entities) + expected = self.generate_default_rules() + + if perf_level < 4 and perf_level > 0: + for append_no in range(1, perf_level + 1): + flash_text = expected['flash_text'] + flash_rodata = expected['flash_rodata'] + iram0_text = expected['iram0_text'] + dram0_data = expected['dram0_data'] + + obj_str = 'obj' + str(append_no) + + flash_text[0].exclusions.add(Entity('lib.a', obj_str)) + flash_rodata[0].exclusions.add(Entity('lib.a', obj_str)) + + iram0_text.append(InputSectionDesc(Entity('lib.a', obj_str), flash_text[0].sections, [])) + dram0_data.append(InputSectionDesc(Entity('lib.a', obj_str), flash_rodata[0].sections, [])) + + self.compare_rules(expected, actual) + + def test_conditional_on_scheme_legacy_mapping_00(self): + # Test use of conditional scheme on legacy mapping fragment grammar. + mapping = u""" +[mapping] +archive: lib.a +entries: + * (cond_noflash) +""" + self._test_conditional_on_scheme(0, mapping) + + def test_conditional_on_scheme_legacy_mapping_01(self): + # Test use of conditional scheme on legacy mapping fragment grammar. + mapping = u""" +[mapping] +archive: lib.a +entries: + * (cond_noflash) +""" + self._test_conditional_on_scheme(0, mapping) + + def test_conditional_entries_legacy_mapping_fragment(self): + # Test conditional entries on legacy mapping fragment grammar. + mapping = u""" [mapping] archive: lib.a entries: @@ -1233,34 +1222,12 @@ entries: obj2 (noflash) obj3 (noflash) """ + self.test_conditional_mapping(mapping) - for perf_level in range(0, 4): - self.sdkconfig.config.syms['PERFORMANCE_LEVEL'].set_value(str(perf_level)) - - self.model.mappings = {} - self.add_fragments(generation_with_condition) - - actual = self.model.generate_rules(self.sections_info) - expected = self.generate_default_rules() - - if perf_level < 4: - for append_no in range(1, perf_level + 1): - flash_text_default = self.get_default('flash_text', expected) - flash_rodata_default = self.get_default('flash_rodata', expected) - - iram_rule = PlacementRule('lib.a', 'obj' + str(append_no), None, self.model.sections['text'].entries, 'iram0_text') - dram_rule = PlacementRule('lib.a', 'obj' + str(append_no), None, self.model.sections['rodata'].entries, 'dram0_data') - - flash_text_default.add_exclusion(iram_rule) - flash_rodata_default.add_exclusion(dram_rule) - - expected['iram0_text'].append(iram_rule) - expected['dram0_data'].append(dram_rule) - - self.compare_rules(expected, actual) - - def test_rule_generation_multiple_deprecated_mapping_definitions(self): - multiple_deprecated_definitions = u""" + def test_multiple_fragment_same_lib_conditional_legacy(self): + # Test conditional entries on legacy mapping fragment grammar + # across multiple fragments. + mapping = u""" [mapping] archive: lib.a entries: @@ -1284,33 +1251,12 @@ entries: obj3 (noflash) """ - for perf_level in range(0, 4): - self.sdkconfig.config.syms['PERFORMANCE_LEVEL'].set_value(str(perf_level)) + self.test_conditional_mapping(mapping) - self.model.mappings = {} - self.add_fragments(multiple_deprecated_definitions) - - actual = self.model.generate_rules(self.sections_info) - expected = self.generate_default_rules() - - if perf_level < 4: - for append_no in range(1, perf_level + 1): - flash_text_default = self.get_default('flash_text', expected) - flash_rodata_default = self.get_default('flash_rodata', expected) - - iram_rule = PlacementRule('lib.a', 'obj' + str(append_no), None, self.model.sections['text'].entries, 'iram0_text') - dram_rule = PlacementRule('lib.a', 'obj' + str(append_no), None, self.model.sections['rodata'].entries, 'dram0_data') - - flash_text_default.add_exclusion(iram_rule) - flash_rodata_default.add_exclusion(dram_rule) - - expected['iram0_text'].append(iram_rule) - expected['dram0_data'].append(dram_rule) - - self.compare_rules(expected, actual) - - def test_rule_generation_multiple_mapping_definitions(self): - multiple_deprecated_definitions = u""" + def test_multiple_fragment_same_lib_conditional(self): + # Test conditional entries on new mapping fragment grammar. + # across multiple fragments. + mapping = u""" [mapping:base] archive: lib.a entries: @@ -1320,8 +1266,6 @@ entries: obj1 (noflash) elif PERFORMANCE_LEVEL = 3: obj1 (noflash) - else: - * (default) [mapping:extra] archive: lib.a @@ -1333,103 +1277,9 @@ entries: elif PERFORMANCE_LEVEL = 3: obj2 (noflash) obj3 (noflash) - else: - * (default) """ - for perf_level in range(0, 4): - self.sdkconfig.config.syms['PERFORMANCE_LEVEL'].set_value(str(perf_level)) - - self.model.mappings = {} - self.add_fragments(multiple_deprecated_definitions) - - actual = self.model.generate_rules(self.sections_info) - expected = self.generate_default_rules() - - if perf_level < 4: - for append_no in range(1, perf_level + 1): - flash_text_default = self.get_default('flash_text', expected) - flash_rodata_default = self.get_default('flash_rodata', expected) - - iram_rule = PlacementRule('lib.a', 'obj' + str(append_no), None, self.model.sections['text'].entries, 'iram0_text') - dram_rule = PlacementRule('lib.a', 'obj' + str(append_no), None, self.model.sections['rodata'].entries, 'dram0_data') - - flash_text_default.add_exclusion(iram_rule) - flash_rodata_default.add_exclusion(dram_rule) - - expected['iram0_text'].append(iram_rule) - expected['dram0_data'].append(dram_rule) - - self.compare_rules(expected, actual) - - def test_rules_order(self): - # The fragments are structured such that ldgen will: - # - parse freertos2 mapping first - # - entry for prvCheckPendingReadyList is parsed first before prvCheckDelayedList - # We expect that despite this, ldgen will output rules in a set order: - # by increasing specificity and alphabetically - test = u""" -[mapping:freertos2] -archive: libfreertos2.a -entries: - croutine2 (noflash_text) - croutine (noflash_text) - -[mapping:freertos] -archive: libfreertos.a -entries: - croutine:prvCheckPendingReadyList (noflash_text) - croutine:prvCheckDelayedList (noflash_text) -""" - self.add_fragments(test) - - actual = self.model.generate_rules(self.sections_info) - - expected = self.generate_default_rules() - - flash_text_default = self.get_default('flash_text', expected) - - iram0_text_E1 = PlacementRule('libfreertos2.a', 'croutine2', None, self.model.sections['text'].entries, 'iram0_text') - iram0_text_E2 = PlacementRule('libfreertos2.a', 'croutine', None, self.model.sections['text'].entries, 'iram0_text') - iram0_text_E3 = PlacementRule('libfreertos.a', 'croutine', 'prvCheckPendingReadyList', self.model.sections['text'].entries, 'iram0_text') - iram0_text_E4 = PlacementRule('libfreertos.a', 'croutine', 'prvCheckDelayedList', self.model.sections['text'].entries, 'iram0_text') - - flash_text_extra = PlacementRule('libfreertos.a', 'croutine', None, ['.text.*', '.literal.*'], 'flash_text') - - # Add the exclusions - flash_text_default.add_exclusion(iram0_text_E1, self.sections_info) - flash_text_default.add_exclusion(iram0_text_E2, self.sections_info) - - flash_text_default.add_exclusion(flash_text_extra, self.sections_info) - flash_text_extra.add_exclusion(iram0_text_E3, self.sections_info) - flash_text_extra.add_exclusion(iram0_text_E4, self.sections_info) - - # Add the rules, arranged by expected order - expected['flash_text'].append(flash_text_extra) - expected['iram0_text'].append(iram0_text_E4) - expected['iram0_text'].append(iram0_text_E3) - expected['iram0_text'].append(iram0_text_E2) - expected['iram0_text'].append(iram0_text_E1) - - # Perform general comparison for all sections - self.compare_rules(expected, actual) - - # Perform ordered comparison - self.assertListEqual(actual['flash_text'], expected['flash_text']) - self.assertListEqual(actual['iram0_text'], expected['iram0_text']) - - def test_sections_info_parsing(self): - - self.sections_info = SectionsInfo() - - with open('data/sections_parse.info') as sections_info_file_obj: - self.sections_info.add_sections_info(sections_info_file_obj) - - sections = self.sections_info.get_obj_sections('libsections_parse.a', 'croutine') - self.assertEqual(set(sections), set(['.text', '.data', '.bss'])) - - sections = self.sections_info.get_obj_sections('libsections_parse.a', 'FreeRTOS-openocd') - self.assertEqual(set(sections), set(['.literal.prvCheckPendingReadyList'])) + self.test_conditional_mapping(mapping) if __name__ == '__main__': diff --git a/tools/ldgen/test/test_output_commands.py b/tools/ldgen/test/test_output_commands.py index fd5a5fb08e..cf4cf97ec0 100755 --- a/tools/ldgen/test/test_output_commands.py +++ b/tools/ldgen/test/test_output_commands.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright 2018-2019 Espressif Systems (Shanghai) PTE LTD +# Copyright 2021 Espressif Systems (Shanghai) CO LTD # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. From 23590374b5b3734653e84ff0d9042cf299c95ea0 Mon Sep 17 00:00:00 2001 From: Renz Bagaporo Date: Thu, 28 Jan 2021 12:33:12 +0800 Subject: [PATCH 4/4] ldgen: add test app for placement sanity check --- .../build_system/ldgen_test/CMakeLists.txt | 15 +++++ .../build_system/ldgen_test/README.txt | 5 ++ .../ldgen_test/check_placements.py | 64 +++++++++++++++++++ .../ldgen_test/main/CMakeLists.txt | 3 + .../build_system/ldgen_test/main/component.mk | 0 .../build_system/ldgen_test/main/linker.lf | 7 ++ .../build_system/ldgen_test/main/src1.c | 16 +++++ .../build_system/ldgen_test/main/src2.c | 6 ++ .../build_system/ldgen_test/main/test_main.c | 14 ++++ 9 files changed, 130 insertions(+) create mode 100644 tools/test_apps/build_system/ldgen_test/CMakeLists.txt create mode 100644 tools/test_apps/build_system/ldgen_test/README.txt create mode 100644 tools/test_apps/build_system/ldgen_test/check_placements.py create mode 100644 tools/test_apps/build_system/ldgen_test/main/CMakeLists.txt create mode 100644 tools/test_apps/build_system/ldgen_test/main/component.mk create mode 100644 tools/test_apps/build_system/ldgen_test/main/linker.lf create mode 100644 tools/test_apps/build_system/ldgen_test/main/src1.c create mode 100644 tools/test_apps/build_system/ldgen_test/main/src2.c create mode 100644 tools/test_apps/build_system/ldgen_test/main/test_main.c diff --git a/tools/test_apps/build_system/ldgen_test/CMakeLists.txt b/tools/test_apps/build_system/ldgen_test/CMakeLists.txt new file mode 100644 index 0000000000..cc7567eceb --- /dev/null +++ b/tools/test_apps/build_system/ldgen_test/CMakeLists.txt @@ -0,0 +1,15 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(ldgen_test) + +idf_build_get_property(python PYTHON) +idf_build_get_property(elf EXECUTABLE) + +add_custom_command( + TARGET ${elf} + POST_BUILD + COMMAND ${python} ${CMAKE_CURRENT_LIST_DIR}/check_placements.py ${CMAKE_OBJDUMP} $ +) diff --git a/tools/test_apps/build_system/ldgen_test/README.txt b/tools/test_apps/build_system/ldgen_test/README.txt new file mode 100644 index 0000000000..adc6d53e2e --- /dev/null +++ b/tools/test_apps/build_system/ldgen_test/README.txt @@ -0,0 +1,5 @@ +Runs a build test to check ldgen places libraries, objects and symbols +correctly as specified in the linker fragments. Specifically, this app +tests the placement for the main component, as specified in `main/linker.lf` +The Python script that performs the checks, `check_placements.py`, automatically +runs after the app is built. diff --git a/tools/test_apps/build_system/ldgen_test/check_placements.py b/tools/test_apps/build_system/ldgen_test/check_placements.py new file mode 100644 index 0000000000..21e7ab7606 --- /dev/null +++ b/tools/test_apps/build_system/ldgen_test/check_placements.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python +# +# Copyright 2020 Espressif Systems (Shanghai) PTE LTD +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Check placements in this test app for main +# specified in main/linker.lf + +import argparse +import subprocess + +from pyparsing import LineEnd, Literal, ParseException, SkipTo, Word, alphanums, hexnums + +argparser = argparse.ArgumentParser() + +argparser.add_argument('objdump') +argparser.add_argument('elf') + +args = argparser.parse_args() + +contents = subprocess.check_output([args.objdump, '-t', args.elf]).decode() + + +def check_location(symbol, expected): + pattern = Word(alphanums + '._').setResultsName('actual') + Word(hexnums) + Literal(symbol) + LineEnd() + pattern = SkipTo(pattern) + pattern + + try: + results = pattern.parseString(contents) + except ParseException: + print("check placement fail: '%s' was not found" % (symbol)) + exit(1) + + if results.actual != expected: + print("check placement fail: '%s' was placed in '%s', not in '%s'" % (symbol, results.actual, expected)) + exit(1) + + print("check placement pass: '%s' was successfully placed in '%s'" % (symbol, results.actual)) + + +# src1:func1 (noflash) - explicit mapping for func2 using 'rtc' scheme +check_location('func1', '.iram0.text') + +# src1:func2 (rtc) - explicit mapping for func2 using 'rtc' scheme +check_location('func2', '.rtc.text') + +# src1 (default) - only func3 in src1 remains that has not been +# mapped using a different scheme +check_location('func3', '.flash.text') + +# * (noflash) - no explicit mapping for src2 +check_location('func4', '.iram0.text') diff --git a/tools/test_apps/build_system/ldgen_test/main/CMakeLists.txt b/tools/test_apps/build_system/ldgen_test/main/CMakeLists.txt new file mode 100644 index 0000000000..466b45c56c --- /dev/null +++ b/tools/test_apps/build_system/ldgen_test/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "src1.c" "src2.c" "test_main.c" + INCLUDE_DIRS "." + LDFRAGMENTS "linker.lf") diff --git a/tools/test_apps/build_system/ldgen_test/main/component.mk b/tools/test_apps/build_system/ldgen_test/main/component.mk new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tools/test_apps/build_system/ldgen_test/main/linker.lf b/tools/test_apps/build_system/ldgen_test/main/linker.lf new file mode 100644 index 0000000000..b467f814ae --- /dev/null +++ b/tools/test_apps/build_system/ldgen_test/main/linker.lf @@ -0,0 +1,7 @@ +[mapping:main] +archive: libmain.a +entries: + * (noflash) + src1 (default) + src1:func1 (noflash) + src1:func2 (rtc) diff --git a/tools/test_apps/build_system/ldgen_test/main/src1.c b/tools/test_apps/build_system/ldgen_test/main/src1.c new file mode 100644 index 0000000000..c8be4906da --- /dev/null +++ b/tools/test_apps/build_system/ldgen_test/main/src1.c @@ -0,0 +1,16 @@ +#include + +void func1(void) +{ + printf("Hello from func1!\n"); +} + +void func2(void) +{ + printf("Hello from func2!\n"); +} + +void func3(void) +{ + printf("Hello from func3!\n"); +} diff --git a/tools/test_apps/build_system/ldgen_test/main/src2.c b/tools/test_apps/build_system/ldgen_test/main/src2.c new file mode 100644 index 0000000000..bdcbee9606 --- /dev/null +++ b/tools/test_apps/build_system/ldgen_test/main/src2.c @@ -0,0 +1,6 @@ +#include + +void func4(void) +{ + printf("Hello from func4!\n"); +} diff --git a/tools/test_apps/build_system/ldgen_test/main/test_main.c b/tools/test_apps/build_system/ldgen_test/main/test_main.c new file mode 100644 index 0000000000..37aaa6aecd --- /dev/null +++ b/tools/test_apps/build_system/ldgen_test/main/test_main.c @@ -0,0 +1,14 @@ + +extern void func1(void); +extern void func2(void); +extern void func3(void); +extern void func4(void); + + +void app_main(void) +{ + func1(); + func2(); + func3(); + func4(); +}