From a9878531624db3d79eedc443aae26da0e85826af Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Mon, 8 Apr 2024 15:12:13 +0100 Subject: [PATCH] CI: Attempt to catch flash overflow. Using a custom patch against MicroPython's CMakeLists.txt this will optionally use a memmap_mp.ld file from the *board* directory and allow us to check if the allocated app flash overflows. Linker scripts need configured on a per board basis. --- .github/workflows/micropython.yml | 6 + ci/micropython.sh | 8 + micropython/board/RPI_PICO/memmap_mp.ld | 274 +++++++++++++++++++++ micropython/board/RPI_PICO_W/memmap_mp.ld | 274 +++++++++++++++++++++ micropython/micropython_board_linker.patch | 17 ++ 5 files changed, 579 insertions(+) create mode 100644 micropython/board/RPI_PICO/memmap_mp.ld create mode 100644 micropython/board/RPI_PICO_W/memmap_mp.ld create mode 100644 micropython/micropython_board_linker.patch diff --git a/.github/workflows/micropython.yml b/.github/workflows/micropython.yml index bc1a5a7d..9b6c76a1 100644 --- a/.github/workflows/micropython.yml +++ b/.github/workflows/micropython.yml @@ -98,6 +98,12 @@ jobs: source $BUILD_TOOLS hack_patch_micropython_print_memory_usage + - name: "HACK: CMakeLists.txt Enable Custom Linker Patch" + shell: bash + run: | + source $BUILD_TOOLS + hack_patch_micropython_enable_custom_linker + - name: "HACK: Pico SDK Patch" shell: bash run: | diff --git a/ci/micropython.sh b/ci/micropython.sh index 1bd56730..9e586368 100644 --- a/ci/micropython.sh +++ b/ci/micropython.sh @@ -55,6 +55,14 @@ function hack_patch_micropython_print_memory_usage { cd ../ } +function hack_patch_micropython_enable_custom_linker { + if [ -f "$MICROPY_BOARD_DIR/memmap_mp.ld" ]; then + cd micropython + git apply $PIMORONI_PICO_DIR/micropython/micropython_board_linker.patch + cd ../ + fi +} + function hack_patch_pico_sdk { # pico-sdk-patch.sh will apply the patch if it exists cd micropython diff --git a/micropython/board/RPI_PICO/memmap_mp.ld b/micropython/board/RPI_PICO/memmap_mp.ld new file mode 100644 index 00000000..e99ce287 --- /dev/null +++ b/micropython/board/RPI_PICO/memmap_mp.ld @@ -0,0 +1,274 @@ +/* Based on GCC ARM embedded samples. + Defines the following symbols for use by code: + __exidx_start + __exidx_end + __etext + __data_start__ + __preinit_array_start + __preinit_array_end + __init_array_start + __init_array_end + __fini_array_start + __fini_array_end + __data_end__ + __bss_start__ + __bss_end__ + __end__ + end + __HeapLimit + __StackLimit + __StackTop + __stack (== StackTop) +*/ + +/* Default RP2 flash size. Assumes no user filesystem. */ +_flash_start = 0x10000000; +_flash_size = 2048k; +_flash_app_size = 640k; +_flash_fs_size = _flash_size - _flash_app_size; +_flash_fs_start = _flash_start + _flash_app_size; + +MEMORY +{ + FLASH(rx) : ORIGIN = _flash_start, LENGTH = _flash_size + APP(rx) : ORIGIN = _flash_start, LENGTH = _flash_app_size + FILESYSTEM(r) : ORIGIN = _flash_fs_start, LENGTH = _flash_fs_size + RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 256k + SCRATCH_X(rwx) : ORIGIN = 0x20040000, LENGTH = 4k + SCRATCH_Y(rwx) : ORIGIN = 0x20041000, LENGTH = 4k +} + +ENTRY(_entry_point) + +SECTIONS +{ + /* Second stage bootloader is prepended to the image. It must be 256 bytes big + and checksummed. It is usually built by the boot_stage2 target + in the Raspberry Pi Pico SDK + */ + + .flash_begin : { + __flash_binary_start = .; + } > APP + + .boot2 : { + __boot2_start__ = .; + KEEP (*(.boot2)) + __boot2_end__ = .; + } > APP + + ASSERT(__boot2_end__ - __boot2_start__ == 256, + "ERROR: Pico second stage bootloader must be 256 bytes in size") + + /* The second stage will always enter the image at the start of .text. + The debugger will use the ELF entry point, which is the _entry_point + symbol if present, otherwise defaults to start of .text. + This can be used to transfer control back to the bootrom on debugger + launches only, to perform proper flash setup. + */ + + .text : { + __logical_binary_start = .; + KEEP (*(.vectors)) + KEEP (*(.binary_info_header)) + __binary_info_header_end = .; + KEEP (*(.reset)) + /* TODO revisit this now memset/memcpy/float in ROM */ + /* bit of a hack right now to exclude all floating point and time critical (e.g. memset, memcpy) code from + * FLASH ... we will include any thing excluded here in .data below by default */ + *(.init) + /* Change for MicroPython... exclude gc.c, parse.c, vm.c from flash */ + *(EXCLUDE_FILE(*libgcc.a: *libc.a: *lib_a-mem*.o *libm.a: *gc.c.obj *vm.c.obj *parse.c.obj) .text*) + *(.fini) + /* Pull all c'tors into .text */ + *crtbegin.o(.ctors) + *crtbegin?.o(.ctors) + *(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors) + *(SORT(.ctors.*)) + *(.ctors) + /* Followed by destructors */ + *crtbegin.o(.dtors) + *crtbegin?.o(.dtors) + *(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors) + *(SORT(.dtors.*)) + *(.dtors) + + *(.eh_frame*) + . = ALIGN(4); + } > APP + + .rodata : { + *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .rodata*) + . = ALIGN(4); + *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.flashdata*))) + . = ALIGN(4); + } > APP + + .ARM.extab : + { + *(.ARM.extab* .gnu.linkonce.armextab.*) + } > APP + + __exidx_start = .; + .ARM.exidx : + { + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + } > APP + __exidx_end = .; + + /* Machine inspectable binary information */ + . = ALIGN(4); + __binary_info_start = .; + .binary_info : + { + KEEP(*(.binary_info.keep.*)) + *(.binary_info.*) + } > APP + __binary_info_end = .; + . = ALIGN(4); + + /* End of .text-like segments */ + __etext = .; + + .ram_vector_table (COPY): { + *(.ram_vector_table) + } > RAM + + .data : { + __data_start__ = .; + *(vtable) + + *(.time_critical*) + + /* remaining .text and .rodata; i.e. stuff we exclude above because we want it in RAM */ + *(.text*) + . = ALIGN(4); + *(.rodata*) + . = ALIGN(4); + + *(.data*) + + . = ALIGN(4); + *(.after_data.*) + . = ALIGN(4); + /* preinit data */ + PROVIDE_HIDDEN (__mutex_array_start = .); + KEEP(*(SORT(.mutex_array.*))) + KEEP(*(.mutex_array)) + PROVIDE_HIDDEN (__mutex_array_end = .); + + . = ALIGN(4); + /* preinit data */ + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP(*(SORT(.preinit_array.*))) + KEEP(*(.preinit_array)) + PROVIDE_HIDDEN (__preinit_array_end = .); + + . = ALIGN(4); + /* init data */ + PROVIDE_HIDDEN (__init_array_start = .); + KEEP(*(SORT(.init_array.*))) + KEEP(*(.init_array)) + PROVIDE_HIDDEN (__init_array_end = .); + + . = ALIGN(4); + /* finit data */ + PROVIDE_HIDDEN (__fini_array_start = .); + *(SORT(.fini_array.*)) + *(.fini_array) + PROVIDE_HIDDEN (__fini_array_end = .); + + *(.jcr) + . = ALIGN(4); + /* All data end */ + __data_end__ = .; + } > RAM AT> APP + + .uninitialized_data (COPY): { + . = ALIGN(4); + *(.uninitialized_data*) + } > RAM + + /* bss without zero init on startup */ + .uninitialized_bss (NOLOAD): { + . = ALIGN(4); + *(.uninitialized_bss*) + } > RAM + + /* Start and end symbols must be word-aligned */ + .scratch_x : { + __scratch_x_start__ = .; + *(.scratch_x.*) + . = ALIGN(4); + __scratch_x_end__ = .; + } > SCRATCH_X AT > APP + __scratch_x_source__ = LOADADDR(.scratch_x); + + .scratch_y : { + __scratch_y_start__ = .; + *(.scratch_y.*) + . = ALIGN(4); + __scratch_y_end__ = .; + } > SCRATCH_Y AT > APP + __scratch_y_source__ = LOADADDR(.scratch_y); + + .bss : { + . = ALIGN(4); + __bss_start__ = .; + *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.bss*))) + *(COMMON) + . = ALIGN(4); + __bss_end__ = .; + } > RAM + + .heap (COPY): + { + __end__ = .; + end = __end__; + *(.heap*) + __HeapLimit = .; + } > RAM + + /* .stack*_dummy section doesn't contains any symbols. It is only + * used for linker to calculate size of stack sections, and assign + * values to stack symbols later + * + * stack1 section may be empty/missing if platform_launch_core1 is not used */ + + /* by default we put core 0 stack at the end of scratch Y, so that if core 1 + * stack is not used then all of SCRATCH_X is free. + */ + .stack1_dummy (COPY): + { + *(.stack1*) + } > SCRATCH_X + .stack_dummy (COPY): + { + *(.stack*) + } > SCRATCH_Y + + .flash_end : { + __flash_binary_end = .; + } > APP + + /* stack limit is poorly named, but historically is maximum heap ptr */ + __StackLimit = __bss_end__ + __micropy_c_heap_size__; + + /* Define start and end of GC heap */ + __GcHeapStart = __StackLimit; /* after the C heap (sbrk limit) */ + __GcHeapEnd = ORIGIN(RAM) + LENGTH(RAM); + + /* Define memory for the C stack */ + __StackOneTop = ORIGIN(SCRATCH_X) + LENGTH(SCRATCH_X); + __StackTop = ORIGIN(SCRATCH_Y) + LENGTH(SCRATCH_Y); + __StackOneBottom = __StackOneTop - SIZEOF(.stack1_dummy); + __StackBottom = __StackTop - SIZEOF(.stack_dummy); + PROVIDE(__stack = __StackTop); + + /* Check GC heap is at least 128 KB */ + /* On a RP2040 using all SRAM this should always be the case. */ + ASSERT((__GcHeapEnd - __GcHeapStart) > 128*1024, "GcHeap is too small") + + ASSERT( __binary_info_header_end - __logical_binary_start <= 256, "Binary info must be in first 256 bytes of the binary") + /* todo assert on extra code */ +} diff --git a/micropython/board/RPI_PICO_W/memmap_mp.ld b/micropython/board/RPI_PICO_W/memmap_mp.ld new file mode 100644 index 00000000..f6fe3256 --- /dev/null +++ b/micropython/board/RPI_PICO_W/memmap_mp.ld @@ -0,0 +1,274 @@ +/* Based on GCC ARM embedded samples. + Defines the following symbols for use by code: + __exidx_start + __exidx_end + __etext + __data_start__ + __preinit_array_start + __preinit_array_end + __init_array_start + __init_array_end + __fini_array_start + __fini_array_end + __data_end__ + __bss_start__ + __bss_end__ + __end__ + end + __HeapLimit + __StackLimit + __StackTop + __stack (== StackTop) +*/ + +/* Default RP2 flash size. Assumes no user filesystem. */ +_flash_start = 0x10000000; +_flash_size = 2048k; +_flash_app_size = 1200k; +_flash_fs_size = _flash_size - _flash_app_size; +_flash_fs_start = _flash_start + _flash_app_size; + +MEMORY +{ + FLASH(rx) : ORIGIN = _flash_start, LENGTH = _flash_size + APP(rx) : ORIGIN = _flash_start, LENGTH = _flash_app_size + FILESYSTEM(r) : ORIGIN = _flash_fs_start, LENGTH = _flash_fs_size + RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 256k + SCRATCH_X(rwx) : ORIGIN = 0x20040000, LENGTH = 4k + SCRATCH_Y(rwx) : ORIGIN = 0x20041000, LENGTH = 4k +} + +ENTRY(_entry_point) + +SECTIONS +{ + /* Second stage bootloader is prepended to the image. It must be 256 bytes big + and checksummed. It is usually built by the boot_stage2 target + in the Raspberry Pi Pico SDK + */ + + .flash_begin : { + __flash_binary_start = .; + } > APP + + .boot2 : { + __boot2_start__ = .; + KEEP (*(.boot2)) + __boot2_end__ = .; + } > APP + + ASSERT(__boot2_end__ - __boot2_start__ == 256, + "ERROR: Pico second stage bootloader must be 256 bytes in size") + + /* The second stage will always enter the image at the start of .text. + The debugger will use the ELF entry point, which is the _entry_point + symbol if present, otherwise defaults to start of .text. + This can be used to transfer control back to the bootrom on debugger + launches only, to perform proper flash setup. + */ + + .text : { + __logical_binary_start = .; + KEEP (*(.vectors)) + KEEP (*(.binary_info_header)) + __binary_info_header_end = .; + KEEP (*(.reset)) + /* TODO revisit this now memset/memcpy/float in ROM */ + /* bit of a hack right now to exclude all floating point and time critical (e.g. memset, memcpy) code from + * FLASH ... we will include any thing excluded here in .data below by default */ + *(.init) + /* Change for MicroPython... exclude gc.c, parse.c, vm.c from flash */ + *(EXCLUDE_FILE(*libgcc.a: *libc.a: *lib_a-mem*.o *libm.a: *gc.c.obj *vm.c.obj *parse.c.obj) .text*) + *(.fini) + /* Pull all c'tors into .text */ + *crtbegin.o(.ctors) + *crtbegin?.o(.ctors) + *(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors) + *(SORT(.ctors.*)) + *(.ctors) + /* Followed by destructors */ + *crtbegin.o(.dtors) + *crtbegin?.o(.dtors) + *(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors) + *(SORT(.dtors.*)) + *(.dtors) + + *(.eh_frame*) + . = ALIGN(4); + } > APP + + .rodata : { + *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .rodata*) + . = ALIGN(4); + *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.flashdata*))) + . = ALIGN(4); + } > APP + + .ARM.extab : + { + *(.ARM.extab* .gnu.linkonce.armextab.*) + } > APP + + __exidx_start = .; + .ARM.exidx : + { + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + } > APP + __exidx_end = .; + + /* Machine inspectable binary information */ + . = ALIGN(4); + __binary_info_start = .; + .binary_info : + { + KEEP(*(.binary_info.keep.*)) + *(.binary_info.*) + } > APP + __binary_info_end = .; + . = ALIGN(4); + + /* End of .text-like segments */ + __etext = .; + + .ram_vector_table (COPY): { + *(.ram_vector_table) + } > RAM + + .data : { + __data_start__ = .; + *(vtable) + + *(.time_critical*) + + /* remaining .text and .rodata; i.e. stuff we exclude above because we want it in RAM */ + *(.text*) + . = ALIGN(4); + *(.rodata*) + . = ALIGN(4); + + *(.data*) + + . = ALIGN(4); + *(.after_data.*) + . = ALIGN(4); + /* preinit data */ + PROVIDE_HIDDEN (__mutex_array_start = .); + KEEP(*(SORT(.mutex_array.*))) + KEEP(*(.mutex_array)) + PROVIDE_HIDDEN (__mutex_array_end = .); + + . = ALIGN(4); + /* preinit data */ + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP(*(SORT(.preinit_array.*))) + KEEP(*(.preinit_array)) + PROVIDE_HIDDEN (__preinit_array_end = .); + + . = ALIGN(4); + /* init data */ + PROVIDE_HIDDEN (__init_array_start = .); + KEEP(*(SORT(.init_array.*))) + KEEP(*(.init_array)) + PROVIDE_HIDDEN (__init_array_end = .); + + . = ALIGN(4); + /* finit data */ + PROVIDE_HIDDEN (__fini_array_start = .); + *(SORT(.fini_array.*)) + *(.fini_array) + PROVIDE_HIDDEN (__fini_array_end = .); + + *(.jcr) + . = ALIGN(4); + /* All data end */ + __data_end__ = .; + } > RAM AT> APP + + .uninitialized_data (COPY): { + . = ALIGN(4); + *(.uninitialized_data*) + } > RAM + + /* bss without zero init on startup */ + .uninitialized_bss (NOLOAD): { + . = ALIGN(4); + *(.uninitialized_bss*) + } > RAM + + /* Start and end symbols must be word-aligned */ + .scratch_x : { + __scratch_x_start__ = .; + *(.scratch_x.*) + . = ALIGN(4); + __scratch_x_end__ = .; + } > SCRATCH_X AT > APP + __scratch_x_source__ = LOADADDR(.scratch_x); + + .scratch_y : { + __scratch_y_start__ = .; + *(.scratch_y.*) + . = ALIGN(4); + __scratch_y_end__ = .; + } > SCRATCH_Y AT > APP + __scratch_y_source__ = LOADADDR(.scratch_y); + + .bss : { + . = ALIGN(4); + __bss_start__ = .; + *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.bss*))) + *(COMMON) + . = ALIGN(4); + __bss_end__ = .; + } > RAM + + .heap (COPY): + { + __end__ = .; + end = __end__; + *(.heap*) + __HeapLimit = .; + } > RAM + + /* .stack*_dummy section doesn't contains any symbols. It is only + * used for linker to calculate size of stack sections, and assign + * values to stack symbols later + * + * stack1 section may be empty/missing if platform_launch_core1 is not used */ + + /* by default we put core 0 stack at the end of scratch Y, so that if core 1 + * stack is not used then all of SCRATCH_X is free. + */ + .stack1_dummy (COPY): + { + *(.stack1*) + } > SCRATCH_X + .stack_dummy (COPY): + { + *(.stack*) + } > SCRATCH_Y + + .flash_end : { + __flash_binary_end = .; + } > APP + + /* stack limit is poorly named, but historically is maximum heap ptr */ + __StackLimit = __bss_end__ + __micropy_c_heap_size__; + + /* Define start and end of GC heap */ + __GcHeapStart = __StackLimit; /* after the C heap (sbrk limit) */ + __GcHeapEnd = ORIGIN(RAM) + LENGTH(RAM); + + /* Define memory for the C stack */ + __StackOneTop = ORIGIN(SCRATCH_X) + LENGTH(SCRATCH_X); + __StackTop = ORIGIN(SCRATCH_Y) + LENGTH(SCRATCH_Y); + __StackOneBottom = __StackOneTop - SIZEOF(.stack1_dummy); + __StackBottom = __StackTop - SIZEOF(.stack_dummy); + PROVIDE(__stack = __StackTop); + + /* Check GC heap is at least 128 KB */ + /* On a RP2040 using all SRAM this should always be the case. */ + ASSERT((__GcHeapEnd - __GcHeapStart) > 128*1024, "GcHeap is too small") + + ASSERT( __binary_info_header_end - __logical_binary_start <= 256, "Binary info must be in first 256 bytes of the binary") + /* todo assert on extra code */ +} diff --git a/micropython/micropython_board_linker.patch b/micropython/micropython_board_linker.patch new file mode 100644 index 00000000..bd7cb51d --- /dev/null +++ b/micropython/micropython_board_linker.patch @@ -0,0 +1,17 @@ +diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt +index 2697efe28..f5d4bc0b2 100644 +--- a/ports/rp2/CMakeLists.txt ++++ b/ports/rp2/CMakeLists.txt +@@ -507,7 +507,11 @@ endif() + # a linker script modification) until we explicitly add macro calls around the function + # defs to move them into RAM. + if (PICO_ON_DEVICE AND NOT PICO_NO_FLASH AND NOT PICO_COPY_TO_RAM) +- pico_set_linker_script(${MICROPY_TARGET} ${CMAKE_CURRENT_LIST_DIR}/memmap_mp.ld) ++ if(EXISTS ${MICROPY_BOARD_DIR}/memmap_mp.ld) ++ pico_set_linker_script(${MICROPY_TARGET} ${MICROPY_BOARD_DIR}/memmap_mp.ld) ++ else() ++ pico_set_linker_script(${MICROPY_TARGET} ${CMAKE_CURRENT_LIST_DIR}/memmap_mp.ld) ++ endif() + endif() + + pico_add_extra_outputs(${MICROPY_TARGET})