blob: 1f3ce3f9afd7558d7ffc53cffd15989fbaa171fb [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "partition_alloc/stack/stack.h"
#include <cstdint>
#include <limits>
#include "partition_alloc/build_config.h"
#include "partition_alloc/partition_alloc_base/compiler_specific.h"
#include "partition_alloc/partition_alloc_buildflags.h"
#include "partition_alloc/partition_alloc_check.h"
#if BUILDFLAG(IS_WIN)
#include <windows.h>
#else
#include <pthread.h>
#endif
#if defined(LIBC_GLIBC)
extern "C" void* __libc_stack_end;
#endif
namespace partition_alloc::internal {
#if BUILDFLAG(IS_WIN)
void* GetStackTop() {
#if defined(ARCH_CPU_X86_64)
return reinterpret_cast<void*>(
reinterpret_cast<NT_TIB64*>(NtCurrentTeb())->StackBase);
#elif defined(ARCH_CPU_32_BITS)
return reinterpret_cast<void*>(
reinterpret_cast<NT_TIB*>(NtCurrentTeb())->StackBase);
#elif defined(ARCH_CPU_ARM64)
// Windows 8 and later, see
// https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getcurrentthreadstacklimits
ULONG_PTR lowLimit, highLimit;
::GetCurrentThreadStackLimits(&lowLimit, &highLimit);
return reinterpret_cast<void*>(highLimit);
#else
#error "Unsupported GetStackStart"
#endif
}
#elif BUILDFLAG(IS_APPLE)
void* GetStackTop() {
return pthread_get_stackaddr_np(pthread_self());
}
#elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
void* GetStackTop() {
pthread_attr_t attr;
int error = pthread_getattr_np(pthread_self(), &attr);
if (!error) {
void* base;
size_t size;
error = pthread_attr_getstack(&attr, &base, &size);
PA_CHECK(!error);
pthread_attr_destroy(&attr);
return reinterpret_cast<uint8_t*>(base) + size;
}
#if defined(LIBC_GLIBC)
// pthread_getattr_np can fail for the main thread. In this case
// just like NaCl we rely on the __libc_stack_end to give us
// the start of the stack.
// See https://code.google.com/p/nativeclient/issues/detail?id=3431.
return __libc_stack_end;
#else
return nullptr;
#endif // defined(LIBC_GLIBC)
}
#else // BUILDFLAG(IS_WIN)
#error "Unsupported GetStackTop"
#endif // BUILDFLAG(IS_WIN)
using IterateStackCallback = void (*)(const Stack*, StackVisitor*, uintptr_t*);
extern "C" void PAPushAllRegistersAndIterateStack(const Stack*,
StackVisitor*,
IterateStackCallback);
Stack::Stack(void* stack_top) : stack_top_(stack_top) {
PA_DCHECK(stack_top);
}
PA_NOINLINE uintptr_t* GetStackPointer() {
return reinterpret_cast<uintptr_t*>(__builtin_frame_address(0));
}
namespace {
[[maybe_unused]] void IterateSafeStackIfNecessary(StackVisitor* visitor) {
#if defined(__has_feature)
#if __has_feature(safe_stack)
// Source:
// https://github.com/llvm/llvm-project/blob/main/compiler-rt/lib/safestack/safestack.cpp
constexpr size_t kSafeStackAlignmentBytes = 16;
void* stack_ptr = __builtin___get_unsafe_stack_ptr();
void* stack_top = __builtin___get_unsafe_stack_top();
PA_CHECK(stack_top > stack_ptr);
PA_CHECK(0u == (reinterpret_cast<uintptr_t>(stack_ptr) &
(kSafeStackAlignmentBytes - 1)));
PA_CHECK(0u == (reinterpret_cast<uintptr_t>(stack_top) &
(kSafeStackAlignmentBytes - 1)));
visitor->VisitStack(reinterpret_cast<uintptr_t*>(stack_ptr),
reinterpret_cast<uintptr_t*>(stack_top));
#endif // __has_feature(safe_stack)
#endif // defined(__has_feature)
}
// Called by the trampoline that pushes registers on the stack. This method
// should never be inlined to ensure that a possible redzone cannot contain
// any data that needs to be scanned.
// No ASAN support as method accesses redzones while walking the stack.
[[maybe_unused]] PA_NOINLINE PA_NO_SANITIZE("address") void IteratePointersImpl(
const Stack* stack,
StackVisitor* visitor,
uintptr_t* stack_ptr) {
PA_DCHECK(stack);
PA_DCHECK(visitor);
PA_CHECK(nullptr != stack->stack_top());
// All supported platforms should have their stack aligned to at least
// sizeof(void*).
constexpr size_t kMinStackAlignment = sizeof(void*);
PA_CHECK(0u ==
(reinterpret_cast<uintptr_t>(stack_ptr) & (kMinStackAlignment - 1)));
visitor->VisitStack(stack_ptr,
reinterpret_cast<uintptr_t*>(stack->stack_top()));
}
} // namespace
void Stack::IteratePointers(StackVisitor* visitor) const {
#if PA_BUILDFLAG(STACK_SCAN_SUPPORTED)
PAPushAllRegistersAndIterateStack(this, visitor, &IteratePointersImpl);
// No need to deal with callee-saved registers as they will be kept alive by
// the regular conservative stack iteration.
IterateSafeStackIfNecessary(visitor);
#endif // PA_BUILDFLAG(STACK_SCAN_SUPPORTED)
}
StackTopRegistry::StackTopRegistry() = default;
StackTopRegistry::~StackTopRegistry() = default;
// static
StackTopRegistry& StackTopRegistry::Get() {
static base::NoDestructor<StackTopRegistry> instance;
return *instance.get();
}
void StackTopRegistry::NotifyThreadCreated(void* stack_top) {
const auto tid = base::PlatformThread::CurrentId();
ScopedGuard guard(lock_);
stack_tops_.insert({tid, stack_top});
// Insertion may fail due to an existing entry
// (i.e. `insert(...).second == false`), but we allow it instead of `CHECK`ing
// it. Guaranteeing this function to be called exactly once is quite hard and
// we aim to guarantee "at least once".
}
void StackTopRegistry::NotifyThreadDestroyed() {
const auto tid = base::PlatformThread::CurrentId();
ScopedGuard guard(lock_);
PA_DCHECK(1 == stack_tops_.count(tid));
stack_tops_.erase(tid);
}
void* StackTopRegistry::GetCurrentThreadStackTop() const {
const auto tid = base::PlatformThread::CurrentId();
ScopedGuard guard(lock_);
auto it = stack_tops_.find(tid);
return it != stack_tops_.end() ? it->second : nullptr;
}
} // namespace partition_alloc::internal