FreeRTOS Cortex-M4F Scheduler Startup: Do Not Retain Pointers into the main Stack
A task receives a pointer to a configuration object created inside main. The engineer expects that object to remain usable because successful scheduler startup never returns to the caller. For the FreeRTOS port examined here, that reasoning misses a hardware stack-pointer reset: the pre-scheduler stack region is made available for reuse without an ordinary C return.
The scope is FreeRTOS-Kernel V11.1.0, GCC ARM_CM4F port.c. No RTOS image was built, scheduler started or corruption test executed. The example assumes the ordinary pre-scheduler path uses MSP for main. Custom startup, other ports, compilers and vendor modifications require their own source review.
Separate a copied pointer from copied configuration
Imagine an automatic BootConfig cfg in main, initialized before task creation. The creation call passes &cfg as pvParameters. The task later casts its received parameter and reads the configuration. This is a hypothetical anti-pattern, not a tested application.
The V11.1.0 task API header defines pvParameters as the pointer supplied to the task and illustrates a static parameter object whose storage survives its use. Allocation of a task’s control block and stack does not independently allocate a copy of the application object referenced by that pointer.
The selected port makes this distinction concrete. pxPortInitialiseStack places the pvParameters value in the synthetic context slot for R0. The value preserved for task startup is an address. The bytes at that address remain wherever the application put them.
If the object contains further pointers, moving only its outer structure also leaves those referenced objects to be reviewed. Ownership follows every retained reference, not just the variable directly passed to the creation API.
Trace the MSP reset rather than the C call graph
In prvPortStartFirstTask, the assembly reads the vector-table offset register at 0xE000ED08, follows that value to the active vector table, then loads its initial stack value. It writes that value into MSP before issuing the supervisor call that starts the first task.
This is not necessarily a hard-coded flash-vector address. A bootloader or startup sequence may have selected another vector table. The relevant initial stack value comes from the table currently selected by VTOR; review that selection in the actual linked system.
The subsequent vPortSVCHandler restores the selected task’s context and writes its task stack pointer to PSP. The task’s execution stack and the system stack are therefore different resources at this handoff. Restoring the task stack does not move a configuration object previously allocated on the main stack.

The consequence is storage reuse, not a promise that every local object is immediately erased. Exception activity and subsequent execution determine which locations are overwritten and when. An intact value in one debugger observation cannot establish that the old region remains reserved for the object.
This failure needs neither a task-stack overflow nor floating-point activity. Increasing the worker’s stack allocation does not repair a pointer into a different region that startup has made reusable.
Choose an owner that survives the handoff
For configuration that remains valid throughout the task’s use, an explicitly owned static-duration object is one straightforward design. Initialize it before exposing the pointer, and preserve any referenced buffers for the same required interval. The correction is the storage and ownership contract, not merely a cast that silences a compiler warning.
An owned allocation can also work when its allocation failure and eventual release are handled deliberately. Do not release it while the task still retains the address. Alternatively, construct the working configuration inside an already running task using inputs that themselves survived scheduler startup.
These choices do not automatically make mutable data thread-safe. If several tasks can update the configuration, define synchronization and which task owns each mutation. Static duration answers how long storage exists; it does not answer who may change its contents concurrently.
Do not patch out the port’s MSP reset as the default application fix. That changes a low-level startup contract and introduces a separate port-maintenance problem. Remove the unsupported dependence on pre-scheduler automatic storage first.
Verify the implementation and the address relationship
Begin with a read-only build review. Identify the exact port.c compiled into the image, the toolchain configuration and any vendor patches. Inspect the linked implementation rather than relying on an IDE folder name or a generic “Cortex-M” label.
Record the configuration object’s intended storage region, the startup stack range, active vector table and initial MSP value. Then trace each task parameter back to its owner. The critical finding is an address into reclaimed pre-scheduler storage, not the absence of an ordinary function return.
A later disposable firmware fixture could observe the addresses across startup and confirm the corrected ownership design without deliberately overflowing any stack. Until that work is performed, report source analysis separately from execution evidence. Successful task creation and a running scheduler are compatible with this borrowed-storage defect; neither proves the parameter object remains protected.