-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Description
After spending some time analyzing this issue, I believe that the actual cause is already known -- i.e., #4488 / #8619-comment-569952641 (and is actually documented in the release notes).
Feel free to close as a duplicate, but I thought I would leave the analysis below regarding the impact of CONFIG_SKIP_BOOT_MSG
.
Description
The reentrant stdio functions in newlib
initialize some shared data (via the function __sinit
) the first time an IO function, e.g., puts
, is used. Normally, before main
, RIOT prints a message using puts
(i.e., "This is RIOT" + version), which ends up calling __sinit
before user code is executed, avoiding most of the issues with initialization.
However if CONFIG_SKIP_BOOT_MSG
is set, the __sinit
will not be called until the first print statement. If two threads attempt to print at a roughly similar time, then it is possible1 for the second thread to execute with a partially initialized reent
object which causes various crashes depending on how much of the structure has been initialized.
1. Platforms that do not perform any locking as part of _lock_acquire
(see: #8619-comment-569952641).
As an example, if we have code configured like this:
USEMODULE += ztimer_usec
USEMODULE += sched_round_robin
CFLAGS += -DCONFIG_SKIP_BOOT_MSG
BOARD=nucleo-f446re
That spawns multiple threads that print -- e.g.:
main.c
#include <stdio.h>
#include <stdint.h>
#include "thread.h"
#include "ztimer.h"
#include "timex.h"
uint32_t WORKER1_SPIN_TIME_US = (10 * US_PER_MS) - 30; // Approximate RR scheduling time - 30 usecs.
volatile uint32_t WORKER1_LOOP_SPIN = 5; // Loop iterations for sub-microsecond adjustment.
void* worker1(void* arg)
{
(void)arg;
ztimer_spin(ZTIMER_USEC, WORKER1_SPIN_TIME_US);
while (WORKER1_LOOP_SPIN != 0) { WORKER1_LOOP_SPIN -= 1; }
puts("WORKER 1\n");
while(1) {};
}
void* worker2(void* arg)
{
(void)arg;
puts("WORKER 2\n");
while(1) {};
}
#define WORKER_STACKSIZE (THREAD_STACKSIZE_TINY+THREAD_EXTRA_STACKSIZE_PRINTF)
int main(void)
{
static char w1_stack[WORKER_STACKSIZE];
thread_create(w1_stack, sizeof(w1_stack), 7, THREAD_CREATE_STACKTEST, worker1, NULL, "T1");
static char w2_stack[WORKER_STACKSIZE];
thread_create(w2_stack, sizeof(w2_stack), 7, THREAD_CREATE_STACKTEST, worker2, NULL, "T2");
return 0;
}
Then if worker1
is preempted in the middle of stdio initialization, e.g. at:
worker1 at main.c:27
_puts_r
__sinit
__sfp
__sfmoreglue
...
<TIMER 1 IRQ>
<PREEMPTION>
Then worker2
may crash when puts
is called:
worker2 at main.c:39
_puts_r
__swsetup_r+52