236 lines
9.0 KiB
C++
236 lines
9.0 KiB
C++
// Copyright 2019 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include <cstring>
|
|
#include <iterator>
|
|
#include <memory>
|
|
#include <numeric>
|
|
|
|
#include "base/profiler/stack_buffer.h"
|
|
#include "base/profiler/stack_copier.h"
|
|
#include "testing/gmock/include/gmock/gmock.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
|
|
namespace base {
|
|
|
|
namespace {
|
|
|
|
class CopyFunctions : public StackCopier {
|
|
public:
|
|
using StackCopier::CopyStackContentsAndRewritePointers;
|
|
using StackCopier::RewritePointerIfInOriginalStack;
|
|
};
|
|
|
|
static constexpr size_t kTestStackBufferSize = sizeof(uintptr_t) * 4;
|
|
|
|
union alignas(StackBuffer::kPlatformStackAlignment) TestStackBuffer {
|
|
uintptr_t as_uintptr[kTestStackBufferSize / sizeof(uintptr_t)];
|
|
uint16_t as_uint16[kTestStackBufferSize / sizeof(uint16_t)];
|
|
uint8_t as_uint8[kTestStackBufferSize / sizeof(uint8_t)];
|
|
};
|
|
|
|
} // namespace
|
|
|
|
TEST(StackCopierTest, RewritePointerIfInOriginalStack_InStack) {
|
|
uintptr_t original_stack[4];
|
|
uintptr_t stack_copy[4];
|
|
EXPECT_EQ(reinterpret_cast<uintptr_t>(&stack_copy[2]),
|
|
CopyFunctions::RewritePointerIfInOriginalStack(
|
|
reinterpret_cast<uint8_t*>(&original_stack[0]),
|
|
&original_stack[0] + std::size(original_stack),
|
|
reinterpret_cast<uint8_t*>(&stack_copy[0]),
|
|
reinterpret_cast<uintptr_t>(&original_stack[2])));
|
|
}
|
|
|
|
TEST(StackCopierTest, RewritePointerIfInOriginalStack_NotInStack) {
|
|
// We use this variable only for its address, which is outside of
|
|
// original_stack.
|
|
uintptr_t non_stack_location;
|
|
uintptr_t original_stack[4];
|
|
uintptr_t stack_copy[4];
|
|
|
|
EXPECT_EQ(reinterpret_cast<uintptr_t>(&non_stack_location),
|
|
CopyFunctions::RewritePointerIfInOriginalStack(
|
|
reinterpret_cast<uint8_t*>(&original_stack[0]),
|
|
&original_stack[0] + std::size(original_stack),
|
|
reinterpret_cast<uint8_t*>(&stack_copy[0]),
|
|
reinterpret_cast<uintptr_t>(&non_stack_location)));
|
|
}
|
|
|
|
TEST(StackCopierTest, StackCopy) {
|
|
TestStackBuffer original_stack;
|
|
// Fill the stack buffer with increasing uintptr_t values.
|
|
std::iota(
|
|
&original_stack.as_uintptr[0],
|
|
&original_stack.as_uintptr[0] + std::size(original_stack.as_uintptr),
|
|
100);
|
|
// Replace the third value with an address within the buffer.
|
|
original_stack.as_uintptr[2] =
|
|
reinterpret_cast<uintptr_t>(&original_stack.as_uintptr[1]);
|
|
TestStackBuffer stack_copy;
|
|
|
|
CopyFunctions::CopyStackContentsAndRewritePointers(
|
|
&original_stack.as_uint8[0],
|
|
&original_stack.as_uintptr[0] + std::size(original_stack.as_uintptr),
|
|
StackBuffer::kPlatformStackAlignment, &stack_copy.as_uintptr[0]);
|
|
|
|
EXPECT_EQ(original_stack.as_uintptr[0], stack_copy.as_uintptr[0]);
|
|
EXPECT_EQ(original_stack.as_uintptr[1], stack_copy.as_uintptr[1]);
|
|
EXPECT_EQ(reinterpret_cast<uintptr_t>(&stack_copy.as_uintptr[1]),
|
|
stack_copy.as_uintptr[2]);
|
|
EXPECT_EQ(original_stack.as_uintptr[3], stack_copy.as_uintptr[3]);
|
|
}
|
|
|
|
TEST(StackCopierTest, StackCopy_NonAlignedStackPointerCopy) {
|
|
TestStackBuffer stack_buffer;
|
|
|
|
// Fill the stack buffer with increasing uint16_t values.
|
|
std::iota(&stack_buffer.as_uint16[0],
|
|
&stack_buffer.as_uint16[0] + std::size(stack_buffer.as_uint16),
|
|
100);
|
|
|
|
// Set the stack bottom to the unaligned location one uint16_t into the
|
|
// buffer.
|
|
uint8_t* unaligned_stack_bottom =
|
|
reinterpret_cast<uint8_t*>(&stack_buffer.as_uint16[1]);
|
|
|
|
// Leave extra space within the stack buffer beyond the end of the stack, but
|
|
// preserve the platform alignment.
|
|
const size_t extra_space = StackBuffer::kPlatformStackAlignment;
|
|
uintptr_t* stack_top =
|
|
&stack_buffer.as_uintptr[std::size(stack_buffer.as_uintptr) -
|
|
extra_space / sizeof(uintptr_t)];
|
|
|
|
// Initialize the copy to all zeros.
|
|
TestStackBuffer stack_copy_buffer = {{0}};
|
|
|
|
const uint8_t* stack_copy_bottom =
|
|
CopyFunctions::CopyStackContentsAndRewritePointers(
|
|
unaligned_stack_bottom, stack_top,
|
|
StackBuffer::kPlatformStackAlignment,
|
|
&stack_copy_buffer.as_uintptr[0]);
|
|
|
|
// The stack copy bottom address is expected to be at the same offset into the
|
|
// stack copy buffer as the unaligned stack bottom is from the stack buffer.
|
|
// Since the buffers have the same platform stack alignment this also ensures
|
|
// the alignment of the bottom addresses is the same.
|
|
EXPECT_EQ(unaligned_stack_bottom - &stack_buffer.as_uint8[0],
|
|
stack_copy_bottom - &stack_copy_buffer.as_uint8[0]);
|
|
|
|
// The first value in the copy should not be overwritten since the stack
|
|
// starts at the second uint16_t.
|
|
EXPECT_EQ(0u, stack_copy_buffer.as_uint16[0]);
|
|
|
|
// The next values up to the extra space should have been copied.
|
|
const size_t max_index =
|
|
std::size(stack_copy_buffer.as_uint16) - extra_space / sizeof(uint16_t);
|
|
for (size_t i = 1; i < max_index; ++i)
|
|
EXPECT_EQ(i + 100, stack_copy_buffer.as_uint16[i]);
|
|
|
|
// None of the values in the empty space should have been copied.
|
|
for (size_t i = max_index; i < std::size(stack_copy_buffer.as_uint16); ++i)
|
|
EXPECT_EQ(0u, stack_copy_buffer.as_uint16[i]);
|
|
}
|
|
|
|
// Checks that an unaligned within-stack pointer value at the start of the stack
|
|
// is not rewritten.
|
|
TEST(StackCopierTest, StackCopy_NonAlignedStackPointerUnalignedRewriteAtStart) {
|
|
// Initially fill the buffer with 0s.
|
|
TestStackBuffer stack_buffer = {{0}};
|
|
|
|
// Set the stack bottom to the unaligned location one uint16_t into the
|
|
// buffer.
|
|
uint8_t* unaligned_stack_bottom =
|
|
reinterpret_cast<uint8_t*>(&stack_buffer.as_uint16[1]);
|
|
|
|
// Set the first unaligned pointer-sized value to an address within the stack.
|
|
uintptr_t within_stack_pointer =
|
|
reinterpret_cast<uintptr_t>(&stack_buffer.as_uintptr[2]);
|
|
std::memcpy(unaligned_stack_bottom, &within_stack_pointer,
|
|
sizeof(within_stack_pointer));
|
|
|
|
TestStackBuffer stack_copy_buffer = {{0}};
|
|
|
|
const uint8_t* stack_copy_bottom =
|
|
CopyFunctions::CopyStackContentsAndRewritePointers(
|
|
unaligned_stack_bottom,
|
|
&stack_buffer.as_uintptr[0] + std::size(stack_buffer.as_uintptr),
|
|
StackBuffer::kPlatformStackAlignment,
|
|
&stack_copy_buffer.as_uintptr[0]);
|
|
|
|
uintptr_t copied_within_stack_pointer;
|
|
std::memcpy(&copied_within_stack_pointer, stack_copy_bottom,
|
|
sizeof(copied_within_stack_pointer));
|
|
|
|
// The rewriting should only operate on pointer-aligned values so the
|
|
// unaligned value should be copied verbatim.
|
|
EXPECT_EQ(within_stack_pointer, copied_within_stack_pointer);
|
|
}
|
|
|
|
// Checks that an unaligned within-stack pointer after the start of the stack is
|
|
// not rewritten.
|
|
TEST(StackCopierTest,
|
|
StackCopy_NonAlignedStackPointerUnalignedRewriteAfterStart) {
|
|
// Initially fill the buffer with 0s.
|
|
TestStackBuffer stack_buffer = {{0}};
|
|
|
|
// Set the stack bottom to the unaligned location one uint16_t into the
|
|
// buffer.
|
|
uint8_t* unaligned_stack_bottom =
|
|
reinterpret_cast<uint8_t*>(&stack_buffer.as_uint16[1]);
|
|
|
|
// Set the second unaligned pointer-sized value to an address within the
|
|
// stack.
|
|
uintptr_t within_stack_pointer =
|
|
reinterpret_cast<uintptr_t>(&stack_buffer.as_uintptr[2]);
|
|
std::memcpy(unaligned_stack_bottom + sizeof(uintptr_t), &within_stack_pointer,
|
|
sizeof(within_stack_pointer));
|
|
|
|
TestStackBuffer stack_copy_buffer = {{0}};
|
|
|
|
const uint8_t* stack_copy_bottom =
|
|
CopyFunctions::CopyStackContentsAndRewritePointers(
|
|
unaligned_stack_bottom,
|
|
&stack_buffer.as_uintptr[0] + std::size(stack_buffer.as_uintptr),
|
|
StackBuffer::kPlatformStackAlignment,
|
|
&stack_copy_buffer.as_uintptr[0]);
|
|
|
|
uintptr_t copied_within_stack_pointer;
|
|
std::memcpy(&copied_within_stack_pointer,
|
|
stack_copy_bottom + sizeof(uintptr_t),
|
|
sizeof(copied_within_stack_pointer));
|
|
|
|
// The rewriting should only operate on pointer-aligned values so the
|
|
// unaligned value should be copied verbatim.
|
|
EXPECT_EQ(within_stack_pointer, copied_within_stack_pointer);
|
|
}
|
|
|
|
TEST(StackCopierTest, StackCopy_NonAlignedStackPointerAlignedRewrite) {
|
|
// Initially fill the buffer with 0s.
|
|
TestStackBuffer stack_buffer = {{0}};
|
|
|
|
// Set the stack bottom to the unaligned location one uint16_t into the
|
|
// buffer.
|
|
uint8_t* unaligned_stack_bottom =
|
|
reinterpret_cast<uint8_t*>(&stack_buffer.as_uint16[1]);
|
|
|
|
// Set the second aligned pointer-sized value to an address within the stack.
|
|
stack_buffer.as_uintptr[1] =
|
|
reinterpret_cast<uintptr_t>(&stack_buffer.as_uintptr[2]);
|
|
|
|
TestStackBuffer stack_copy_buffer = {{0}};
|
|
|
|
CopyFunctions::CopyStackContentsAndRewritePointers(
|
|
unaligned_stack_bottom,
|
|
&stack_buffer.as_uintptr[0] + std::size(stack_buffer.as_uintptr),
|
|
StackBuffer::kPlatformStackAlignment, &stack_copy_buffer.as_uintptr[0]);
|
|
|
|
// The aligned pointer should have been rewritten to point within the stack
|
|
// copy.
|
|
EXPECT_EQ(reinterpret_cast<uintptr_t>(&stack_copy_buffer.as_uintptr[2]),
|
|
stack_copy_buffer.as_uintptr[1]);
|
|
}
|
|
|
|
} // namespace base
|