From 6f6b4c3983298f55b7b86f8224fe6705cc7c8057 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 24 Jun 2020 15:52:50 +1000 Subject: [PATCH] cmake partition_table: Check binaries fit in partition spaces at build time - Bootloader is checked not to overlap partition table - Apps are checked not to overlap any app partition regions Supported for CMake build system only. Closes https://github.com/espressif/esp-idf/pull/612 Closes https://github.com/espressif/esp-idf/issues/5043 Probable fix for https://github.com/espressif/esp-idf/issues/5456 --- components/bootloader/project_include.cmake | 2 +- components/esptool_py/CMakeLists.txt | 19 ++- components/partition_table/check_sizes.py | 148 ++++++++++++++++++ components/partition_table/gen_esp32part.py | 62 +++++--- .../partition_table/project_include.cmake | 63 +++++++- .../check_sizes_test.py | 105 +++++++++++++ .../gen_esp32part_tests.py | 14 +- .../test_gen_esp32part_host/test_utils.py | 18 +++ docs/en/api-guides/partition-tables.rst | 18 +++ .../security/secure-boot-bootloader-size.rst | 14 +- tools/ci/check_build_warnings.py | 3 +- tools/ci/executable-list.txt | 2 + tools/ci/mypy_ignore_list.txt | 1 - 13 files changed, 423 insertions(+), 46 deletions(-) create mode 100755 components/partition_table/check_sizes.py create mode 100755 components/partition_table/test_gen_esp32part_host/check_sizes_test.py create mode 100644 components/partition_table/test_gen_esp32part_host/test_utils.py diff --git a/components/bootloader/project_include.cmake b/components/bootloader/project_include.cmake index 42e0fe480c..23318c9a7b 100644 --- a/components/bootloader/project_include.cmake +++ b/components/bootloader/project_include.cmake @@ -1,4 +1,4 @@ -set(BOOTLOADER_OFFSET CONFIG_BOOTLOADER_OFFSET_IN_FLASH) +set(BOOTLOADER_OFFSET ${CONFIG_BOOTLOADER_OFFSET_IN_FLASH}) # Do not generate flash file when building bootloader if(BOOTLOADER_BUILD OR NOT CONFIG_APP_BUILD_BOOTLOADER) diff --git a/components/esptool_py/CMakeLists.txt b/components/esptool_py/CMakeLists.txt index 3b588b7c91..c57f25c820 100644 --- a/components/esptool_py/CMakeLists.txt +++ b/components/esptool_py/CMakeLists.txt @@ -1,4 +1,4 @@ -idf_component_register(REQUIRES bootloader) +idf_component_register(REQUIRES bootloader PRIV_REQUIRES partition_table) if(NOT BOOTLOADER_BUILD) idf_build_get_property(build_dir BUILD_DIR) @@ -41,6 +41,7 @@ consist of two ota app without factory or test partitions.") endif() if(CONFIG_APP_BUILD_GENERATE_BINARIES) + # Generate flasher args files file(READ "flasher_args.json.in" flasher_args_content) string(CONFIGURE "${flasher_args_content}" flasher_args_content) @@ -48,5 +49,21 @@ consist of two ota app without factory or test partitions.") CONTENT "${flasher_args_content}") file_generate("${CMAKE_BINARY_DIR}/flasher_args.json" INPUT "${CMAKE_CURRENT_BINARY_DIR}/flasher_args.json.in") + + # Generate app_check_size_command target to check the app size against the partition table parameters + partition_table_add_check_size_target(app_check_size + DEPENDS gen_project_binary + BINARY_PATH "${build_dir}/${PROJECT_BIN}" + PARTITION_TYPE app) + add_dependencies(app app_check_size) endif() +endif() # NOT BOOTLOADER_BUILD + +if(BOOTLOADER_BUILD) + # Generate bootloader post-build check of the bootloader size against the offset + partition_table_add_check_bootloader_size_target(bootloader_check_size + DEPENDS gen_project_binary + BOOTLOADER_BINARY_PATH "${build_dir}/${PROJECT_BIN}" + RESULT bootloader_check_size_command) + add_dependencies(app bootloader_check_size) # note: in the subproject, so the target is 'app'... endif() diff --git a/components/partition_table/check_sizes.py b/components/partition_table/check_sizes.py new file mode 100755 index 0000000000..d47b9d2838 --- /dev/null +++ b/components/partition_table/check_sizes.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python +# +# check_sizes.py is a tool run by the ESP-IDF build system +# to check a particular binary fits in the available partitions of +# a particular type/subtype. Can be used to check if the app binary fits in +# all available app partitions, for example. +# +# (Can also check if the bootloader binary fits before the partition table.) +# +# 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. +from __future__ import division, print_function, unicode_literals + +import argparse +import io # noqa: F401 # pylint: disable=unused-import +import os +import sys + +try: + from typing import IO # noqa: F401 # pylint: disable=unused-import +except ImportError: + pass # used for type hinting only + +import gen_esp32part +from gen_esp32part import PartitionTable, get_ptype_as_int, get_subtype_as_int + +allow_failures = False + + +def _file_size(f): # type: (IO) -> int + before = f.tell() + f.seek(0, 2) # seek to end + result = f.tell() + f.seek(before) + return result + + +def _fail(msg): # type: (str) -> None + if allow_failures: + print('Warning: {}'.format(msg)) + else: + raise SystemExit('Error: {}'.format(msg)) + + +def check_bootloader(partition_table_offset, bootloader_offset, binary_file): # type: (int, int, IO) -> None + max_size = partition_table_offset - bootloader_offset + bootloader_size = _file_size(binary_file) + if bootloader_size > max_size: + msg = ('Bootloader binary size {:#x} bytes is too large for partition table offset {:#02x}. ' + + 'Bootloader binary can be maximum {:#x} ({}) bytes unless the partition table offset ' + + 'is increased in the Partition Table section of the project configuration menu.').format( + bootloader_size, partition_table_offset, max_size, max_size) + _fail(msg) + free_size = max_size - bootloader_size + print('Bootloader binary size {:#x} bytes. {:#x} bytes ({}%) free.'.format( + bootloader_size, free_size, round(free_size * 100 / bootloader_size))) + + +def check_partition(ptype, subtype, partition_table_file, bin_file): # type: (str, str, io.IOBase, IO) -> None + table, _ = PartitionTable.from_file(partition_table_file) + ptype_str = str(ptype) + ptype = get_ptype_as_int(ptype) + partitions = [p for p in table if p.type == ptype] + + if subtype is not None: + ptype_str += ' ({})'.format(subtype) + subtype = get_subtype_as_int(ptype, subtype) + partitions = [p for p in partitions if p.subtype == subtype] + + if len(partitions) == 0: + print('WARNING: Partition table does not contain any partitions matching {}'.format(ptype_str)) + return + + bin_name = os.path.basename(bin_file.name) + bin_size = _file_size(bin_file) + smallest_size = min(p.size for p in partitions) + if smallest_size >= bin_size: + free_size = smallest_size - bin_size + print('{} binary size {:#x} bytes. Smallest {} partition is {:#x} bytes. {:#x} bytes ({}%) free.'.format( + bin_name, bin_size, ptype_str, smallest_size, free_size, round(free_size * 100 / smallest_size))) + return + + too_small_partitions = [p for p in partitions if p.size < bin_size] + if len(partitions) == 1: + msg = '{} partition is'.format(ptype_str) + elif len(partitions) == len(too_small_partitions): + msg = 'All {} partitions are'.format(ptype_str) + else: + msg = '{}/{} {} partitions are'.format(len(too_small_partitions), len(partitions), ptype_str) + msg += ' too small for binary {} size {:#x}:'.format(bin_name, bin_size) + for p in too_small_partitions: + msg += '\n - {} (overflow {:#x})'.format(p, bin_size - p.size) + if not allow_failures and len(partitions) == len(too_small_partitions): + # if some partitions can fit the binary then just print a warning + raise SystemExit('Error: ' + msg) + else: + print('Warning: ' + msg) + + +def main(): # type: () -> None + global allow_failures # pylint: disable=global-statement + + parser = argparse.ArgumentParser(description='Check binary sizes against partition table entries') + parser.add_argument('--target', choices=['esp32', 'esp32s2']) + parser.add_argument('--allow_failures', action='store_true', help='If true, failures will print warnings but not exit with an error') + parser.add_argument('--offset', '-o', help='Set partition table offset', default='0x8000') + + subparsers = parser.add_subparsers(dest='check_target', + help='Type of binary to check against partition table layout') + sp_bootloader = subparsers.add_parser('bootloader') + sp_bootloader.add_argument('bootloader_offset', help='Hex offset of bootloader in flash') + sp_bootloader.add_argument('bootloader_binary', type=argparse.FileType('rb'), help='Bootloader binary (.bin) file from build output') + + sp_part = subparsers.add_parser('partition') + sp_part.add_argument('--type', type=str, help='Check the file size against all partitions of this type.', required=True) + sp_part.add_argument('--subtype', type=str, help='Optional, only check the file size against all partitions of this subtype.') + sp_part.add_argument('partition_table', type=argparse.FileType('rb'), help='Partition table file') + sp_part.add_argument('binary', type=argparse.FileType('rb'), help='Binary file which will have the size checked') + + args = parser.parse_args() + + gen_esp32part.quiet = True + + args.offset = int(args.offset, 0) + gen_esp32part.offset_part_table = args.offset + + if args.check_target is None: # add_subparsers only has a 'required' argument since Python 3 + parser.print_help() + sys.exit(1) + if args.check_target == 'bootloader': + check_bootloader(args.offset, int(args.bootloader_offset, 0), args.bootloader_binary) + else: + check_partition(args.type, args.subtype, args.partition_table, args.binary) + + +if __name__ == '__main__': + main() diff --git a/components/partition_table/gen_esp32part.py b/components/partition_table/gen_esp32part.py index d7b57a469b..2011c78970 100755 --- a/components/partition_table/gen_esp32part.py +++ b/components/partition_table/gen_esp32part.py @@ -48,6 +48,18 @@ TYPES = { 'data': DATA_TYPE, } + +def get_ptype_as_int(ptype): + """ Convert a string which might be numeric or the name of a partition type to an integer """ + try: + return TYPES[ptype] + except KeyError: + try: + return int(ptype, 0) + except TypeError: + return ptype + + # Keep this map in sync with esp_partition_subtype_t enum in esp_partition.h SUBTYPES = { APP_TYPE: { @@ -67,6 +79,18 @@ SUBTYPES = { }, } + +def get_subtype_as_int(ptype, subtype): + """ Convert a string which might be numeric or the name of a partition subtype to an integer """ + try: + return SUBTYPES[get_ptype_as_int(ptype)][subtype] + except KeyError: + try: + return int(subtype, 0) + except TypeError: + return subtype + + quiet = False md5sum = True secure = False @@ -89,6 +113,18 @@ class PartitionTable(list): def __init__(self): super(PartitionTable, self).__init__(self) + @classmethod + def from_file(cls, f): + input = f.read() + input_is_binary = input[0:2] == PartitionDefinition.MAGIC_BYTES + if input_is_binary: + status('Parsing binary partition input...') + return cls.from_binary(input), True + else: + input = input.decode() + status('Parsing CSV input...') + return cls.from_csv(input), False + @classmethod def from_csv(cls, csv_contents): res = PartitionTable() @@ -149,20 +185,8 @@ class PartitionTable(list): """ Return a partition by type & subtype, returns None if not found """ # convert ptype & subtypes names (if supplied this way) to integer values - try: - ptype = TYPES[ptype] - except KeyError: - try: - ptype = int(ptype, 0) - except TypeError: - pass - try: - subtype = SUBTYPES[int(ptype)][subtype] - except KeyError: - try: - subtype = int(subtype, 0) - except TypeError: - pass + ptype = get_ptype_as_int(ptype) + subtype = get_subtype_as_int(subtype) for p in self: if p.type == ptype and p.subtype == subtype: @@ -471,15 +495,7 @@ def main(): md5sum = not args.disable_md5sum secure = args.secure offset_part_table = int(args.offset, 0) - input = args.input.read() - input_is_binary = input[0:2] == PartitionDefinition.MAGIC_BYTES - if input_is_binary: - status('Parsing binary partition input...') - table = PartitionTable.from_binary(input) - else: - input = input.decode() - status('Parsing CSV input...') - table = PartitionTable.from_csv(input) + table, input_is_binary = PartitionTable.from_file(args.input) if not args.no_verify: status('Verifying table...') diff --git a/components/partition_table/project_include.cmake b/components/partition_table/project_include.cmake index 9f0dc93854..87496f0406 100644 --- a/components/partition_table/project_include.cmake +++ b/components/partition_table/project_include.cmake @@ -1,6 +1,11 @@ -if(NOT BOOTLOADER_BUILD) - set(PARTITION_TABLE_OFFSET ${CONFIG_PARTITION_TABLE_OFFSET}) +set(PARTITION_TABLE_OFFSET ${CONFIG_PARTITION_TABLE_OFFSET}) +set(PARTITION_TABLE_CHECK_SIZES_TOOL_PATH "${CMAKE_CURRENT_LIST_DIR}/check_sizes.py") + +idf_build_get_property(build_dir BUILD_DIR) +idf_build_set_property(PARTITION_TABLE_BIN_PATH "${build_dir}/partition_table/partition-table.bin") + +if(NOT BOOTLOADER_BUILD) # Set PARTITION_CSV_PATH to the configured partition CSV file # absolute path if(CONFIG_PARTITION_TABLE_CUSTOM) @@ -49,3 +54,57 @@ function(partition_table_get_partition_info result get_part_info_args part_info) endif() set(${result} ${info} PARENT_SCOPE) endfunction() + +# Add a custom target (see add_custom_target) that checks a given binary built by the build system +# doesn't overflow any partitions with the given partition type and (optional) sub-type. +# +# Adding the target doesn't mean it gets called during the build, use add_dependencies to make another +# target depend on this one. +# +# Arguments: +# - target name - (first argument) name of the target to create +# - DEPENDS - dependencies the target should have (i.e. whatever target generates the binary). +# - BINARY_PATH - path to the target binary file to check +# - PARTITION_TYPE - partition type to check against +# - PARTITION_SUBTYPE - (optional) partition subtype to check against + +function(partition_table_add_check_size_target target_name) + # result binary_path partition_type partition_subtype + set(args BINARY_PATH PARTITION_TYPE PARTITION_SUBTYPE) + set(multi_args DEPENDS) + cmake_parse_arguments(CMD "" "${args}" "${multi_args}" ${ARGN}) + + idf_build_get_property(python PYTHON) + idf_build_get_property(table_bin PARTITION_TABLE_BIN_PATH) + if(CMD_PARTITION_SUBTYPE) + set(subtype_arg --subtype ${CMD_PARTITION_SUBTYPE}) + else() + set(subtype_arg) + endif() + set(command ${python} ${PARTITION_TABLE_CHECK_SIZES_TOOL_PATH} + --offset ${PARTITION_TABLE_OFFSET} + partition --type ${CMD_PARTITION_TYPE} ${subtype_arg} + ${table_bin} ${CMD_BINARY_PATH}) + + add_custom_target(${target_name} COMMAND ${command} DEPENDS ${CMD_DEPENDS} partition_table_bin) +endfunction() + +# Add a custom target (see add_custom_target) that checks a bootloader binary +# built by the build system will not overlap the partition table. +# +# Adding the target doesn't mean it gets called during the build, use add_dependencies to make another +# target depend on this one. +# +# Arguments: +# - target name - (first argument) name of the target to create +# - DEPENDS - dependencies the new target should have (i.e. whatever target generates the bootloader binary) +# - BOOTLOADER_BINARY_PATH - path to bootloader binary file +function(partition_table_add_check_bootloader_size_target target_name) + cmake_parse_arguments(CMD "" "BOOTLOADER_BINARY_PATH" "DEPENDS" ${ARGN}) + idf_build_get_property(python PYTHON) + + set(command ${python} ${PARTITION_TABLE_CHECK_SIZES_TOOL_PATH} + --offset ${PARTITION_TABLE_OFFSET} + bootloader ${BOOTLOADER_OFFSET} ${CMD_BOOTLOADER_BINARY_PATH}) + add_custom_target(${target_name} COMMAND ${command} DEPENDS ${CMD_DEPENDS}) +endfunction() diff --git a/components/partition_table/test_gen_esp32part_host/check_sizes_test.py b/components/partition_table/test_gen_esp32part_host/check_sizes_test.py new file mode 100755 index 0000000000..8dcd4a106b --- /dev/null +++ b/components/partition_table/test_gen_esp32part_host/check_sizes_test.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python +from __future__ import division, print_function + +import io +import sys +import unittest + +from test_utils import Py23TestCase + +try: + from typing import IO +except ImportError: + pass # only needed for type annotations + +try: + import check_sizes +except ImportError: + sys.path.append('..') + import check_sizes + +try: + import gen_esp32part +except ImportError: + sys.path.append('..') + import gen_esp32part + + +BASE_CSV = """ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap +nvs, data, nvs, , 0x4000, +otadata, data, ota, , 0x2000, +phy_init, data, phy, , 0x1000, + +""" + + +def gen_table(factory_appsize=None, ota0_appsize=None, ota1_appsize=None): # type: (str, str, str) -> io.BytesIO + """ generate a partition table binary with up to 3 app partitions with the specified sizes. """ + csv = BASE_CSV + if factory_appsize: + csv += 'factory, app, factory, , {}\n'.format(factory_appsize) + if ota0_appsize: + csv += 'ota0, app, ota_0, , {}\n'.format(ota0_appsize) + if ota1_appsize: + csv += 'ota1, app, ota_1, , {}\n'.format(ota1_appsize) + + table = gen_esp32part.PartitionTable.from_csv(csv) + return io.BytesIO(table.to_binary()) + + +def fake_file(kilobytes, name): # type: (int, str) -> IO + result = io.BytesIO(b'\xEE' * kilobytes * 1024) + result.name = name + return result + + +class TestAppSizes(Py23TestCase): + + def test_sizes_ok(self): # type: () -> None + pt = gen_table('1M') + app = fake_file(512, 'test.bin') + check_sizes.check_partition('app', None, pt, app) + + def test_single_part_too_small(self): # type: () -> None + pt = gen_table('1M') + app = fake_file(1500, 'too_big.bin') + with self.assertRaises(SystemExit) as e: + check_sizes.check_partition('app', None, pt, app) + self.assertIn('too small', str(e.exception)) + self.assertIn('too_big.bin', str(e.exception)) + + def test_all_parts_too_small(self): # type: () -> None + pt = gen_table('500K', '500K', '500K') + app = fake_file(501, 'just_too_big.bin') + with self.assertRaises(SystemExit) as e: + check_sizes.check_partition('app', None, pt, app) + self.assertIn('All', str(e.exception)) + self.assertIn('too small', str(e.exception)) + self.assertIn('just_too_big.bin', str(e.exception)) + + def test_one_part_too_small_warning(self): # type: () -> None + pt = gen_table('500K', '1M', '1M') + app = fake_file(501, 'big.bin') + # this will print a warning, no easy way to verify it looks correct + check_sizes.check_partition('app', None, pt, app) + + +class TestBootloaderSizes(Py23TestCase): + + def test_sizes_ok(self): # type: () -> None + bootloader = fake_file(25, 'test.bin') + check_sizes.check_bootloader(0x8000, 0x1000, bootloader) + + def test_bootloader_too_big_default(self): # type: () -> None + bootloader = fake_file(40, 'test.bin') + with self.assertRaises(SystemExit) as e: + check_sizes.check_bootloader(0x8000, 0x1000, bootloader) + self.assertIn('overlap', str(e.exception)) + # move the partition table offset up, it will pass + check_sizes.check_bootloader(0xb000, 0x1000, bootloader) + + +if __name__ == '__main__': + unittest.main() diff --git a/components/partition_table/test_gen_esp32part_host/gen_esp32part_tests.py b/components/partition_table/test_gen_esp32part_host/gen_esp32part_tests.py index 2af5031395..70911f12e4 100755 --- a/components/partition_table/test_gen_esp32part_host/gen_esp32part_tests.py +++ b/components/partition_table/test_gen_esp32part_host/gen_esp32part_tests.py @@ -10,6 +10,8 @@ import sys import tempfile import unittest +from test_utils import Py23TestCase + try: import gen_esp32part except ImportError: @@ -59,18 +61,6 @@ def _strip_trailing_ffs(binary_table): return binary_table -class Py23TestCase(unittest.TestCase): - - def __init__(self, *args, **kwargs): - super(Py23TestCase, self).__init__(*args, **kwargs) - try: - self.assertRaisesRegex - except AttributeError: - # assertRaisesRegexp is deprecated in Python3 but assertRaisesRegex doesn't exist in Python2 - # This fix is used in order to avoid using the alias from the six library - self.assertRaisesRegex = self.assertRaisesRegexp - - class CSVParserTests(Py23TestCase): def test_simple_partition(self): diff --git a/components/partition_table/test_gen_esp32part_host/test_utils.py b/components/partition_table/test_gen_esp32part_host/test_utils.py new file mode 100644 index 0000000000..688d6626ae --- /dev/null +++ b/components/partition_table/test_gen_esp32part_host/test_utils.py @@ -0,0 +1,18 @@ +import unittest + +try: + from typing import Any +except ImportError: + pass # only needed to check type annotations + + +class Py23TestCase(unittest.TestCase): + + def __init__(self, *args, **kwargs): # type: (Any, Any) -> None + super(Py23TestCase, self).__init__(*args, **kwargs) + try: + self.assertRaisesRegex + except AttributeError: + # assertRaisesRegexp is deprecated in Python3 but assertRaisesRegex doesn't exist in Python2 + # This fix is used in order to avoid using the alias from the six library + self.assertRaisesRegex = self.assertRaisesRegexp # type: ignore diff --git a/docs/en/api-guides/partition-tables.rst b/docs/en/api-guides/partition-tables.rst index a6e68e243a..3a6ef16547 100644 --- a/docs/en/api-guides/partition-tables.rst +++ b/docs/en/api-guides/partition-tables.rst @@ -169,6 +169,24 @@ To display the contents of a binary partition table on stdout (this is how the s python gen_esp32part.py binary_partitions.bin +Partition Size Checks +--------------------- + +The ESP-IDF build system will automatically check if generated binaries fit in the available partition space, and will fail with an error if a binary is too large. + +Currently these checks are performed for the following binaries: + +* Bootloader binary must fit in space before partition table (see :ref:`secure-boot-bootloader-size`) +* App binary should fit in at least one partition of type "app". If the app binary doesn't fit in any app partition, the build will fail. If it only fits in some of the app partitions, a warning is printed about this. + +.. note:: + + Although the build process will fail if the size check returns an error, the binary files are still generated and can be flashed (although they may not work if they are too large for the available space.) + +.. note:: + + Build system binary size checks are only performed when using the CMake build system. When using the legacy GNU Make build system, file sizes can be checked manually or an error will be logged during boot. + MD5 checksum ~~~~~~~~~~~~ diff --git a/docs/en/security/secure-boot-bootloader-size.rst b/docs/en/security/secure-boot-bootloader-size.rst index df403f3e75..3f508f86c4 100644 --- a/docs/en/security/secure-boot-bootloader-size.rst +++ b/docs/en/security/secure-boot-bootloader-size.rst @@ -5,14 +5,18 @@ Bootloader Size =============== - +{IDF_TARGET_DEFAULT_MAX_BOOTLOADER_SIZE:default = "0x8000 (32768)", esp32 = "0x7000 (28672)", esp32s2 = "0x7000 (28672)"} {IDF_TARGET_MAX_BOOTLOADER_SIZE:default = "64KB (0x10000 bytes)", esp32 = "48KB (0xC000 bytes)"} {IDF_TARGET_MAX_PARTITION_TABLE_OFFSET:default = "0x12000", esp32 = "0xE000"} .. Above is calculated as 0x1000 at start of flash + IDF_TARGET_MAX_BOOTLOADER_SIZE + 0x1000 signature sector -When secure boot is enabled the bootloader app binary ``bootloader.bin`` may exceed the default bootloader size limit. This is especially likely if flash encryption is enabled as well. The default size limit is 0x7000 (28672) bytes (partition table offset 0x8000 - bootloader offset 0x1000). +When secure boot is enabled the bootloader app binary ``bootloader.bin`` may exceed the bootloader binary size limit and overlap with the partition table. This is especially likely if flash encryption is enabled as well. When using the default :ref:`CONFIG_PARTITION_TABLE_OFFSET` value 0x8000, the size limit is {IDF_TARGET_DEFAULT_MAX_BOOTLOADER_SIZE} bytes. -If the bootloader becomes too large, the {IDF_TARGET_NAME} will fail to boot - errors will be logged about either invalid partition table or invalid bootloader checksum. +If the bootloader binary is too large, then the bootloader build will fail with an error "Bootloader binary size [..] is too large for partition table offset". If the bootloader binary is flashed anyhow then the {IDF_TARGET_NAME} will fail to boot - errors will be logged about either invalid partition table or invalid bootloader checksum. + +.. note:: + + The bootloader size check only happens in the CMake Build System, when using the legacy GNU Make Build System the size is not checked but the {IDF_TARGET_NAME} will fail to boot if bootloader is too large. When Secure Boot V2 is enabled, there is also an absolute binary size limit of {IDF_TARGET_MAX_BOOTLOADER_SIZE} (excluding the 4KB signature), because the bootloader is first loaded into a fixed size buffer for verification. @@ -20,6 +24,6 @@ Options to work around this are: - Set :ref:`bootloader compiler optimization ` back to "Size" if it has been changed from this default value. - Reduce :ref:`bootloader log level `. Setting log level to Warning, Error or None all significantly reduce the final binary size (but may make it harder to debug). -- Set :ref:`partition table offset ` to a higher value than 0x8000, to place the partition table later in the flash. This increases the space available for the bootloader. If the :doc:`partition table ` CSV file contains explicit partition offsets, they will need changing so no partition has an offset lower than ``CONFIG_PARTITION_TABLE_OFFSET + 0x1000``. (This includes the default partition CSV files supplied with ESP-IDF.) +- Set :ref:`CONFIG_PARTITION_TABLE_OFFSET` to a higher value than 0x8000, to place the partition table later in the flash. This increases the space available for the bootloader. If the :doc:`partition table ` CSV file contains explicit partition offsets, they will need changing so no partition has an offset lower than ``CONFIG_PARTITION_TABLE_OFFSET + 0x1000``. (This includes the default partition CSV files supplied with ESP-IDF.) - Note that because of the absolute binary size limit, there is no benefit to moving the partition table any higher than offset {IDF_TARGET_MAX_PARTITION_TABLE_OFFSET}. +Note that because of the absolute binary size limit, when using Secure Boot V2 there is no benefit to moving the partition table any higher than offset {IDF_TARGET_MAX_PARTITION_TABLE_OFFSET}. diff --git a/tools/ci/check_build_warnings.py b/tools/ci/check_build_warnings.py index 0ccd5ed3b0..348943b330 100755 --- a/tools/ci/check_build_warnings.py +++ b/tools/ci/check_build_warnings.py @@ -32,6 +32,7 @@ IGNORE_WARNS = [ r'CryptographyDeprecationWarning', r'Python 3 versions older than 3.6 are not supported.', r'Support for Python 2 is deprecated and will be removed in future versions.', + r'Warning: \d+/\d+ app partitions are too small for binary' ] ] @@ -49,7 +50,7 @@ def line_has_warnings(line): # type: (str) -> bool return has_warnings -def main(): +def main(): # type: () -> None parser = argparse.ArgumentParser(description='ESP-IDF app builder') parser.add_argument( '-v', diff --git a/tools/ci/executable-list.txt b/tools/ci/executable-list.txt index 6a6ec198a9..5e293e3287 100644 --- a/tools/ci/executable-list.txt +++ b/tools/ci/executable-list.txt @@ -10,9 +10,11 @@ components/heap/test_multi_heap_host/test_all_configs.sh components/mbedtls/esp_crt_bundle/gen_crt_bundle.py components/mbedtls/esp_crt_bundle/test_gen_crt_bundle/test_gen_crt_bundle.py components/nvs_flash/nvs_partition_generator/nvs_partition_gen.py +components/partition_table/check_sizes.py components/partition_table/gen_empty_partition.py components/partition_table/gen_esp32part.py components/partition_table/parttool.py +components/partition_table/test_gen_esp32part_host/check_sizes_test.py components/partition_table/test_gen_esp32part_host/gen_esp32part_tests.py components/spiffs/spiffsgen.py components/ulp/esp32ulp_mapgen.py diff --git a/tools/ci/mypy_ignore_list.txt b/tools/ci/mypy_ignore_list.txt index addd024731..4c8d021df6 100644 --- a/tools/ci/mypy_ignore_list.txt +++ b/tools/ci/mypy_ignore_list.txt @@ -156,7 +156,6 @@ tools/ble/lib_gatt.py tools/check_python_dependencies.py tools/check_term.py tools/ci/check_artifacts_expire_time.py -tools/ci/check_build_warnings.py tools/ci/check_callgraph.py tools/ci/check_codeowners.py tools/ci/check_deprecated_kconfigs.py