blob: e6c92995e00be0fb19397f7e6824cb8a1f4ac87d [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.
#ifndef LIB_C_THREADS_THREAD_STORAGE_H_
#define LIB_C_THREADS_THREAD_STORAGE_H_
#include <lib/elfldltl/tls-layout.h>
#include <lib/zx/result.h>
#include <cstddef>
#include <span>
#include <string_view>
#include <type_traits>
#include "../asm-linkage.h" // TODO(https://fxbug.dev/342469121): see below
#include "../zircon/vmar.h"
#include "shadow-call-stack.h"
#include "src/__support/macros/config.h"
struct __pthread; // Forward declared for legacy "threads_impl.h".
namespace LIBC_NAMESPACE_DECL {
using Thread = ::__pthread; // Legacy C type.
// ThreadStorage handles the memory allocation and ownership for Thread. It's
// responsible for allocating all the various kinds of stacks, and the thread
// area that underlies both the implementation's private Thread Control Block
// (TCB) as well as the public Fuchsia Compiler ABI and ELF TLS. ThreadStorage
// initializes only the parts of the TCB that are part of the public ABI
// (including all of ELF Initial Exec TLS) and then leaves the rest of the TCB
// zero-initialized for later use by libc internals.
//
// Initially it's used at process startup and thread creation. It owns those
// allocations and cleans them up on destruction e.g. if thread creation fails.
// During the thread's lifetime, the ThreadStorage is moved into its Thread.
// This is a bit tricky, as the Thread object's own memory resides in one of
// the blocks that ThreadStorage owns. So to destroy Thread, its ThreadStorage
// must be moved back out before explicitly calling ~Thread().
//
// ThreadStorage can only be default-constructed or moved. Before Allocate()
// has returned successfully (or after it fails), it should only be destroyed
// and no other methods used (except for moves).
class ThreadStorage {
public:
constexpr ThreadStorage() = default;
ThreadStorage(ThreadStorage&& other) { *this = std::move(other); }
ThreadStorage& operator=(ThreadStorage&& other) {
auto move_members = [this, &other](auto... m) {
((this->*m = std::exchange(other.*m, {})), ...);
};
move_members(&ThreadStorage::stack_size_, &ThreadStorage::guard_size_,
&ThreadStorage::thread_block_, &ThreadStorage::machine_stack_,
&ThreadStorage::unsafe_stack_, &ThreadStorage::shadow_call_stack_);
return *this;
}
// This allocates everything and holds ownership in this object. If it
// returns an error, some resources may now be owned but nothing more should
// be done with the object but to destroy it (and thus reclaim them).
//
// When this returns success, the remaining methods return useful values.
// The machine stack, unsafe stack (for the SafeStack ABI, enabled on all
// machines), and shadow call stack (enabled on some machines), are all
// allocated with guard pages and all-zeroes contents. In the thread area:
//
// * The unsafe stack pointer at $tp + ZX_TLS_UNSAFE_SP_OFFSET is set to
// the top of the unsafe stack.
//
// * If the machine's TLS ABI has *$tp = $tp (like x86), that is set.
//
// * The ZX_TLS_STACK_GUARD_OFFSET word still must be set. For the initial
// thread, the caller will choose random bits; for new threads, the caller
// will copy the value found via its own $tp.
//
// * All the ELF Initial Exec TLS data (static TLS) is initialized.
//
// * The DTV for the dynamic TLS implementation is allocated and filled in.
// TODO(https://fxbug.dev/397084454): The new //sdk/lib/dl TLS runtime
// does not require a bespoke ABI contract for a DTV.
//
// The name should match the ZX_PROP_NAME used for the zx::thread. The VMAR
// handle is saved and used for destruction, so it must remain valid for the
// lifetime of the object (it's just the long-lived primary allocation / root
// VMAR handle, except in tests).
//
// The returned Thread* points somewhere inside the thread area block owned
// by this ThreadStorage object, which also contains the static TLS area
// already initialized with all the Initial Exec TLS data. The ABI rules
// govern where that is in relation to the thread pointer ($tp) and thereby,
// indirectly, where the TCB lies relative to $tp. (Note that here we
// consider the Fuchsia Compiler ABI <zircon/tls.h> fixed slots, as well as
// the $tp->self pointer on x86, to be part of the TCB--at one end of it or
// the other--though nothing else about the TCB is part of any public ABI.)
zx::result<Thread*> Allocate(zx::unowned_vmar allocate_from, std::string_view thread_name,
PageRoundedSize stack, PageRoundedSize guard);
// This frees just the blocks for all the stacks, leaving the thread block
// (where the Thread object itself resides) intact until destruction.
void FreeStacks();
// This moves ownership of the ThreadStorage out of the Thread, making it
// possible to destroy the Thread before destroying the ThreadStorage makes
// its memory inaccessible. (When Thread becomes a true C++ type, this will
// be replaced with plain move-construction from its ThreadStorage member.)
static ThreadStorage FromThread(Thread& thread, zx::unowned_vmar vmar);
// This moves ownership from this ThreadStorage into the Thread. (When
// Thread becomes a true C++ type, this will be replaced with plain
// move-assignment to its ThreadStorage member.)
void ToThread(Thread& thread) &&;
// This returns the initial value for the machine SP. This is always the
// limit of the stack, where a push (`*--sp = ...`) will be the first thing
// done. This makes it appropriate for a call site, and on most machines for
// the entry to a C function. But on x86 it needs a return address pushed
// before it can be used at a C function's entry point.
uint64_t* machine_sp() const { return GrowsDown(machine_stack_); }
// This returns the initial value for the unsafe SP. This is always the
// limit of the stack, which is always the protocol for function entry.
// (This is already stored at $tp + ZX_TLS_UNSAFE_SP_OFFSET, too.)
uint64_t* unsafe_sp() const { return GrowsDown(unsafe_stack_); }
// This returns the initial value for the shadow call stack pointer.
// That stack grows up, so the next operation will be `*sp++ = ...`.
uint64_t* shadow_call_sp() const { return GrowsUp(shadow_call_stack_); }
PageRoundedSize stack_size() const { return stack_size_; }
PageRoundedSize guard_size() const { return guard_size_; }
~ThreadStorage() { FreeStacks(); }
// This takes a pointer-to-data-member and all the members are private. It's
// only used in implementation template code outside the class that's called
// by method implementations.
template <typename T>
requires(!std::is_function_v<T>)
static constexpr bool StackGrowsUp(T ThreadStorage::* stack) {
if constexpr (kShadowCallStackAbi) {
return stack == &ThreadStorage::shadow_call_stack_;
}
return false;
}
private:
// The shadow call stack grows up with guard above, so the initial pointer is
// just the base of the mapping.
static uint64_t* GrowsUp(NoShadowCallStack) { return nullptr; }
static uint64_t* GrowsUp(uintptr_t base) { return reinterpret_cast<uint64_t*>(base); }
// The other stacks grow down with guard below, so the initial pointer is at
// the end of the whole mapping.
uint64_t* GrowsDown(uintptr_t base) const {
return reinterpret_cast<uint64_t*>(base + guard_size_.get() + stack_size_.get());
}
// This acquires the information from the dynamic linker or from a static
// PIE's own PT_TLS segment.
static elfldltl::TlsLayout<> GetTlsLayout()
// TODO(https://fxbug.dev/342469121): This and InitializeTls only need
// asm-linkage to be defined in a separate hermetic_source_set() that
// allows the startup code to be shared between legacy and new
// implementations. When the legacy implementation is retired, these can
// drop special linkage.
LIBC_ASM_LINKAGE_DECLARE(GetTlsLayout);
// Given that `thread_block.data() + tp_offset` will become $tp for the new
// thread and that the space set aside for static TLS is all zero bytes now,
// this will initialize all its PT_TLS segments properly.
static void InitializeTls(std::span<std::byte> thread_block, size_t tp_offset)
// TODO(https://fxbug.dev/342469121): see above
LIBC_ASM_LINKAGE_DECLARE(InitializeTls);
GuardedPageBlock thread_block_;
PageRoundedSize stack_size_, guard_size_;
uintptr_t machine_stack_ = 0;
uintptr_t unsafe_stack_ = 0;
[[no_unique_address]] IfShadowCallStack<uintptr_t> shadow_call_stack_{};
};
static_assert(std::is_default_constructible_v<ThreadStorage>);
static_assert(std::is_move_constructible_v<ThreadStorage>);
static_assert(std::is_move_assignable_v<ThreadStorage>);
} // namespace LIBC_NAMESPACE_DECL
#endif // LIB_C_THREADS_THREAD_STORAGE_H_