Merge branch 'bugfix/ldgen_ignore_nonexistent_archives_and_obj_v4.0' into 'release/v4.0'

ldgen: check mappings (v4.0)

See merge request espressif/esp-idf!14065
This commit is contained in:
Angus Gratton 2021-07-14 08:46:34 +00:00
commit 2b7f4ec8e6
15 changed files with 137 additions and 40 deletions

View File

@ -3,10 +3,11 @@ archive: libapp_trace.a
entries: entries:
app_trace (noflash) app_trace (noflash)
app_trace_util (noflash) app_trace_util (noflash)
SEGGER_SYSVIEW (noflash) if SYSVIEW_ENABLE = y:
SEGGER_RTT_esp32 (noflash) SEGGER_SYSVIEW (noflash)
SEGGER_SYSVIEW_Config_FreeRTOS (noflash) SEGGER_RTT_esp32 (noflash)
SEGGER_SYSVIEW_FreeRTOS (noflash) SEGGER_SYSVIEW_Config_FreeRTOS (noflash)
SEGGER_SYSVIEW_FreeRTOS (noflash)
[mapping:driver] [mapping:driver]
archive: libdriver.a archive: libdriver.a

View File

@ -673,8 +673,8 @@ static IRAM_ATTR __attribute__((noinline)) bool iram_ringbuf_test()
{ {
bool result = true; bool result = true;
spi_flash_guard_get()->start(); // Disables flash cache
RingbufHandle_t handle = xRingbufferCreate(CONT_DATA_TEST_BUFF_LEN, RINGBUF_TYPE_NOSPLIT); RingbufHandle_t handle = xRingbufferCreate(CONT_DATA_TEST_BUFF_LEN, RINGBUF_TYPE_NOSPLIT);
spi_flash_guard_get()->start(); // Disables flash cache
result = result && (handle != NULL); result = result && (handle != NULL);
xRingbufferGetMaxItemSize(handle); xRingbufferGetMaxItemSize(handle);
vRingbufferDelete(handle); vRingbufferDelete(handle);

View File

@ -5,3 +5,5 @@ entries:
tasks:vTaskStartScheduler (default) tasks:vTaskStartScheduler (default)
port:xPortStartScheduler (default) port:xPortStartScheduler (default)
port:pxPortInitialiseStack (default) port:pxPortInitialiseStack (default)
if FREERTOS_SUPPORT_STATIC_ALLOCATION = y:
queue:xQueueGenericCreateStatic (default)

View File

@ -2,4 +2,6 @@
archive: libheap.a archive: libheap.a
entries: entries:
multi_heap (noflash) multi_heap (noflash)
multi_heap_poisoning (noflash) if HEAP_POISONING_DISABLED = n:
multi_heap_poisoning (noflash)

View File

@ -62,7 +62,6 @@ entries:
pbuf:pbuf_header_impl (noflash_text) pbuf:pbuf_header_impl (noflash_text)
pbuf:pbuf_header (noflash_text) pbuf:pbuf_header (noflash_text)
pbuf:pbuf_free (noflash_text) pbuf:pbuf_free (noflash_text)
timeouts:sys_timeouts_mbox_fetch (noflash_text)
udp:udp_input_local_match (noflash_text) udp:udp_input_local_match (noflash_text)
udp:udp_input (noflash_text) udp:udp_input (noflash_text)
udp:udp_send (noflash_text) udp:udp_send (noflash_text)

View File

@ -11,6 +11,5 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#pragma once #pragma once
#include "soc/efuse_reg.h" #include "soc/efuse_reg.h"

View File

@ -5,7 +5,6 @@ entries:
rtc_clk (noflash) rtc_clk (noflash)
rtc_clk_init (noflash_text) rtc_clk_init (noflash_text)
rtc_init (noflash_text) rtc_init (noflash_text)
rtc_periph (noflash_text)
rtc_pm (noflash_text) rtc_pm (noflash_text)
rtc_sleep (noflash_text) rtc_sleep (noflash_text)
rtc_time (noflash_text) rtc_time (noflash_text)

View File

@ -0,0 +1,3 @@
libc
sha256_coredump
gcc

View File

@ -80,7 +80,8 @@ build_ssc:
build_esp_idf_tests_make: build_esp_idf_tests_make:
extends: .build_esp_idf_unit_test_template extends: .build_esp_idf_unit_test_template
variables: variables:
PYTHON_VER: 3 PYTHON_VER: 3
LDGEN_CHECK_MAPPING: 1
script: script:
- export EXTRA_CFLAGS=${PEDANTIC_CFLAGS} - export EXTRA_CFLAGS=${PEDANTIC_CFLAGS}
- export EXTRA_CXXFLAGS=${EXTRA_CFLAGS} - export EXTRA_CXXFLAGS=${EXTRA_CFLAGS}
@ -129,6 +130,7 @@ build_examples_make:
- $LOG_PATH - $LOG_PATH
expire_in: 4 days expire_in: 4 days
variables: variables:
LDGEN_CHECK_MAPPING: 1
LOG_PATH: "$CI_PROJECT_DIR/log_examples_make" LOG_PATH: "$CI_PROJECT_DIR/log_examples_make"
only: only:
# Here both 'variables' and 'refs' conditions are given. They are combined with "AND" logic. # Here both 'variables' and 'refs' conditions are given. They are combined with "AND" logic.

View File

@ -56,6 +56,12 @@ function(__ldgen_process_template template output)
idf_build_get_property(config_env_path CONFIG_ENV_PATH) idf_build_get_property(config_env_path CONFIG_ENV_PATH)
if($ENV{LDGEN_CHECK_MAPPING})
set(ldgen_check "--check-mapping"
"--check-mapping-exceptions=${idf_path}/tools/ci/check_ldgen_mapping_exceptions.txt")
message(STATUS "Mapping check enabled in ldgen")
endif()
add_custom_command( add_custom_command(
OUTPUT ${output} OUTPUT ${output}
COMMAND ${python} ${idf_path}/tools/ldgen/ldgen.py COMMAND ${python} ${idf_path}/tools/ldgen/ldgen.py
@ -67,6 +73,7 @@ function(__ldgen_process_template template output)
--env-file "${config_env_path}" --env-file "${config_env_path}"
--libraries-file ${build_dir}/ldgen_libraries --libraries-file ${build_dir}/ldgen_libraries
--objdump ${CMAKE_OBJDUMP} --objdump ${CMAKE_OBJDUMP}
${ldgen_check}
DEPENDS ${template} ${ldgen_fragment_files} ${ldgen_depends} ${SDKCONFIG} DEPENDS ${template} ${ldgen_fragment_files} ${ldgen_depends} ${SDKCONFIG}
) )

View File

@ -21,7 +21,7 @@ import fnmatch
from fragments import Sections, Scheme, Mapping, Fragment from fragments import Sections, Scheme, Mapping, Fragment
from pyparsing import Suppress, White, ParseException, Literal, Group, ZeroOrMore from pyparsing import Suppress, White, ParseException, Literal, Group, ZeroOrMore
from pyparsing import Word, OneOrMore, nums, alphanums, alphas, Optional, restOfLine from pyparsing import Word, OneOrMore, nums, alphas, restOfLine, SkipTo
from ldgen_common import LdGenFailure from ldgen_common import LdGenFailure
@ -80,7 +80,6 @@ class PlacementRule():
def do_section_expansion(rule, section): def do_section_expansion(rule, section):
if section in rule.get_section_names(): if section in rule.get_section_names():
sections_in_obj = sections_infos.get_obj_sections(rule.archive, rule.obj) sections_in_obj = sections_infos.get_obj_sections(rule.archive, rule.obj)
expansions = fnmatch.filter(sections_in_obj, section) expansions = fnmatch.filter(sections_in_obj, section)
return expansions return expansions
@ -254,11 +253,18 @@ class GenerationModel:
DEFAULT_SCHEME = "default" DEFAULT_SCHEME = "default"
def __init__(self): def __init__(self, check_mappings=False, check_mapping_exceptions=None):
self.schemes = {} self.schemes = {}
self.sections = {} self.sections = {}
self.mappings = {} self.mappings = {}
self.check_mappings = check_mappings
if check_mapping_exceptions:
self.check_mapping_exceptions = check_mapping_exceptions
else:
self.check_mapping_exceptions = []
def _add_mapping_rules(self, archive, obj, symbol, scheme_name, scheme_dict, rules): def _add_mapping_rules(self, archive, obj, symbol, scheme_name, scheme_dict, rules):
# Use an ordinary dictionary to raise exception on non-existing keys # Use an ordinary dictionary to raise exception on non-existing keys
temp_dict = dict(scheme_dict) temp_dict = dict(scheme_dict)
@ -338,6 +344,19 @@ class GenerationModel:
try: try:
if not (obj == Mapping.MAPPING_ALL_OBJECTS and symbol is None and if not (obj == Mapping.MAPPING_ALL_OBJECTS and symbol is None and
scheme_name == GenerationModel.DEFAULT_SCHEME): 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)
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)
self._add_mapping_rules(archive, obj, symbol, scheme_name, scheme_dictionary, mapping_rules) self._add_mapping_rules(archive, obj, symbol, scheme_name, scheme_dictionary, mapping_rules)
except KeyError: except KeyError:
message = GenerationException.UNDEFINED_REFERENCE + " to scheme '" + scheme_name + "'." message = GenerationException.UNDEFINED_REFERENCE + " to scheme '" + scheme_name + "'."
@ -589,7 +608,7 @@ class SectionsInfo(dict):
results = None results = None
try: try:
results = parser.parseString(first_line) results = parser.parseString(first_line, parseAll=True)
except ParseException as p: except ParseException as p:
raise ParseException("Parsing sections info for library " + sections_info_dump.name + " failed. " + p.message) raise ParseException("Parsing sections info for library " + sections_info_dump.name + " failed. " + p.message)
@ -597,43 +616,57 @@ class SectionsInfo(dict):
self.sections[archive] = SectionsInfo.__info(sections_info_dump.name, sections_info_dump.read()) self.sections[archive] = SectionsInfo.__info(sections_info_dump.name, sections_info_dump.read())
def _get_infos_from_file(self, info): def _get_infos_from_file(self, info):
# Object file line: '{object}: file format elf32-xtensa-le' # {object}: file format elf32-xtensa-le
object = Fragment.ENTITY.setResultsName("object") + Literal(":").suppress() + Literal("file format elf32-xtensa-le").suppress() object_line = SkipTo(":").setResultsName("object") + Suppress(restOfLine)
# Sections table # Sections:
header = Suppress(Literal("Sections:") + Literal("Idx") + Literal("Name") + Literal("Size") + Literal("VMA") + # Idx Name ...
Literal("LMA") + Literal("File off") + Literal("Algn")) section_start = Suppress(Literal("Sections:"))
entry = Word(nums).suppress() + Fragment.ENTITY + Suppress(OneOrMore(Word(alphanums, exact=8)) + section_header = Suppress(OneOrMore(Word(alphas)))
Word(nums + "*") + ZeroOrMore(Word(alphas.upper()) +
Optional(Literal(","))))
# Content is object file line + sections table # 00 {section} 0000000 ...
content = Group(object + header + Group(ZeroOrMore(entry)).setResultsName("sections")) # 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") parser = Group(ZeroOrMore(content)).setResultsName("contents")
sections_info_text = info.content
results = None results = None
try: try:
results = parser.parseString(sections_info_text) results = parser.parseString(info.content, parseAll=True)
except ParseException as p: except ParseException as p:
raise ParseException("Unable to parse section info file " + info.filename + ". " + p.message) raise ParseException("Unable to parse section info file " + info.filename + ". " + p.message)
return results return results
def get_obj_sections(self, archive, obj): def get_obj_sections(self, archive, obj):
stored = self.sections[archive] res = []
try:
stored = self.sections[archive]
# Parse the contents of the sections file # Parse the contents of the sections file on-demand,
if not isinstance(stored, dict): # save the result for later
parsed = self._get_infos_from_file(stored) if not isinstance(stored, dict):
stored = dict() parsed = self._get_infos_from_file(stored)
for content in parsed.contents: stored = dict()
sections = list(map(lambda s: s, content.sections)) for content in parsed.contents:
stored[content.object] = sections sections = list(map(lambda s: s, content.sections))
self.sections[archive] = stored stored[content.object] = sections
self.sections[archive] = stored
for obj_key in stored.keys(): try:
if obj_key == obj + ".o" or obj_key == obj + ".c.obj": res = stored[obj + ".o"]
return stored[obj_key] 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

View File

@ -75,6 +75,18 @@ def main():
"--kconfig", "-k", "--kconfig", "-k",
help="IDF Kconfig file") help="IDF Kconfig file")
argparser.add_argument(
"--check-mapping",
help="Perform a check if a mapping (archive, obj, symbol) exists",
action='store_true'
)
argparser.add_argument(
"--check-mapping-exceptions",
help="Mappings exempted from check",
type=argparse.FileType("r")
)
argparser.add_argument( argparser.add_argument(
"--env", "-e", "--env", "-e",
action='append', default=[], action='append', default=[],
@ -98,6 +110,12 @@ def main():
kconfig_file = args.kconfig kconfig_file = args.kconfig
objdump = args.objdump objdump = args.objdump
check_mapping = args.check_mapping
if args.check_mapping_exceptions:
check_mapping_exceptions = [line.strip() for line in args.check_mapping_exceptions]
else:
check_mapping_exceptions = None
try: try:
sections_infos = SectionsInfo() sections_infos = SectionsInfo()
for library in libraries_file: for library in libraries_file:
@ -107,7 +125,7 @@ def main():
dump.name = library dump.name = library
sections_infos.add_sections_info(dump) sections_infos.add_sections_info(dump)
generation_model = GenerationModel() generation_model = GenerationModel(check_mapping, check_mapping_exceptions)
_update_environment(args) # assign args.env and args.env_file to os.environ _update_environment(args) # assign args.env and args.env_file to os.environ

View File

@ -1,4 +1,4 @@
In archive /home/user/ãóç+ěščřžýáíé/build/esp-idf/freertos/libfreertos.a: In archive /home/user/build/esp-idf/freertos/libfreertos.a:
FreeRTOS-openocd.c.obj: file format elf32-xtensa-le FreeRTOS-openocd.c.obj: file format elf32-xtensa-le

View File

@ -0,0 +1,19 @@
In archive /home/user/ãóç+ěščřžýáíé/build/esp-idf/freertos/libsections_parse.a:
croutine.c.obj: file format elf32-littleriscv
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000000 00000000 00000000 00000034 2**0
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .data 00000000 00000000 00000000 00000034 2**0
CONTENTS, ALLOC, LOAD, DATA
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
Sections:
Idx Name Size VMA LMA File off Algn
0 .literal.prvCheckPendingReadyList 00000018 00000000 00000000 00000034 2**2
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE

View File

@ -1405,6 +1405,19 @@ entries:
self.assertListEqual(actual["flash_text"], expected["flash_text"]) self.assertListEqual(actual["flash_text"], expected["flash_text"])
self.assertListEqual(actual["iram0_text"], expected["iram0_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"]))
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()