When enabling SMP the initial call to RNG::get().init() in apic_revenant crashes. RNG::get().init() returns a static variable that is initialised by the first call to get() (here). After stepping through the code I see that the crash is caused by a surrounding call to __cxa_guard_acquire that protects the initialisation of the static variable, which then calls a syscall that returns ENOSYS. This value is written to __errno_location by musl, which then triggers a CPU exception.
There seems to be multiple issues here:
__cxa_guard_acquire is added by the compiler to protect the initialisation of static variables at runtime (such as in RNG). The implementation is here. Depending on whether we have threads enabled in LIBCXX it will call various locking mechanisms in libc/musl, which have to be initialised first. If this happens early in the boot process, the kernel/musl may be unable to handle them. I'm not sure what the best approach to fix this would be, but ideally we shouldn't use code that relied on this feature until we get far enough in the boot process.
__errno_location isn't set up properly, at least not on other cores than core 0, and syscalls will write to the memory location it points to. In combination with the __cxa_guard_acquire issue above, this may happen unexpectedly. After a call to syscall, musl's syscall_ret writes errno to the location stored in __errno_location function from the pthread struct. The struct is retrieved from %fs:0 here. It looks like TLS may not be properly set up, there's a function in IncludeOS to do it here -- but it doesn't look like it's called from apic_revenant.
I'm not sure why this has worked previously, but perhaps the new toolchain/build system happens to make errno_val point outside valid memory, so we now get a crash instead of potential instability? Could this have been causing issues such as #2252?
To reproduce, first enable SMP, then run nix-shell --argstr unikernel test/kernel/integration/smp --run ./test.py
(this is on v0.16.0-release branch)
When enabling SMP the initial call to
RNG::get().init()inapic_revenantcrashes.RNG::get().init()returns a static variable that is initialised by the first call toget()(here). After stepping through the code I see that the crash is caused by a surrounding call to__cxa_guard_acquirethat protects the initialisation of the static variable, which then calls a syscall that returnsENOSYS. This value is written to__errno_locationby musl, which then triggers a CPU exception.There seems to be multiple issues here:
__cxa_guard_acquireis added by the compiler to protect the initialisation of static variables at runtime (such as in RNG). The implementation is here. Depending on whether we have threads enabled in LIBCXX it will call various locking mechanisms in libc/musl, which have to be initialised first. If this happens early in the boot process, the kernel/musl may be unable to handle them. I'm not sure what the best approach to fix this would be, but ideally we shouldn't use code that relied on this feature until we get far enough in the boot process.__errno_locationisn't set up properly, at least not on other cores than core 0, and syscalls will write to the memory location it points to. In combination with the__cxa_guard_acquireissue above, this may happen unexpectedly. After a call to syscall, musl's syscall_ret writeserrnoto the location stored in__errno_locationfunction from the pthread struct. The struct is retrieved from%fs:0here. It looks like TLS may not be properly set up, there's a function in IncludeOS to do it here -- but it doesn't look like it's called fromapic_revenant.I'm not sure why this has worked previously, but perhaps the new toolchain/build system happens to make
errno_valpoint outside valid memory, so we now get a crash instead of potential instability? Could this have been causing issues such as #2252?To reproduce, first enable SMP, then run
nix-shell --argstr unikernel test/kernel/integration/smp --run ./test.py(this is on v0.16.0-release branch)