Commit 30bff49e authored by Mike Wittman's avatar Mike Wittman Committed by Commit Bot

Create StackCopier interface and extract utility functions

This interface will be implemented in following CLs and used in
StackSamplerImpl. The main thrust of this change is to move utility
functions used by the stack copying (and their tests) to their
ultimate location.

No functional change is intended.

TBR=gab@chromium.org

Bug: 988579
Change-Id: I45d8c5ca155dd16dd69875c01548dd5e1ab9602d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1744907Reviewed-by: default avatarMike Wittman <wittman@chromium.org>
Reviewed-by: default avatarCharlie Andrews <charliea@chromium.org>
Commit-Queue: Mike Wittman <wittman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#691820}
parent 2e4ff929
...@@ -616,6 +616,8 @@ jumbo_component("base") { ...@@ -616,6 +616,8 @@ jumbo_component("base") {
"profiler/sample_metadata.h", "profiler/sample_metadata.h",
"profiler/stack_buffer.cc", "profiler/stack_buffer.cc",
"profiler/stack_buffer.h", "profiler/stack_buffer.h",
"profiler/stack_copier.cc",
"profiler/stack_copier.h",
"profiler/stack_sampler.cc", "profiler/stack_sampler.cc",
"profiler/stack_sampler.h", "profiler/stack_sampler.h",
"profiler/stack_sampler_android.cc", "profiler/stack_sampler_android.cc",
...@@ -2634,6 +2636,7 @@ test("base_unittests") { ...@@ -2634,6 +2636,7 @@ test("base_unittests") {
"process/process_util_unittest.cc", "process/process_util_unittest.cc",
"profiler/metadata_recorder_unittest.cc", "profiler/metadata_recorder_unittest.cc",
"profiler/sample_metadata_unittest.cc", "profiler/sample_metadata_unittest.cc",
"profiler/stack_copier_unittest.cc",
"profiler/stack_sampler_impl_unittest.cc", "profiler/stack_sampler_impl_unittest.cc",
"profiler/stack_sampling_profiler_unittest.cc", "profiler/stack_sampling_profiler_unittest.cc",
"rand_util_unittest.cc", "rand_util_unittest.cc",
......
// Copyright 2019 The Chromium 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 "base/profiler/stack_copier.h"
#include "base/compiler_specific.h"
namespace base {
StackCopier::~StackCopier() = default;
// static
uintptr_t StackCopier::RewritePointerIfInOriginalStack(
const uint8_t* original_stack_bottom,
const uintptr_t* original_stack_top,
const uint8_t* stack_copy_bottom,
uintptr_t pointer) {
auto original_stack_bottom_uint =
reinterpret_cast<uintptr_t>(original_stack_bottom);
auto original_stack_top_uint =
reinterpret_cast<uintptr_t>(original_stack_top);
auto stack_copy_bottom_uint = reinterpret_cast<uintptr_t>(stack_copy_bottom);
if (pointer < original_stack_bottom_uint ||
pointer >= original_stack_top_uint)
return pointer;
return stack_copy_bottom_uint + (pointer - original_stack_bottom_uint);
}
// static
NO_SANITIZE("address")
const uint8_t* StackCopier::CopyStackContentsAndRewritePointers(
const uint8_t* original_stack_bottom,
const uintptr_t* original_stack_top,
int platform_stack_alignment,
uintptr_t* stack_buffer_bottom) {
const uint8_t* byte_src = original_stack_bottom;
// The first address in the stack with pointer alignment. Pointer-aligned
// values from this point to the end of the stack are possibly rewritten using
// RewritePointerIfInOriginalStack(). Bytes before this cannot be a pointer
// because they occupy less space than a pointer would.
const uint8_t* first_aligned_address = reinterpret_cast<uint8_t*>(
(reinterpret_cast<uintptr_t>(byte_src) + sizeof(uintptr_t) - 1) &
~(sizeof(uintptr_t) - 1));
// The stack copy bottom, which is offset from |stack_buffer_bottom| by the
// same alignment as in the original stack. This guarantees identical
// alignment between values in the original stack and the copy. This uses the
// platform stack alignment rather than pointer alignment so that the stack
// copy is aligned to platform expectations.
uint8_t* stack_copy_bottom =
reinterpret_cast<uint8_t*>(stack_buffer_bottom) +
(reinterpret_cast<uintptr_t>(byte_src) & (platform_stack_alignment - 1));
uint8_t* byte_dst = stack_copy_bottom;
// Copy bytes verbatim up to the first aligned address.
for (; byte_src < first_aligned_address; ++byte_src, ++byte_dst)
*byte_dst = *byte_src;
// Copy the remaining stack by pointer-sized values, rewriting anything that
// looks like a pointer into the stack.
const uintptr_t* src = reinterpret_cast<const uintptr_t*>(byte_src);
uintptr_t* dst = reinterpret_cast<uintptr_t*>(byte_dst);
for (; src < original_stack_top; ++src, ++dst) {
*dst = RewritePointerIfInOriginalStack(
original_stack_bottom, original_stack_top, stack_copy_bottom, *src);
}
return stack_copy_bottom;
}
} // namespace base
// Copyright 2019 The Chromium 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 BASE_PROFILER_STACK_COPIER_H_
#define BASE_PROFILER_STACK_COPIER_H_
#include <stdint.h>
#include "base/base_export.h"
#include "base/profiler/register_context.h"
namespace base {
class StackBuffer;
class ProfileBuilder;
// StackCopier causes a thread to be suspended, copies its stack, and resumes
// the thread's execution. It's intended to provide an abstraction over stack
// copying techniques where the thread suspension is performed directly by the
// profiler thread (Windows and Mac platforms) vs. where the thread suspension
// is performed by the OS through signals (Android).
class BASE_EXPORT StackCopier {
public:
virtual ~StackCopier();
// Copies the thread's register context into |thread_context|, the stack into
// |stack_buffer|, and the top of stack address into |stack_top|. Records
// metadata while the thread is suspended via |profile_builder|. Returns true
// if successful.
virtual bool CopyStack(StackBuffer* stack_buffer,
uintptr_t* stack_top,
ProfileBuilder* profile_builder,
RegisterContext* thread_context) = 0;
// If the value at |pointer| points to the original stack, rewrite it to point
// to the corresponding location in the copied stack.
//
// NO HEAP ALLOCATIONS.
static uintptr_t RewritePointerIfInOriginalStack(
const uint8_t* original_stack_bottom,
const uintptr_t* original_stack_top,
const uint8_t* stack_copy_bottom,
uintptr_t pointer);
// Copies the stack to a buffer while rewriting possible pointers to locations
// within the stack to point to the corresponding locations in the copy. This
// is necessary to handle stack frames with dynamic stack allocation, where a
// pointer to the beginning of the dynamic allocation area is stored on the
// stack and/or in a non-volatile register.
//
// Eager rewriting of anything that looks like a pointer to the stack, as done
// in this function, does not adversely affect the stack unwinding. The only
// other values on the stack the unwinding depends on are return addresses,
// which should not point within the stack memory. The rewriting is guaranteed
// to catch all pointers because the stacks are guaranteed by the ABI to be
// sizeof(uintptr_t*) aligned.
//
// |original_stack_bottom| and |original_stack_top| are different pointer
// types due on their differing guaranteed alignments -- the bottom may only
// be 1-byte aligned while the top is aligned to double the pointer width.
//
// Returns a pointer to the bottom address in the copied stack. This value
// matches the alignment of |original_stack_bottom| to ensure that the stack
// contents have the same alignment as in the original stack. As a result the
// value will be different than |stack_buffer_bottom| if
// |original_stack_bottom| is not aligned to double the pointer width.
//
// NO HEAP ALLOCATIONS.
static const uint8_t* CopyStackContentsAndRewritePointers(
const uint8_t* original_stack_bottom,
const uintptr_t* original_stack_top,
int platform_stack_alignment,
uintptr_t* stack_buffer_bottom);
};
} // namespace base
#endif // BASE_PROFILER_STACK_COPIER_H_
// Copyright 2019 The Chromium 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 <memory>
#include <numeric>
#include "base/profiler/stack_buffer.h"
#include "base/profiler/stack_copier.h"
#include "base/stl_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
namespace {
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]),
StackCopier::RewritePointerIfInOriginalStack(
reinterpret_cast<uint8_t*>(&original_stack[0]),
&original_stack[0] + base::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),
StackCopier::RewritePointerIfInOriginalStack(
reinterpret_cast<uint8_t*>(&original_stack[0]),
&original_stack[0] + 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] + 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;
StackCopier::CopyStackContentsAndRewritePointers(
&original_stack.as_uint8[0],
&original_stack.as_uintptr[0] + 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] + 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[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 =
StackCopier::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 =
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 < 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 =
StackCopier::CopyStackContentsAndRewritePointers(
unaligned_stack_bottom,
&stack_buffer.as_uintptr[0] + 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 =
StackCopier::CopyStackContentsAndRewritePointers(
unaligned_stack_bottom,
&stack_buffer.as_uintptr[0] + 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}};
StackCopier::CopyStackContentsAndRewritePointers(
unaligned_stack_bottom,
&stack_buffer.as_uintptr[0] + 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
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include "base/profiler/profile_builder.h" #include "base/profiler/profile_builder.h"
#include "base/profiler/sample_metadata.h" #include "base/profiler/sample_metadata.h"
#include "base/profiler/stack_buffer.h" #include "base/profiler/stack_buffer.h"
#include "base/profiler/stack_copier.h"
#include "base/profiler/thread_delegate.h" #include "base/profiler/thread_delegate.h"
#include "base/profiler/unwinder.h" #include "base/profiler/unwinder.h"
...@@ -112,18 +113,18 @@ bool StackSamplerImpl::CopyStack(StackBuffer* stack_buffer, ...@@ -112,18 +113,18 @@ bool StackSamplerImpl::CopyStack(StackBuffer* stack_buffer,
profile_builder->RecordMetadata(get_metadata_items.get()); profile_builder->RecordMetadata(get_metadata_items.get());
stack_copy_bottom = CopyStackContentsAndRewritePointers( stack_copy_bottom = StackCopier::CopyStackContentsAndRewritePointers(
reinterpret_cast<uint8_t*>(bottom), reinterpret_cast<uintptr_t*>(top), reinterpret_cast<uint8_t*>(bottom), reinterpret_cast<uintptr_t*>(top),
stack_buffer->buffer()); StackBuffer::kPlatformStackAlignment, stack_buffer->buffer());
} }
*stack_top = reinterpret_cast<uintptr_t>(stack_copy_bottom) + (top - bottom); *stack_top = reinterpret_cast<uintptr_t>(stack_copy_bottom) + (top - bottom);
for (uintptr_t* reg : for (uintptr_t* reg :
thread_delegate_->GetRegistersToRewrite(thread_context)) { thread_delegate_->GetRegistersToRewrite(thread_context)) {
*reg = RewritePointerIfInOriginalStack(reinterpret_cast<uint8_t*>(bottom), *reg = StackCopier::RewritePointerIfInOriginalStack(
reinterpret_cast<uintptr_t*>(top), reinterpret_cast<uint8_t*>(bottom), reinterpret_cast<uintptr_t*>(top),
stack_copy_bottom, *reg); stack_copy_bottom, *reg);
} }
return true; return true;
...@@ -173,90 +174,4 @@ std::vector<Frame> StackSamplerImpl::WalkStack(ModuleCache* module_cache, ...@@ -173,90 +174,4 @@ std::vector<Frame> StackSamplerImpl::WalkStack(ModuleCache* module_cache,
return stack; return stack;
} }
// If the value at |pointer| points to the original stack, rewrite it to point
// to the corresponding location in the copied stack. NO HEAP ALLOCATIONS.
// static
uintptr_t RewritePointerIfInOriginalStack(const uint8_t* original_stack_bottom,
const uintptr_t* original_stack_top,
const uint8_t* stack_copy_bottom,
uintptr_t pointer) {
auto original_stack_bottom_uint =
reinterpret_cast<uintptr_t>(original_stack_bottom);
auto original_stack_top_uint =
reinterpret_cast<uintptr_t>(original_stack_top);
auto stack_copy_bottom_uint = reinterpret_cast<uintptr_t>(stack_copy_bottom);
if (pointer < original_stack_bottom_uint ||
pointer >= original_stack_top_uint)
return pointer;
return stack_copy_bottom_uint + (pointer - original_stack_bottom_uint);
}
// Copies the stack to a buffer while rewriting possible pointers to locations
// within the stack to point to the corresponding locations in the copy. This is
// necessary to handle stack frames with dynamic stack allocation, where a
// pointer to the beginning of the dynamic allocation area is stored on the
// stack and/or in a non-volatile register.
//
// Eager rewriting of anything that looks like a pointer to the stack, as done
// in this function, does not adversely affect the stack unwinding. The only
// other values on the stack the unwinding depends on are return addresses,
// which should not point within the stack memory. The rewriting is guaranteed
// to catch all pointers because the stacks are guaranteed by the ABI to be
// sizeof(uintptr_t*) aligned.
//
// |original_stack_bottom| and |original_stack_top| are different pointer types
// due on their differing guaranteed alignments -- the bottom may only be 1-byte
// aligned while the top is aligned to double the pointer width.
//
// Returns a pointer to the bottom address in the copied stack. This value
// matches the alignment of |original_stack_bottom| to ensure that the stack
// contents have the same alignment as in the original stack. As a result the
// value will be different than |stack_buffer_bottom| if |original_stack_bottom|
// is not aligned to double the pointer width.
//
// NO HEAP ALLOCATIONS.
//
// static
NO_SANITIZE("address")
const uint8_t* CopyStackContentsAndRewritePointers(
const uint8_t* original_stack_bottom,
const uintptr_t* original_stack_top,
uintptr_t* stack_buffer_bottom) {
const uint8_t* byte_src = original_stack_bottom;
// The first address in the stack with pointer alignment. Pointer-aligned
// values from this point to the end of the stack are possibly rewritten using
// RewritePointerIfInOriginalStack(). Bytes before this cannot be a pointer
// because they occupy less space than a pointer would.
const uint8_t* first_aligned_address = reinterpret_cast<uint8_t*>(
(reinterpret_cast<uintptr_t>(byte_src) + sizeof(uintptr_t) - 1) &
~(sizeof(uintptr_t) - 1));
// The stack copy bottom, which is offset from |stack_buffer_bottom| by the
// same alignment as in the original stack. This guarantees identical
// alignment between values in the original stack and the copy. This uses the
// platform stack alignment rather than pointer alignment so that the stack
// copy is aligned to platform expectations.
uint8_t* stack_copy_bottom = reinterpret_cast<uint8_t*>(stack_buffer_bottom) +
(reinterpret_cast<uintptr_t>(byte_src) &
(StackBuffer::kPlatformStackAlignment - 1));
uint8_t* byte_dst = stack_copy_bottom;
// Copy bytes verbatim up to the first aligned address.
for (; byte_src < first_aligned_address; ++byte_src, ++byte_dst)
*byte_dst = *byte_src;
// Copy the remaining stack by pointer-sized values, rewriting anything that
// looks like a pointer into the stack.
const uintptr_t* src = reinterpret_cast<const uintptr_t*>(byte_src);
uintptr_t* dst = reinterpret_cast<uintptr_t*>(byte_dst);
for (; src < original_stack_top; ++src, ++dst) {
*dst = RewritePointerIfInOriginalStack(
original_stack_bottom, original_stack_top, stack_copy_bottom, *src);
}
return stack_copy_bottom;
}
} // namespace base } // namespace base
...@@ -61,19 +61,6 @@ class BASE_EXPORT StackSamplerImpl : public StackSampler { ...@@ -61,19 +61,6 @@ class BASE_EXPORT StackSamplerImpl : public StackSampler {
StackSamplerTestDelegate* const test_delegate_; StackSamplerTestDelegate* const test_delegate_;
}; };
// These two functions are exposed for testing.
BASE_EXPORT uintptr_t
RewritePointerIfInOriginalStack(const uint8_t* original_stack_bottom,
const uintptr_t* original_stack_top,
const uint8_t* stack_copy_bottom,
uintptr_t pointer);
BASE_EXPORT const uint8_t* CopyStackContentsAndRewritePointers(
const uint8_t* original_stack_bottom,
const uintptr_t* original_stack_top,
uintptr_t* stack_buffer_bottom);
} // namespace base } // namespace base
#endif // BASE_PROFILER_STACK_SAMPLER_IMPL_H_ #endif // BASE_PROFILER_STACK_SAMPLER_IMPL_H_
...@@ -228,208 +228,8 @@ class FakeTestUnwinder : public Unwinder { ...@@ -228,208 +228,8 @@ class FakeTestUnwinder : public Unwinder {
std::vector<Result> results_; std::vector<Result> results_;
}; };
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 } // namespace
TEST(StackSamplerImplTest, RewritePointerIfInOriginalStack_InStack) {
uintptr_t original_stack[4];
uintptr_t stack_copy[4];
EXPECT_EQ(reinterpret_cast<uintptr_t>(&stack_copy[2]),
RewritePointerIfInOriginalStack(
reinterpret_cast<uint8_t*>(&original_stack[0]),
&original_stack[0] + base::size(original_stack),
reinterpret_cast<uint8_t*>(&stack_copy[0]),
reinterpret_cast<uintptr_t>(&original_stack[2])));
}
TEST(StackSamplerImplTest, 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),
RewritePointerIfInOriginalStack(
reinterpret_cast<uint8_t*>(&original_stack[0]),
&original_stack[0] + size(original_stack),
reinterpret_cast<uint8_t*>(&stack_copy[0]),
reinterpret_cast<uintptr_t>(&non_stack_location)));
}
TEST(StackSamplerImplTest, 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] + 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;
CopyStackContentsAndRewritePointers(
&original_stack.as_uint8[0],
&original_stack.as_uintptr[0] + size(original_stack.as_uintptr),
&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(StackSamplerImplTest, 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] + 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[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 = CopyStackContentsAndRewritePointers(
unaligned_stack_bottom, stack_top, &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 =
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 < 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(StackSamplerImplTest,
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 = CopyStackContentsAndRewritePointers(
unaligned_stack_bottom,
&stack_buffer.as_uintptr[0] + size(stack_buffer.as_uintptr),
&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(StackSamplerImplTest,
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 = CopyStackContentsAndRewritePointers(
unaligned_stack_bottom,
&stack_buffer.as_uintptr[0] + size(stack_buffer.as_uintptr),
&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(StackSamplerImplTest, 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}};
CopyStackContentsAndRewritePointers(
unaligned_stack_bottom,
&stack_buffer.as_uintptr[0] + size(stack_buffer.as_uintptr),
&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]);
}
TEST(StackSamplerImplTest, CopyStack) { TEST(StackSamplerImplTest, CopyStack) {
ModuleCache module_cache; ModuleCache module_cache;
const std::vector<uintptr_t> stack = {0, 1, 2, 3, 4}; const std::vector<uintptr_t> stack = {0, 1, 2, 3, 4};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment