diff --git a/components/esp32/ld/esp32.ld b/components/esp32/ld/esp32.ld index 22544af788..ffde663904 100644 --- a/components/esp32/ld/esp32.ld +++ b/components/esp32/ld/esp32.ld @@ -134,3 +134,12 @@ REGION_ALIAS("rtc_data_location", rtc_data_seg ); #else REGION_ALIAS("default_rodata_seg", dram0_0_seg); #endif // CONFIG_APP_BUILD_USE_FLASH_SECTIONS + +/** + * If rodata default segment is placed in `drom0_0_seg`, then flash's first rodata section must + * also be first in the segment. + */ +#ifdef CONFIG_APP_BUILD_USE_FLASH_SECTIONS + ASSERT(_rodata_start == ORIGIN(default_rodata_seg), + ".flash.appdesc section must be placed at the beginning of the rodata segment.") +#endif diff --git a/components/esp32/ld/esp32.project.ld.in b/components/esp32/ld/esp32.project.ld.in index 6c5d40b524..4d1d7bd701 100644 --- a/components/esp32/ld/esp32.project.ld.in +++ b/components/esp32/ld/esp32.project.ld.in @@ -270,14 +270,23 @@ SECTIONS ASSERT(((_bss_end - ORIGIN(dram0_0_seg)) <= LENGTH(dram0_0_seg)), "DRAM segment data does not fit.") - /* When modifying the alignment, update tls_section_alignment in pxPortInitialiseStack */ - .flash.rodata : ALIGN(0x10) + .flash.appdesc : ALIGN(0x10) { _rodata_start = ABSOLUTE(.); *(.rodata_desc .rodata_desc.*) /* Should be the first. App version info. DO NOT PUT ANYTHING BEFORE IT! */ *(.rodata_custom_desc .rodata_custom_desc.*) /* Should be the second. Custom app version info. DO NOT PUT ANYTHING BEFORE IT! */ + /* Create an empty gap within this section. Thanks to this, the end of this + * section will match .flah.rodata's begin address. Thus, both sections + * will be merged when creating the final bin image. */ + . = ALIGN(ALIGNOF(.flash.rodata)); + } >default_rodata_seg + + .flash.rodata : ALIGN(0x10) + { + _flash_rodata_start = ABSOLUTE(.); + mapping[flash_rodata] @@ -335,6 +344,8 @@ SECTIONS . = ALIGN(4); } >default_rodata_seg + _flash_rodata_align = ALIGNOF(.flash.rodata); + .flash.text : { _stext = .; diff --git a/components/esp32c3/ld/esp32c3.ld b/components/esp32c3/ld/esp32c3.ld index 7f897a1c91..63a26c4276 100644 --- a/components/esp32c3/ld/esp32c3.ld +++ b/components/esp32c3/ld/esp32c3.ld @@ -107,3 +107,12 @@ REGION_ALIAS("rtc_data_location", rtc_iram_seg ); #else REGION_ALIAS("default_rodata_seg", dram0_0_seg); #endif // CONFIG_APP_BUILD_USE_FLASH_SECTIONS + +/** + * If rodata default segment is placed in `drom0_0_seg`, then flash's first rodata section must + * also be first in the segment. + */ +#if CONFIG_APP_BUILD_USE_FLASH_SECTIONS + ASSERT(_flash_rodata_dummy_start == ORIGIN(default_rodata_seg), + ".flash_rodata_dummy section must be placed at the beginning of the rodata segment.") +#endif diff --git a/components/esp32c3/ld/esp32c3.project.ld.in b/components/esp32c3/ld/esp32c3.project.ld.in index 342a968a95..2419f1500f 100644 --- a/components/esp32c3/ld/esp32c3.project.ld.in +++ b/components/esp32c3/ld/esp32c3.project.ld.in @@ -283,21 +283,40 @@ SECTIONS _flash_cache_start = ABSOLUTE(0); } > default_code_seg + /** + * This dummy section represents the .flash.text section but in default_rodata_seg. + * Thus, it must have its alignement and (at least) its size. + */ .flash_rodata_dummy (NOLOAD): { - . = SIZEOF(.flash.text); + _flash_rodata_dummy_start = .; + /* Start at the same alignement constraint than .flash.text */ + . = ALIGN(ALIGNOF(.flash.text)); + /* Create an empty gap as big as .flash.text section */ + . = . + SIZEOF(.flash.text); + /* Prepare the alignement of the section above. Few bytes (0x20) must be + * added for the mapping header. */ . = ALIGN(0x10000) + 0x20; _rodata_reserved_start = .; } > default_rodata_seg - /* When modifying the alignment, don't forget to update tls_section_alignment in pxPortInitialiseStack */ - .flash.rodata : ALIGN(0x10) + .flash.appdesc : ALIGN(0x10) { _rodata_start = ABSOLUTE(.); *(.rodata_desc .rodata_desc.*) /* Should be the first. App version info. DO NOT PUT ANYTHING BEFORE IT! */ *(.rodata_custom_desc .rodata_custom_desc.*) /* Should be the second. Custom app version info. DO NOT PUT ANYTHING BEFORE IT! */ + /* Create an empty gap within this section. Thanks to this, the end of this + * section will match .flash.rodata's begin address. Thus, both sections + * will be merged when creating the final bin image. */ + . = ALIGN(ALIGNOF(.flash.rodata)); + } >default_rodata_seg + + .flash.rodata : ALIGN(0x10) + { + _flash_rodata_start = ABSOLUTE(.); + mapping[flash_rodata] *(.irom1.text) /* catch stray ICACHE_RODATA_ATTR */ diff --git a/components/esp32s2/ld/esp32s2.ld b/components/esp32s2/ld/esp32s2.ld index 2ae61149ff..668d6fce6a 100644 --- a/components/esp32s2/ld/esp32s2.ld +++ b/components/esp32s2/ld/esp32s2.ld @@ -136,3 +136,13 @@ REGION_ALIAS("rtc_data_location", rtc_data_seg ); #else REGION_ALIAS("default_rodata_seg", dram0_0_seg); #endif // CONFIG_APP_BUILD_USE_FLASH_SECTIONS + + +/** + * If rodata default segment is placed in `drom0_0_seg`, then flash's first rodata section must + * also be first in the segment. + */ +#ifdef CONFIG_APP_BUILD_USE_FLASH_SECTIONS + ASSERT(_rodata_reserved_start == ORIGIN(default_rodata_seg), + ".flash.appdesc section must be placed at the beginning of the rodata segment.") +#endif diff --git a/components/esp32s2/ld/esp32s2.project.ld.in b/components/esp32s2/ld/esp32s2.project.ld.in index 06528a15b2..172e8296fc 100644 --- a/components/esp32s2/ld/esp32s2.project.ld.in +++ b/components/esp32s2/ld/esp32s2.project.ld.in @@ -265,8 +265,7 @@ SECTIONS _bss_end = ABSOLUTE(.); } > dram0_0_seg - /* When modifying the alignment, update tls_section_alignment in pxPortInitialiseStack */ - .flash.rodata : ALIGN(0x10) + .flash.appdesc : ALIGN(0x10) { _rodata_reserved_start = ABSOLUTE(.); _rodata_start = ABSOLUTE(.); @@ -274,6 +273,16 @@ SECTIONS *(.rodata_desc .rodata_desc.*) /* Should be the first. App version info. DO NOT PUT ANYTHING BEFORE IT! */ *(.rodata_custom_desc .rodata_custom_desc.*) /* Should be the second. Custom app version info. DO NOT PUT ANYTHING BEFORE IT! */ + /* Create an empty gap within this section. Thanks to this, the end of this + * section will match .flah.rodata's begin address. Thus, both sections + * will be merged when creating the final bin image. */ + . = ALIGN(ALIGNOF(.flash.rodata)); + } >default_rodata_seg + + .flash.rodata : ALIGN(0x10) + { + _flash_rodata_start = ABSOLUTE(.); + mapping[flash_rodata] *(.irom1.text) /* catch stray ICACHE_RODATA_ATTR */ @@ -330,6 +339,8 @@ SECTIONS . = ALIGN(4); } >default_rodata_seg + _flash_rodata_align = ALIGNOF(.flash.rodata); + .flash.text : { _stext = .; diff --git a/components/esp32s3/ld/esp32s3.ld b/components/esp32s3/ld/esp32s3.ld index 1b7c3d1bb9..e6c3c7bce2 100644 --- a/components/esp32s3/ld/esp32s3.ld +++ b/components/esp32s3/ld/esp32s3.ld @@ -117,3 +117,12 @@ REGION_ALIAS("default_rodata_seg", drom0_0_seg); #else REGION_ALIAS("default_rodata_seg", dram0_0_seg); #endif // CONFIG_APP_BUILD_USE_FLASH_SECTIONS + +/** + * If rodata default segment is placed in `drom0_0_seg`, then flash's first rodata section must + * also be first in the segment. + */ +#if CONFIG_APP_BUILD_USE_FLASH_SECTIONS + ASSERT(_flash_rodata_dummy_start == ORIGIN(default_rodata_seg), + ".flash_rodata_dummy section must be placed at the beginning of the rodata segment.") +#endif diff --git a/components/esp32s3/ld/esp32s3.project.ld.in b/components/esp32s3/ld/esp32s3.project.ld.in index ff46c4d269..4735e1fd0f 100644 --- a/components/esp32s3/ld/esp32s3.project.ld.in +++ b/components/esp32s3/ld/esp32s3.project.ld.in @@ -314,21 +314,40 @@ SECTIONS _flash_cache_start = ABSOLUTE(0); } > default_code_seg + /** + * This dummy section represents the .flash.text section but in default_rodata_seg. + * Thus, it must have its alignement and (at least) its size. + */ .flash_rodata_dummy (NOLOAD): { - . = SIZEOF(.flash.text); + _flash_rodata_dummy_start = .; + /* Start at the same alignement constraint than .flash.text */ + . = ALIGN(ALIGNOF(.flash.text)); + /* Create an empty gap as big as .flash.text section */ + . = . + SIZEOF(.flash.text); + /* Prepare the alignement of the section above. Few bytes (0x20) must be + * added for the mapping header. */ . = ALIGN(0x10000) + 0x20; _rodata_reserved_start = .; } > default_rodata_seg - /* When modifying the alignment, don't forget to update tls_section_alignment in pxPortInitialiseStack */ - .flash.rodata : ALIGN(0x10) + .flash.appdesc : ALIGN(0x10) { _rodata_start = ABSOLUTE(.); *(.rodata_desc .rodata_desc.*) /* Should be the first. App version info. DO NOT PUT ANYTHING BEFORE IT! */ *(.rodata_custom_desc .rodata_custom_desc.*) /* Should be the second. Custom app version info. DO NOT PUT ANYTHING BEFORE IT! */ + /* Create an empty gap within this section. Thanks to this, the end of this + * section will match .flah.rodata's begin address. Thus, both sections + * will be merged when creating the final bin image. */ + . = ALIGN(ALIGNOF(.flash.rodata)); + } >default_rodata_seg + + .flash.rodata : ALIGN(0x10) + { + _flash_rodata_start = ABSOLUTE(.); + mapping[flash_rodata] *(.irom1.text) /* catch stray ICACHE_RODATA_ATTR */ @@ -382,6 +401,8 @@ SECTIONS . = ALIGN(4); } > default_rodata_seg + _flash_rodata_align = ALIGNOF(.flash.rodata); + /* Marks the end of IRAM code segment */ .iram0.text_end (NOLOAD) : { diff --git a/components/freertos/port/riscv/port.c b/components/freertos/port/riscv/port.c index e53c20eb2c..2f6786841a 100644 --- a/components/freertos/port/riscv/port.c +++ b/components/freertos/port/riscv/port.c @@ -225,18 +225,58 @@ StackType_t *pxPortInitialiseStack(StackType_t *pxTopOfStack, TaskFunction_t pxC extern uint32_t __global_pointer$; uint8_t* task_thread_local_start; uint8_t* threadptr; - extern char _thread_local_start, _thread_local_end, _rodata_start; + extern char _thread_local_start, _thread_local_end, _flash_rodata_start; /* Byte pointer, so that subsequent calculations don't depend on sizeof(StackType_t). */ uint8_t* sp = (uint8_t*) pxTopOfStack; - /* Set up TLS area */ + /* Set up TLS area. + * The following diagram illustrates the layout of link-time and run-time + * TLS sections. + * + * +-------------+ + * |Section: | Linker symbols: + * |.flash.rodata| --------------- + * 0x0+-------------+ <-- _flash_rodata_start + * ^ | | + * | | Other data | + * | | ... | + * | +-------------+ <-- _thread_local_start + * | |.tbss | ^ + * v | | | + * 0xNNNN|int example; | | (thread_local_size) + * |.tdata | v + * +-------------+ <-- _thread_local_end + * | Other data | + * | ... | + * | | + * +-------------+ + * + * Local variables of + * pxPortInitialiseStack + * ----------------------- + * +-------------+ <-- pxTopOfStack + * |.tdata (*) | ^ + * ^ |int example; | |(thread_local_size + * | | | | + * | |.tbss (*) | v + * | +-------------+ <-- task_thread_local_start + * 0xNNNN | | | ^ + * | | | | + * | | | |_thread_local_start - _rodata_start + * | | | | + * | | | v + * v +-------------+ <-- threadptr + * + * (*) The stack grows downward! + */ + uint32_t thread_local_sz = (uint32_t) (&_thread_local_end - &_thread_local_start); thread_local_sz = ALIGNUP(0x10, thread_local_sz); sp -= thread_local_sz; task_thread_local_start = sp; memcpy(task_thread_local_start, &_thread_local_start, thread_local_sz); - threadptr = task_thread_local_start - (&_thread_local_start - &_rodata_start); + threadptr = task_thread_local_start - (&_thread_local_start - &_flash_rodata_start); /* Simulate the stack frame as it would be created by a context switch interrupt. */ sp -= RV_STK_FRMSZ; diff --git a/components/freertos/port/xtensa/port.c b/components/freertos/port/xtensa/port.c index 438d9dcfe5..edd8205df5 100644 --- a/components/freertos/port/xtensa/port.c +++ b/components/freertos/port/xtensa/port.c @@ -189,7 +189,7 @@ StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t px #endif uint32_t *threadptr; void *task_thread_local_start; - extern int _thread_local_start, _thread_local_end, _rodata_start; + extern int _thread_local_start, _thread_local_end, _flash_rodata_start, _flash_rodata_align; // TODO: check that TLS area fits the stack uint32_t thread_local_sz = (uint8_t *)&_thread_local_end - (uint8_t *)&_thread_local_start; @@ -248,24 +248,25 @@ StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t px frame->vpri = 0xFFFFFFFF; #endif - /* Init threadptr reg and TLS vars */ + /* Init threadptr register and set up TLS run-time area. + * The diagram in port/riscv/port.c illustrates the calculations below. + */ task_thread_local_start = (void *)(((uint32_t)pxTopOfStack - XT_CP_SIZE - thread_local_sz) & ~0xf); memcpy(task_thread_local_start, &_thread_local_start, thread_local_sz); threadptr = (uint32_t *)(sp + XT_STK_EXTRA); - /* Calculate THREADPTR value: + /* Calculate THREADPTR value. * The generated code will add THREADPTR value to a constant value determined at link time, * to get the address of the TLS variable. * The constant value is calculated by the linker as follows * (search for 'tpoff' in elf32-xtensa.c in BFD): * offset = address - tls_section_vma + align_up(TCB_SIZE, tls_section_alignment) - * where TCB_SIZE is hardcoded to 8. There doesn't seem to be a way to propagate - * the section alignment value from the ld script into the code, so it is hardcoded - * in both places. + * where TCB_SIZE is hardcoded to 8. + * Note this is slightly different compared to the RISC-V port, where offset = address - tls_section_vma. */ - const uint32_t tls_section_alignment = 0x10; /* has to be in sync with ALIGN value of .flash.rodata section */ + const uint32_t tls_section_alignment = (uint32_t) &_flash_rodata_align; /* ALIGN value of .flash.rodata section */ const uint32_t tcb_size = 8; /* Unrelated to FreeRTOS, this is the constant from BFD */ const uint32_t base = (tcb_size + tls_section_alignment - 1) & (~(tls_section_alignment - 1)); - *threadptr = (uint32_t)task_thread_local_start - ((uint32_t)&_thread_local_start - (uint32_t)&_rodata_start) - base; + *threadptr = (uint32_t)task_thread_local_start - ((uint32_t)&_thread_local_start - (uint32_t)&_flash_rodata_start) - base; #if XCHAL_CP_NUM > 0 /* Init the coprocessor save area (see xtensa_context.h) */ diff --git a/tools/test_apps/build_system/ldalign_test/CMakeLists.txt b/tools/test_apps/build_system/ldalign_test/CMakeLists.txt new file mode 100644 index 0000000000..74eb8e8377 --- /dev/null +++ b/tools/test_apps/build_system/ldalign_test/CMakeLists.txt @@ -0,0 +1,18 @@ +# 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(ldalign_test) + +idf_build_get_property(python PYTHON) +idf_build_get_property(elf EXECUTABLE) + +set(check_alignment "${CMAKE_CURRENT_LIST_DIR}/check_alignment.py") +set(readelf "${CMAKE_TOOLCHAIN_PREFIX}readelf") + +add_custom_command( + TARGET ${elf} + POST_BUILD + COMMAND ${python} ${check_alignment} ${readelf} $ +) diff --git a/tools/test_apps/build_system/ldalign_test/README.txt b/tools/test_apps/build_system/ldalign_test/README.txt new file mode 100644 index 0000000000..a6299c7fad --- /dev/null +++ b/tools/test_apps/build_system/ldalign_test/README.txt @@ -0,0 +1,7 @@ +Runs a build test to check alignment and position of `.flash.appdesc` and +`.flash.rodata` sections. Indeed, `.flash.appdesc` shall ALWAYS be aligned on +a 16-byte bounds, whereas `.flash.rodata` can have any alignment. In any case, +the end address of first one shall match the start address of the second one. +This will let both of them be merged when generating the final bin image. +The Python script that performs the checks, `check_alignment.py`, automatically +runs after the app is built. diff --git a/tools/test_apps/build_system/ldalign_test/check_alignment.py b/tools/test_apps/build_system/ldalign_test/check_alignment.py new file mode 100644 index 0000000000..1f988fd9c7 --- /dev/null +++ b/tools/test_apps/build_system/ldalign_test/check_alignment.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. +# + +import argparse +import re +import subprocess +from typing import Tuple + +argparser = argparse.ArgumentParser() + +argparser.add_argument('readelf') +argparser.add_argument('elf') + +args = argparser.parse_args() + +# Get the content of the readelf command +contents = subprocess.check_output([args.readelf, '-S', args.elf]).decode() + + +# Define a class for readelf parsing error +class ParsingError(Exception): + pass + + +# Look for the start address and size of any section +def find_partition_info(sectionname): # type: (str) -> Tuple[int, int, int] + match = re.search(sectionname + r'\s+PROGBITS\s+([a-f0-9]+) [a-f0-9]+ ([a-f0-9]+) \d+\s+[A-Z]+ 0 0 (\d+)', + contents) + if not match: + raise ParsingError('ELF header parsing error') + # Return the address of the section, the size and the alignment + address = match.group(1) + size = match.group(2) + alignment = match.group(3) + return (int(address, 16), int(size, 16), int(alignment, 10)) + + +# Get address and size for .flash.appdesc section +app_address, app_size, app_align = find_partition_info('.flash.appdesc') + +# Same goes for .flash.rodata section +rodata_address, _, rodata_align = find_partition_info('.flash.rodata') + +# Assert than everything is as expected: +# appdesc is aligned on 16 +# rodata is aligned on 64 +# appdesc ends where rodata starts +assert app_align == 16, '.flash.appdesc section should have been aligned on 16!' +assert rodata_align == 64, '.flash.rodata section should have been aligned on 64!' +assert app_address + app_size == rodata_address, ".flash.appdesc's end address and .flash.rodata's begin start must have no gap in between!" diff --git a/tools/test_apps/build_system/ldalign_test/main/CMakeLists.txt b/tools/test_apps/build_system/ldalign_test/main/CMakeLists.txt new file mode 100644 index 0000000000..1df31fac80 --- /dev/null +++ b/tools/test_apps/build_system/ldalign_test/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "test_main.c" + INCLUDE_DIRS ".") diff --git a/tools/test_apps/build_system/ldalign_test/main/test_main.c b/tools/test_apps/build_system/ldalign_test/main/test_main.c new file mode 100644 index 0000000000..160197ba59 --- /dev/null +++ b/tools/test_apps/build_system/ldalign_test/main/test_main.c @@ -0,0 +1,16 @@ +#include + +const static uint32_t __attribute__ ((aligned (64))) testTab[] = + { + 0xff445566, 0x44556677, 0x33221100, + 0x88997755, 0x99887755, 0x88997755, + 0x99546327, 0x7946fa9e, 0xa6b5f8ee, + 0x12345678 + }; + +void app_main(void) +{ + /* Do something with the array, in order to avoid it being discarded. */ + for (uint32_t i = 0; i < 10; i++) + printf ("%x\n", testTab[i]); +}