blob: b50e9125b5dd16d5086e35a70e00dfcbd1f478fb [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/starscan/stack/stack.h"
#include <memory>
#include <ostream>
#include "build/build_config.h"
#include "partition_alloc/partition_alloc_base/compiler_specific.h"
#include "testing/gtest/include/gtest/gtest.h"
#if !defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
#if BUILDFLAG(IS_LINUX) && (defined(ARCH_CPU_X86) || defined(ARCH_CPU_X86_64))
#include <xmmintrin.h>
#endif
namespace partition_alloc::internal {
namespace {
class PartitionAllocStackTest : public ::testing::Test {
protected:
PartitionAllocStackTest() : stack_(std::make_unique<Stack>(GetStackTop())) {}
Stack* GetStack() const { return stack_.get(); }
private:
std::unique_ptr<Stack> stack_;
};
class StackScanner final : public StackVisitor {
public:
struct Container {
std::unique_ptr<int> value;
};
StackScanner() : container_(std::make_unique<Container>()) {
container_->value = std::make_unique<int>();
}
void VisitStack(uintptr_t* stack_ptr, uintptr_t* stack_top) final {
for (; stack_ptr != stack_top; ++stack_ptr) {
if (*stack_ptr == reinterpret_cast<uintptr_t>(container_->value.get())) {
found_ = true;
}
}
}
void Reset() { found_ = false; }
bool found() const { return found_; }
int* needle() const { return container_->value.get(); }
private:
std::unique_ptr<Container> container_;
bool found_ = false;
};
} // namespace
TEST_F(PartitionAllocStackTest, IteratePointersFindsOnStackValue) {
auto scanner = std::make_unique<StackScanner>();
// No check that the needle is initially not found as on some platforms it
// may be part of temporaries after setting it up through StackScanner.
{
[[maybe_unused]] int* volatile tmp = scanner->needle();
GetStack()->IteratePointers(scanner.get());
EXPECT_TRUE(scanner->found());
}
}
TEST_F(PartitionAllocStackTest,
IteratePointersFindsOnStackValuePotentiallyUnaligned) {
auto scanner = std::make_unique<StackScanner>();
// No check that the needle is initially not found as on some platforms it
// may be part of temporaries after setting it up through StackScanner.
{
[[maybe_unused]] char a = 'c';
[[maybe_unused]] int* volatile tmp = scanner->needle();
GetStack()->IteratePointers(scanner.get());
EXPECT_TRUE(scanner->found());
}
}
namespace {
// Prevent inlining as that would allow the compiler to prove that the parameter
// must not actually be materialized.
//
// Parameter positiosn are explicit to test various calling conventions.
PA_NOINLINE void* RecursivelyPassOnParameterImpl(void* p1,
void* p2,
void* p3,
void* p4,
void* p5,
void* p6,
void* p7,
void* p8,
Stack* stack,
StackVisitor* visitor) {
if (p1) {
return RecursivelyPassOnParameterImpl(nullptr, p1, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
stack, visitor);
} else if (p2) {
return RecursivelyPassOnParameterImpl(nullptr, nullptr, p2, nullptr,
nullptr, nullptr, nullptr, nullptr,
stack, visitor);
} else if (p3) {
return RecursivelyPassOnParameterImpl(nullptr, nullptr, nullptr, p3,
nullptr, nullptr, nullptr, nullptr,
stack, visitor);
} else if (p4) {
return RecursivelyPassOnParameterImpl(nullptr, nullptr, nullptr, nullptr,
p4, nullptr, nullptr, nullptr, stack,
visitor);
} else if (p5) {
return RecursivelyPassOnParameterImpl(nullptr, nullptr, nullptr, nullptr,
nullptr, p5, nullptr, nullptr, stack,
visitor);
} else if (p6) {
return RecursivelyPassOnParameterImpl(nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, p6, nullptr, stack,
visitor);
} else if (p7) {
return RecursivelyPassOnParameterImpl(nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, p7, stack,
visitor);
} else if (p8) {
stack->IteratePointers(visitor);
return p8;
}
return nullptr;
}
PA_NOINLINE void* RecursivelyPassOnParameter(size_t num,
void* parameter,
Stack* stack,
StackVisitor* visitor) {
switch (num) {
case 0:
stack->IteratePointers(visitor);
return parameter;
case 1:
return RecursivelyPassOnParameterImpl(nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr,
parameter, stack, visitor);
case 2:
return RecursivelyPassOnParameterImpl(nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, parameter,
nullptr, stack, visitor);
case 3:
return RecursivelyPassOnParameterImpl(nullptr, nullptr, nullptr, nullptr,
nullptr, parameter, nullptr,
nullptr, stack, visitor);
case 4:
return RecursivelyPassOnParameterImpl(nullptr, nullptr, nullptr, nullptr,
parameter, nullptr, nullptr,
nullptr, stack, visitor);
case 5:
return RecursivelyPassOnParameterImpl(nullptr, nullptr, nullptr,
parameter, nullptr, nullptr,
nullptr, nullptr, stack, visitor);
case 6:
return RecursivelyPassOnParameterImpl(nullptr, nullptr, parameter,
nullptr, nullptr, nullptr, nullptr,
nullptr, stack, visitor);
case 7:
return RecursivelyPassOnParameterImpl(nullptr, parameter, nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr, stack, visitor);
case 8:
return RecursivelyPassOnParameterImpl(parameter, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr, stack, visitor);
default:
__builtin_unreachable();
}
__builtin_unreachable();
}
} // namespace
TEST_F(PartitionAllocStackTest, IteratePointersFindsParameterNesting0) {
auto scanner = std::make_unique<StackScanner>();
void* needle = RecursivelyPassOnParameter(0, scanner->needle(), GetStack(),
scanner.get());
EXPECT_EQ(scanner->needle(), needle);
EXPECT_TRUE(scanner->found());
}
TEST_F(PartitionAllocStackTest, IteratePointersFindsParameterNesting1) {
auto scanner = std::make_unique<StackScanner>();
void* needle = RecursivelyPassOnParameter(1, scanner->needle(), GetStack(),
scanner.get());
EXPECT_EQ(scanner->needle(), needle);
EXPECT_TRUE(scanner->found());
}
TEST_F(PartitionAllocStackTest, IteratePointersFindsParameterNesting2) {
auto scanner = std::make_unique<StackScanner>();
void* needle = RecursivelyPassOnParameter(2, scanner->needle(), GetStack(),
scanner.get());
EXPECT_EQ(scanner->needle(), needle);
EXPECT_TRUE(scanner->found());
}
TEST_F(PartitionAllocStackTest, IteratePointersFindsParameterNesting3) {
auto scanner = std::make_unique<StackScanner>();
void* needle = RecursivelyPassOnParameter(3, scanner->needle(), GetStack(),
scanner.get());
EXPECT_EQ(scanner->needle(), needle);
EXPECT_TRUE(scanner->found());
}
TEST_F(PartitionAllocStackTest, IteratePointersFindsParameterNesting4) {
auto scanner = std::make_unique<StackScanner>();
void* needle = RecursivelyPassOnParameter(4, scanner->needle(), GetStack(),
scanner.get());
EXPECT_EQ(scanner->needle(), needle);
EXPECT_TRUE(scanner->found());
}
TEST_F(PartitionAllocStackTest, IteratePointersFindsParameterNesting5) {
auto scanner = std::make_unique<StackScanner>();
void* needle = RecursivelyPassOnParameter(5, scanner->needle(), GetStack(),
scanner.get());
EXPECT_EQ(scanner->needle(), needle);
EXPECT_TRUE(scanner->found());
}
TEST_F(PartitionAllocStackTest, IteratePointersFindsParameterNesting6) {
auto scanner = std::make_unique<StackScanner>();
void* needle = RecursivelyPassOnParameter(6, scanner->needle(), GetStack(),
scanner.get());
EXPECT_EQ(scanner->needle(), needle);
EXPECT_TRUE(scanner->found());
}
TEST_F(PartitionAllocStackTest, IteratePointersFindsParameterNesting7) {
auto scanner = std::make_unique<StackScanner>();
void* needle = RecursivelyPassOnParameter(7, scanner->needle(), GetStack(),
scanner.get());
EXPECT_EQ(scanner->needle(), needle);
EXPECT_TRUE(scanner->found());
}
TEST_F(PartitionAllocStackTest, IteratePointersFindsParameterNesting8) {
auto scanner = std::make_unique<StackScanner>();
void* needle = RecursivelyPassOnParameter(8, scanner->needle(), GetStack(),
scanner.get());
EXPECT_EQ(scanner->needle(), needle);
EXPECT_TRUE(scanner->found());
}
// The following test uses inline assembly and has been checked to work on clang
// to verify that the stack-scanning trampoline pushes callee-saved registers.
//
// The test uses a macro loop as asm() can only be passed string literals.
#if defined(__clang__) && defined(ARCH_CPU_X86_64) && !BUILDFLAG(IS_WIN)
// Excluded from test: rbp
#define FOR_ALL_CALLEE_SAVED_REGS(V) \
V(rbx) \
V(r12) \
V(r13) \
V(r14) \
V(r15)
namespace {
extern "C" void IteratePointersNoMangling(Stack* stack, StackVisitor* visitor) {
stack->IteratePointers(visitor);
}
#define DEFINE_MOVE_INTO(reg) \
PA_NOINLINE void MoveInto##reg(Stack* local_stack, \
StackScanner* local_scanner) { \
asm volatile(" mov %0, %%" #reg \
"\n mov %1, %%rdi" \
"\n mov %2, %%rsi" \
"\n call %P3" \
"\n mov $0, %%" #reg \
: \
: "r"(local_scanner->needle()), "r"(local_stack), \
"r"(local_scanner), "i"(IteratePointersNoMangling) \
: "memory", #reg, "rdi", "rsi", "cc"); \
}
FOR_ALL_CALLEE_SAVED_REGS(DEFINE_MOVE_INTO)
} // namespace
TEST_F(PartitionAllocStackTest, IteratePointersFindsCalleeSavedRegisters) {
auto scanner = std::make_unique<StackScanner>();
// No check that the needle is initially not found as on some platforms it
// may be part of temporaries after setting it up through StackScanner.
// First, clear all callee-saved registers.
#define CLEAR_REGISTER(reg) asm("mov $0, %%" #reg : : : #reg);
FOR_ALL_CALLEE_SAVED_REGS(CLEAR_REGISTER)
#undef CLEAR_REGISTER
// Keep local raw pointers to keep instruction sequences small below.
auto* local_stack = GetStack();
auto* local_scanner = scanner.get();
// Moves |local_scanner->needle()| into a callee-saved register, leaving the
// callee-saved register as the only register referencing the needle.
// (Ignoring implementation-dependent dirty registers/stack.)
#define KEEP_ALIVE_FROM_CALLEE_SAVED(reg) \
local_scanner->Reset(); \
MoveInto##reg(local_stack, local_scanner); \
EXPECT_TRUE(local_scanner->found()) \
<< "pointer in callee-saved register not found. register: " << #reg \
<< std::endl;
FOR_ALL_CALLEE_SAVED_REGS(KEEP_ALIVE_FROM_CALLEE_SAVED)
#undef KEEP_ALIVE_FROM_CALLEE_SAVED
#undef FOR_ALL_CALLEE_SAVED_REGS
}
#endif // defined(__clang__) && defined(ARCH_CPU_X86_64) && !BUILDFLAG(IS_WIN)
#if BUILDFLAG(IS_LINUX) && (defined(ARCH_CPU_X86) || defined(ARCH_CPU_X86_64))
class CheckStackAlignmentVisitor final : public StackVisitor {
public:
void VisitStack(uintptr_t*, uintptr_t*) final {
// Check that the stack doesn't get misaligned by asm trampolines.
float f[4] = {0.};
[[maybe_unused]] volatile auto xmm = ::_mm_load_ps(f);
}
};
TEST_F(PartitionAllocStackTest, StackAlignment) {
auto checker = std::make_unique<CheckStackAlignmentVisitor>();
GetStack()->IteratePointers(checker.get());
}
#endif // BUILDFLAG(IS_LINUX) && (defined(ARCH_CPU_X86) ||
// defined(ARCH_CPU_X86_64))
} // namespace partition_alloc::internal
#endif // !defined(MEMORY_TOOL_REPLACES_ALLOCATOR)