Stack overflowing on simple task

Hello,

I’ve come across a stack overflow issue with a debug version of the embedded-stm32f429disco runtime and I am trying to understand if I’ve got a corrupt memory on my board or there is something else going on.

Consider the following simple task spec and body (which doesn’t actually do anything useful):

with System;
package usart_tasks2 is

   task type Test_Type with Priority => System.Max_Priority;
   
   TT : Test_Type;

end usart_tasks2;
package body usart_tasks2 is

   task body Test_Type is
      M_A : Integer := 0;
   begin
      loop
         M_A := (M_A + 1) rem 100;
      end loop;
   end Test_Type;

end usart_tasks2;

Now running this on an actual board traps immediately and after a bit of tracing in gdb I’ve come to the conclusion that the elaboration of the above task is causing the overflow.

Below are some screenshots of the tracing (please, keep in mind I am compiling with -fstack-check):
Starting from start-rom.s I put a breakpoint in adainit right before the elaboration is run:


and then I put another one in Stack-Check whose call I believe is put in place by the compiler due to -fstack-check:

so it’s clear that the elaboration code is somehow overshooting the stack.
The actual address values can be seen below:

where clearly stack_address (in the printed Arglist) is way below Bottom_Of_Stack (cortex m4s are (full) descending).
I’m not entirely sure how stack boundaries per-task are calculated but I assume each task is given a starting stack address and a (configurable) size and that Bottom_Of_Stack refers to the overall stack boundary calculated in the linker script? I.e. __stack_start here:

Any ideas what might be causing this? The only things declared are a main and the above task package where there are no complex declarations in the elaboration section. Could that be some memory corruption issue? :thinking:

Thanks!

..come to think of it now it’s the elaboration of the spec that’s failing so I’m wondering if declaring a task object in the spec is somehow causing this :thinking:

Switch -fstack-check works a bit weird on embedded targets as for me: it checks 4k free space in stack. My advice - don’t use it, or replace Stack_Check routine by custom implementation.

See How to protect from stack overflow on embedded targets?

I think these are referring to two different stacks. stack_address in the Arglist is referring to the stack for TT, but Bottom_Of_Stack is referring to the caller’s stack (the environment task I believe), so the stacks for TT and the environment task would be expected to not overlap.

Note that __stack_start and __stack_end in the linker script marks the area used for the environment task’s stack. Stacks for tasks are allocated in .bss as far as I understand.

Yes, the additional 4k is because the stack check needs to make sure that there is sufficient stack space available to be able to safely raise and propagate a Storage_Error if the stack check fails. If the stack check only checked that only the current stack frame is within the limit, then it may not have enough space on top of that to be able to raise and propagate the Storage_Error, which would result in a stack overflow anyway.

So to me it sounds like the environment task’s default stack size (4 kB) is too small for -fstack-check which requires additional overhead mentioned above, so you probably need to increase it when you have stack checking enabled. You can increase it by defining a value for __stack_size in your linker switches. For example, by adding this to your project’s GPR file to set it to 8 kB:

package Linker is
   for Switches ("Ada") use ("-Wl,--defsym=__stack_size=8192");
end Linker;

Right, I see..so it sounds like the flag requires that there’s at least 4K of free space after Bottom_Of_Stack to do the propagation (so probably the real stack end for the caller stack is Bottom_Of_Stack - 4K :thinking:)

Good to know, I was under the impression these marked the entire available space for everything and then the runtime chopped it up based on the consumer entities. Can also see in the linker script there are __interrupt_stack_start & __interrupt_stack_end too which come right after __bss_end = .; so it looks like interrupt handlers get their own range too.

Yeah, that’s what I observed in gdb too as the raised exception hits the __gnat_last_chance_handler which tries to do some printing in semihosting and that generates a range of frames with each one declaring various objects etc so guaranteeing the extra space definitely makes sense. Would be curious to know how the runtime actually propagates that exception and calls the last chance if only local handling is allowed in this profile?

That’s really cool to know, however in my setup it doesn’t seem to take effect as I am getting the same issue. Removing the flag alltogether as suggested by Max seems to fix that.

Many thanks for all the input folks!

A Storage_Error from a stack check failure can’t be handled locally unfortunately in this case. So when exception propagation is disabled (i.e. when the No_Exception_Propagation restriction is in effect, as it is for “light” and “light-tasking” runtime profiles), then the last chance handler is called immediately when the Storage_Error exception is raised.