Protected Object ceiling priority

Hi,

I’ve written a small program which is targeting the embedded-stm32f429disco runtime and have come across an issue related to tasks accessing a protected object.

The details of the program aren’t too important for this I reckon (characters that get generated randomly from a task get printed into a minicom console via a series of daisy-chained USARTs) but consider the following:

task type Character_Stream with Priority => System.Max_Priority;
   
task type Character_Stream_Consumer with Priority => System.Max_Priority - 1;
   
task type USART_Consumer with Priority => System.Max_Priority - 2;

and then:

protected Buffer with Priority => System.Max_Priority is
            
     procedure Put (C : Character ; OK : out Boolean);
      
     procedure Get (C : out Character ; OK : out Boolean);
.........................................................................
.........................................................................

with Character_Stream and Character_Stream_Consumer both calling the Put and Get procedures respectively (the buffer is declared as a straight object than a type).

Now, the moment Character_Stream calls Put the program traps immediately and through some weird machinery of __gnat_last_chance_handler and local exception handling (allowed in Ravenscar) which can be the topic of a separate post, I managed to narrow this down to procedure Lock (Object : Protection_Access) inside System.Tasking.Protected_Objects in ./arm-eabi/lib/gnat/embedded-stm32f429disco/gnarl/s-taprob.adb:

where the explicit raise takes place.

But that conditional is for > and not for >= which as the comment above explains is perfectly fine, so the call from Character_Stream should be accepted, shouldn’t it?

Does the protected object need to be configured with a specific ceiling protocol or something like that?

Any thoughts? - Thanks!

You are right, Character_Stream calls should be accepted. Try to see in the debugger value of the properties. Probably you have corrupted memory or exhausted stack.

The object ceiling can be dynamically changed through the attribute 'Priority (ARM D.5.2). Could it be the case?

P.S. You do not need protected objects for a non-blocking LIFO. In your case it is non-blocking because you made Put and Get procedures rather than entries.

Yeah, this is what I’m trying to do but struggling a bit to include the runtime symbols in the elf file so that they are available for debugging (obviously compiling with -g).

Any tips? :slightly_smiling_face:

Oh, that’s a good point, didn’t think of that at all! But this should actually have the desired effect already i.e. the priority of the entering task is raised to that of the protected object (if lower) so that it can’t be preempted by a mid-priority task and therefore cause priority inversion to a higher priority task (assuming the priorities of all participants have been worked out correctly, that is). But it’s not raised higher than that of the protected object which seems to be the issue here (and the check is done before setting the priority).

The runtime code actually shows that:

Yeah, the intention for this one is to be a queue so that characters are added at the tail and removed at the head (i.e. characters are printed on minicom in the same order as they are generated in the mcu).
Adding more than one entry in Ravenscar isn’t allowed due to the Max_Protected_Entries => 1 restriction so implementing a shared queue requires the use of two Suspension_Object’s whose state is constantly flipped by the consumer and producer tasks.

Rebuilding runtime should be simple:

Same for FIFO. Use atomic in and out indices with a ring buffer. That suffice because producer and consumer modify only one of the indices and buffer full/empty tests are read-only. No protected object is needed so long you deploy busy waiting as you do.

You need only Get to be an entry (wait for a character). Put can remain procedure raising Program_Error when buffer is full.

So, I copied the entire directory of this runtime into a new directory with a -debug suffix and then ran the following inside the new directory (i.e. embedded-stm32f429disco-debug):

gprbuild -j0 -P ravenscar_build.gpr -XBUILD=Debug

which completed successfully.

I then pointed my alire gpr file to it:

for Runtime ("Ada") use "embedded-stm32f429disco-debug";

and ran an alr build which also completed successfully.

But trying to load the elf file in gdb results in the following error:

Launching: st-util -p 4242 --connect-under-reset --semihosting
Launching debugger.
[2026-08-24 16:31:33] Language unknown, defaulting to C: asm

With the full load command being something like:

(gdb) load
Loading section .text, size 0x20018 lma 0x8000000
+download,{section=.text,section-size=131096,total-size=1142213}
+download,{section=.text,section-sent=16128,section-size=131096,total-sent=16128,total-size=1142213}
+download,{section=.text,section-sent=131096,section-size=131096,total-sent=131096,total-size=1142213}
~Loading section .ARM.extab, size 0x1fbc lma 0x8020018
+download,{section=.ARM.extab,section-size=8124,total-size=1142213}
~Loading section .ARM.exidx, size 0x1a40 lma 0x8021fd4
+download,{section=.ARM.exidx,section-size=6720,total-size=1142213}
~Loading section .rodata, size 0x414c lma 0x8023a18
+download,{section=.rodata,section-size=16716,total-size=1142213}
Loading section .data, size 0xe6c lma 0x8027b64
+download,{section=.data,section-size=3692,total-size=1142213}
Start address 0x0801ccc4, load size 166348
Transfer rate: 18 KB/sec, 9241 bytes/write.

and when I try to continue it aborts:

(gdb) c
Continuing.
[program running]
Command aborted.

Any ideas how I can troubleshoot this further? :thinking:

Is it something to do with the file size? The debug one is naturally larger (~1.3M) whereas the non-debug one (which continues to load and run fine) is in the 950K region.

Thanks!

After that I usually do (in arm-eabi-gdb console): (At least it works with OpenOCD, but probably should work with st-util also)

br __gnat_last_chance_handler
mon reset init
cont

First command sets a breakpoint on exception handler. The second one resets the board. The last one launches the execution. If an exception occurs, GDB should stops in __gnat_last_chance_handler and you will be able to see backtrace (with bt), switch the frame (with frame 4 or something) and examine the state with print Var or info locals.

Thanks for the suggestions :folded_hands:

So, in my case the __gnat_last_chance_handler is never hit..rather it goes straight to the default fault/trap handler declared in handler.S:

Following some suggestions from gpt and digging through the Cortex-M4 programming manual I can see that the PRECISERR & BFARVALID flags in the CFSR register (section 4.4.10 in the PM) are set which I believe suggest some kind of bus fault error.

I grabbed the addresses of the psp & msp from within the default fault handler and using arm-eabi-addr2line I got the following for psp :

arm-eabi-addr2line -e usart_flow -f -C 0x200012b0
__bss_start
??:?

and the following for the msp:

arm-eabi-addr2line -e usart_flow -f -C 0x200077d8
__stack_start
??:?

(I believe the lr had a psp return address but not entirely sure)

So, maybe some unaligned access in start-rom.S? :thinking:

Though, I can see in the linker script both those symbols are properly aligned:
. = ALIGN(0x8);

One final thing, loading the elf file “manually” in an arm-eabi-gdb console doesn’t result in the previous error (where I was loading it via the GNAT Studio console).

I shall continue to investigate, thanks for your time.

In case of bus fault, check address in BFAR (0xE000ED38) register (not in *SP registers).

After mon reset init if you execute several si (step by one asm instruction), does it start executing instructions from start-rom.S?

The address of psp is in the range of the symbol sec_default_sized_stacks, which is unusual since this is the area for secondary stacks, not primary stacks. So it looks to me like you may be encountering a stack overflow in one of your tasks, where the task is overflowing and corrupting adjacent memory locations. This would also explain why you were seeing a ceiling locking violation in your original message, since a stack overflow may be corrupting the Caller_Priority. I’ve seen similar things before caused by stack overflows in the past.

Since you’re using the “embedded” runtime profile which supports exception propagation, you could try turning on stack checking with -fstack-check and a Storage_Error exception should then be raised in the task that is overflowing, which may help with identifying the offending task.

You can set the stack size for tasks using the Storage_Size aspect or pragma, for example:

task type Character_Stream with 
  Priority     => System.Max_Priority,
  Storage_Size => 8 * 1024; --  8 kB stack

It was a stack overflow indeed! :slightly_smiling_face:

Doubling the primary stack as suggested fixed the initial issue but also that in the debug runtime too so that I was able to step through Lock inside System.Tasking.Protected_Objects and check the Caller_Priority value. :+1:

Interesting how that was corrupting the priority value for the task. It looks as if this was overshooting even the secondary stack and landing into the area right next to that where (presumably) the task attributes are stored..? :thinking:

One thing to note is that initially I also gave the -fstack-check a try to prove this suggestion and effectively did the comb inside Character_Stream in something like:

declare
    ....................
 begin
    ....................
 exception
    when Storage_Error => 
       declare
          I  : Integer;
       begin
          I := I + 1;
       end;
 end;

with I merely declared so that I could have something to place a breakpoint to but that never got hit and the flow was again going straight to the fault handler in handler.S

Assume this is where the exception handling logic needs to go as only local handling is allowed in this profile?

Set it this way:

package Compiler is
  for Default_Switches ("Ada") use Stm32F429_Discovery_Full.Compiler'Default_Switches ("Ada") & ("-fstack-check");
end Compiler;

Many thanks all for your time and all the suggestions, that was really useful. :folded_hands: