blob: a3b24b9ab375f1c571a8900fb605caf2bf583277 [file] [log] [blame]
// Copyright 2025 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <assert.h>
#include <lib/zx/result.h>
#include <pthread.h>
#include <zircon/sanitizer.h>
#include "../weak.h"
#include "thread-list.h"
#include "thread-storage.h"
#include "thread.h"
#include "threads_impl.h"
extern "C" decltype(__sanitizer_before_thread_create_hook) __sanitizer_before_thread_create_hook
[[gnu::weak]];
namespace LIBC_NAMESPACE_DECL {
namespace {
using SanitizerBeforeThreadCreateHook = Weak<__sanitizer_before_thread_create_hook>;
using ThreadState = zxr_thread_t::State;
// TODO(https://fxbug.dev/342469121): This is only needed in this form while
// using the legacy musl dynamic linker. The new libdl's implementation of
// dlopen needs some analogous locking, but it can be done at finer grain.
constinit pthread_rwlock_t gThreadCreationLock = PTHREAD_RWLOCK_INITIALIZER;
// Many threads could be reading the TLS state. They don't exclude each other
// from doing separate ThreadStorage::Allocate() calls.
__TA_EXCLUDES(kStaticTlsLock) void LockForThreadCreate() {
pthread_rwlock_rdlock(&gThreadCreationLock);
}
__TA_EXCLUDES(kStaticTlsLock) void UnlockForThreadCreate() {
pthread_rwlock_unlock(&gThreadCreationLock);
}
constexpr WeakLock<LockForThreadCreate, UnlockForThreadCreate> kLockForThreadCreate;
} // namespace
using ::__thread_allocation_inhibit, ::__thread_allocation_release;
// dlopen calls this under another lock. Only one dlopen call can be modifying
// state at a time. It excludes all ThreadStorage::Allocate() calls until the
// corresponding Thread goes on the gAllThreads list (thread-list.h).
extern "C" void __thread_allocation_inhibit() {
pthread_rwlock_wrlock(&LIBC_NAMESPACE::gThreadCreationLock);
}
extern "C" void __thread_allocation_release() {
pthread_rwlock_unlock(&LIBC_NAMESPACE::gThreadCreationLock);
}
zx::result<CreatedThread> ThreadCreate(ThreadAttributes attrs) {
assert(!attrs.name.empty());
std::string_view thread_name = attrs.name.str();
std::string_view vmo_name = thread_name;
// First allocate the storage for the Thread, its stacks, etc.
CreatedThread thread;
{
// TODO(https://fxbug.dev/342469121): With the legacy musl dynamic linker,
// the "static" TLS size can change dynamically with dlopen calls. The
// GetTlsLayout() and InitializeTls() calls inside Allocate() need to be
// "atomic" with respect to adding the thread to the global list, from the
// perspective of dlopen. Either this thread used the current TLS sizes
// after the last dlopen change, or the next dlopen will see this thread on
// its list to be updated for new TLS sizes.
std::lock_guard static_tls_lock{kLockForThreadCreate};
ThreadStorage storage;
zx::unowned_vmar allocation_vmar = AllocationVmar();
zx::result allocate =
storage.Allocate(allocation_vmar->borrow(), vmo_name, attrs.stack, attrs.guard);
if (allocate.is_error()) {
return allocate.take_error();
}
assert(storage.vmar().get() == allocation_vmar->get());
// Take ownership of the Thread here, moving storage ownership into it.
thread.reset(*allocate);
std::move(storage).ToThread(*thread);
assert(thread->storage_vmar == allocation_vmar->get());
// With that ownership goes ownership of its place on the all-threads list.
AllThreads().push_front(*thread);
}
// Hereafter, when this CreatedThread object dies, that will remove it from
// the all-threads list and reclaim all the storage. But there is still no
// kernel thread yet, so it's time to create that now.
Thread& self = *__pthread_self();
zx::unowned_process process{self.process_handle};
zx::thread thread_handle;
zx_status_t status = zx::thread::create(
*process, thread_name.data(), static_cast<uint32_t>(thread_name.size()), 0, &thread_handle);
if (status != ZX_OK) [[unlikely]] {
return zx::error{status};
}
thread->zxr_thread.handle = thread_handle.release();
const ThreadState initial_state = // State must be set before ThreadStart.
attrs.detached ? ThreadState::DETACHED : ThreadState::JOINABLE;
// The state update is always ordered after the handle update.
thread->zxr_thread.state.store(initial_state, std::memory_order_release);
// This is the same in every thread, with the initial thread's slot holding
// the original source of truth rather than any global location.
thread->abi.stack_guard = self.abi.stack_guard;
// This is inherited from the creating thread, but might be changed with
// thrd_set_zx_process.
thread->process_handle = self.process_handle;
// The user callback supplies the void* given to the next user callback.
thread->sanitizer_hook = SanitizerBeforeThreadCreateHook::Or<void*>{}(
ToC11Thread(*thread), attrs.detached, attrs.name.c_str(), thread->safe_stack.iov_base,
thread->safe_stack.iov_len);
return zx::ok(std::move(thread));
}
} // namespace LIBC_NAMESPACE_DECL