-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
CONFIG_SKIP_BOOT_MSG
makes newlib stdio races more likely
#20067
Comments
Thanks for reporting this. In #8619 it is mentioned that the nano variant of newlib behaves differently, would you mind to check which version is used? Can you reproduce the issue in both cases? ( (Note that RIOT will by default use the nano version, but only if that version is available.) In a different case were racy code did not result in garbled stdio (there seems to be a consensus that this is acceptable in most use cases), but in crashes, we opted to make this thread-safe no matter what. (This was for memory allocation.) So the consistent thing here would be to make sure that Also, it would be nice to also provide at least the option to have newlib thread-safe even for functions that do not crash when racing. Some people do like e.g. clean stdio output enough to spend resources on that :) |
Took me a while to get back to this, but I've managed to reproduce with a docker based build of the target. Based on the output of Compile command
It ended up being quite difficult to get a consistent crash when attached to a debugger. I think this is partly because some of the data allocated as part of stdio initialization is within 'uninitialized' RAM, which happens to be initialized with data from any previous execution. The method I ended with, was to try and force the firmware to call a null pointer in The null call then occurs as part of Getting this exact crash, required sub-microsecond adjustment which I did via a busy loop, however I think other uses of Although |
This eagerly calls `__sinit()` instead of lazy initialization upon the first call to stdio (e.g. `puts()`, `printf()`). The issue is that without locking (as is currently the case for all RIOT platforms but ESP) two concurrent "first calls" may result in concurrent initialization of the same structure and data corruption. Fixes RIOT-OS#20067
This eagerly calls `__sinit()` instead of lazy initialization upon the first call to stdio (e.g. `puts()`, `printf()`). The issue is that without locking (as is currently the case for all RIOT platforms but ESP) two concurrent "first calls" may result in concurrent initialization of the same structure and data corruption. Fixes RIOT-OS#20067
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, beforemain
, RIOT prints a message usingputs
(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 initializedreent
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:
That spawns multiple threads that print -- e.g.:
main.c
Then if
worker1
is preempted in the middle of stdio initialization, e.g. at:Then
worker2
may crash whenputs
is called:The text was updated successfully, but these errors were encountered: