123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335 |
- // Copyright 2022 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/frame_pointer_unwinder.h"
- #include <memory>
- #include "base/profiler/module_cache.h"
- #include "base/profiler/stack_sampling_profiler_test_util.h"
- #include "base/profiler/unwinder.h"
- #include "build/buildflag.h"
- #include "testing/gtest/include/gtest/gtest.h"
- #if BUILDFLAG(IS_APPLE)
- #include "base/mac/mac_util.h"
- #endif
- namespace base {
- namespace {
- constexpr uintptr_t kModuleStart = 0x1000;
- constexpr size_t kModuleSize = 0x1000;
- constexpr uintptr_t kNonNativeModuleStart = 0x4000;
- // Used to construct test stacks. If `relative` is true, the value should be the
- // address `offset` positions from the bottom of the stack (at 8-byte alignment)
- // Otherwise, `offset` is added to the stack as an absolute address/value.
- // For example, when creating a stack with bottom 0x2000, {false, 0xf00d} will
- // become 0xf00d, and {true, 0x3} will become 0x2018.
- struct StackEntrySpec {
- bool relative;
- uintptr_t offset;
- };
- // Enables constructing a stack buffer that has pointers to itself
- // and provides convenience methods for calling the unwinder.
- struct InputStack {
- explicit InputStack(const std::vector<StackEntrySpec>& offsets)
- : buffer(offsets.size()) {
- size_t size = offsets.size();
- for (size_t i = 0; i < size; ++i) {
- auto spec = offsets[i];
- if (spec.relative) {
- buffer[i] = bottom() + (spec.offset * sizeof(uintptr_t));
- } else {
- buffer[i] = spec.offset;
- }
- }
- }
- uintptr_t bottom() const {
- return reinterpret_cast<uintptr_t>(buffer.data());
- }
- uintptr_t top() const { return bottom() + buffer.size() * sizeof(uintptr_t); }
- private:
- std::vector<uintptr_t> buffer;
- };
- } // namespace
- class FramePointerUnwinderTest : public testing::Test {
- protected:
- FramePointerUnwinderTest() {
- #if BUILDFLAG(IS_APPLE)
- if (__builtin_available(iOS 12, *)) {
- #else
- {
- #endif
- unwinder_ = std::make_unique<FramePointerUnwinder>();
- auto test_module =
- std::make_unique<TestModule>(kModuleStart, kModuleSize);
- module_ = test_module.get();
- module_cache_.AddCustomNativeModule(std::move(test_module));
- auto non_native_module = std::make_unique<TestModule>(
- kNonNativeModuleStart, kModuleSize, false);
- non_native_module_ = non_native_module.get();
- std::vector<std::unique_ptr<const ModuleCache::Module>> wrapper;
- wrapper.push_back(std::move(non_native_module));
- module_cache()->UpdateNonNativeModules({}, std::move(wrapper));
- unwinder_->Initialize(&module_cache_);
- }
- }
- ModuleCache* module_cache() { return &module_cache_; }
- ModuleCache::Module* module() { return module_; }
- ModuleCache::Module* non_native_module() { return non_native_module_; }
- Unwinder* unwinder() { return unwinder_.get(); }
- private:
- std::unique_ptr<Unwinder> unwinder_;
- base::ModuleCache module_cache_;
- raw_ptr<ModuleCache::Module> module_;
- raw_ptr<ModuleCache::Module> non_native_module_;
- };
- TEST_F(FramePointerUnwinderTest, FPPointsOutsideOfStack) {
- InputStack input({
- {false, 0x1000},
- {false, 0x1000},
- {false, 0x1000},
- {false, 0x1000},
- {false, 0x1000},
- });
- RegisterContext context;
- RegisterContextStackPointer(&context) = input.bottom();
- RegisterContextInstructionPointer(&context) = kModuleStart;
- RegisterContextFramePointer(&context) = 0x1;
- std::vector<Frame> stack = {
- Frame(RegisterContextInstructionPointer(&context), module())};
- EXPECT_EQ(UnwindResult::kAborted,
- unwinder()->TryUnwind(&context, input.top(), &stack));
- EXPECT_EQ(std::vector<Frame>({{kModuleStart, module()}}), stack);
- RegisterContextFramePointer(&context) = input.bottom() - sizeof(uintptr_t);
- EXPECT_EQ(UnwindResult::kAborted,
- unwinder()->TryUnwind(&context, input.top(), &stack));
- EXPECT_EQ(std::vector<Frame>({{kModuleStart, module()}}), stack);
- RegisterContextFramePointer(&context) = input.top();
- EXPECT_EQ(UnwindResult::kAborted,
- unwinder()->TryUnwind(&context, input.top(), &stack));
- EXPECT_EQ(std::vector<Frame>({{kModuleStart, module()}}), stack);
- }
- TEST_F(FramePointerUnwinderTest, FPPointsToSelf) {
- InputStack input({
- {true, 0},
- {false, kModuleStart + 0x10},
- {true, 4},
- {false, kModuleStart + 0x20},
- {false, 0},
- {false, 0},
- });
- RegisterContext context;
- RegisterContextStackPointer(&context) = input.bottom();
- RegisterContextInstructionPointer(&context) = kModuleStart;
- RegisterContextFramePointer(&context) = input.bottom();
- std::vector<Frame> stack = {
- Frame(RegisterContextInstructionPointer(&context), module())};
- EXPECT_EQ(UnwindResult::kAborted,
- unwinder()->TryUnwind(&context, input.top(), &stack));
- EXPECT_EQ(std::vector<Frame>({
- {kModuleStart, module()},
- }),
- stack);
- }
- // Tests that two frame pointers that point to each other can't create an
- // infinite loop
- TEST_F(FramePointerUnwinderTest, FPCycle) {
- InputStack input({
- {true, 2},
- {false, kModuleStart + 0x10},
- {true, 0},
- {false, kModuleStart + 0x20},
- {true, 4},
- {false, kModuleStart + 0x30},
- {false, 0},
- {false, 0},
- });
- RegisterContext context;
- RegisterContextStackPointer(&context) = input.bottom();
- RegisterContextInstructionPointer(&context) = kModuleStart;
- RegisterContextFramePointer(&context) = input.bottom();
- std::vector<Frame> stack = {
- Frame(RegisterContextInstructionPointer(&context), module())};
- EXPECT_EQ(UnwindResult::kAborted,
- unwinder()->TryUnwind(&context, input.top(), &stack));
- EXPECT_EQ(std::vector<Frame>({
- {kModuleStart, module()},
- {kModuleStart + 0x10, module()},
- }),
- stack);
- }
- TEST_F(FramePointerUnwinderTest, NoModuleForIP) {
- uintptr_t not_in_module = kModuleStart - 0x10;
- InputStack input({
- {true, 2},
- {false, not_in_module},
- {true, 4},
- {true, kModuleStart + 0x10},
- {false, 0},
- {false, 0},
- });
- RegisterContext context;
- RegisterContextStackPointer(&context) = input.bottom();
- RegisterContextInstructionPointer(&context) = kModuleStart;
- RegisterContextFramePointer(&context) = input.bottom();
- std::vector<Frame> stack = {
- Frame(RegisterContextInstructionPointer(&context), module())};
- EXPECT_EQ(UnwindResult::kAborted,
- unwinder()->TryUnwind(&context, input.top(), &stack));
- EXPECT_EQ(
- std::vector<Frame>({{kModuleStart, module()}, {not_in_module, nullptr}}),
- stack);
- }
- // Tests that testing that checking if there's space to read two values from the
- // stack doesn't overflow.
- TEST_F(FramePointerUnwinderTest, FPAdditionOverflows) {
- uintptr_t will_overflow = std::numeric_limits<uintptr_t>::max() - 1;
- InputStack input({
- {true, 2},
- {false, kModuleStart + 0x10},
- {false, 0},
- {false, 0},
- });
- RegisterContext context;
- RegisterContextStackPointer(&context) = input.bottom();
- RegisterContextInstructionPointer(&context) = kModuleStart;
- RegisterContextFramePointer(&context) = will_overflow;
- std::vector<Frame> stack = {
- Frame(RegisterContextInstructionPointer(&context), module())};
- EXPECT_EQ(UnwindResult::kAborted,
- unwinder()->TryUnwind(&context, input.top(), &stack));
- EXPECT_EQ(std::vector<Frame>({
- {kModuleStart, module()},
- }),
- stack);
- }
- // Tests the happy path: a successful unwind with no non-native modules.
- TEST_F(FramePointerUnwinderTest, RegularUnwind) {
- InputStack input({
- {true, 4}, // fp of frame 1
- {false, kModuleStart + 0x20}, // ip of frame 1
- {false, 0xaaaa},
- {false, 0xaaaa},
- {true, 8}, // fp of frame 2
- {false, kModuleStart + 0x42}, // ip of frame 2
- {false, 0xaaaa},
- {false, 0xaaaa},
- {false, 0},
- {false, 1},
- });
- RegisterContext context;
- RegisterContextStackPointer(&context) = input.bottom();
- RegisterContextInstructionPointer(&context) = kModuleStart;
- RegisterContextFramePointer(&context) = input.bottom();
- std::vector<Frame> stack = {
- Frame(RegisterContextInstructionPointer(&context), module())};
- EXPECT_EQ(UnwindResult::kCompleted,
- unwinder()->TryUnwind(&context, input.top(), &stack));
- EXPECT_EQ(std::vector<Frame>({
- {kModuleStart, module()},
- {kModuleStart + 0x20, module()},
- {kModuleStart + 0x42, module()},
- }),
- stack);
- }
- // Tests that if a V8 frame is encountered, unwinding stops and
- // kUnrecognizedFrame is returned to facilitate continuing with the V8 unwinder.
- TEST_F(FramePointerUnwinderTest, NonNativeFrame) {
- InputStack input({
- {true, 4}, // fp of frame 1
- {false, kModuleStart + 0x20}, // ip of frame 1
- {false, 0xaaaa},
- {false, 0xaaaa},
- {true, 8}, // fp of frame 2
- {false, kNonNativeModuleStart + 0x42}, // ip of frame 2
- {false, 0xaaaa},
- {false, 0xaaaa},
- {true, 12}, // fp of frame 3
- {false, kModuleStart + 0x10}, // ip of frame 3
- {true, 0xaaaa},
- {true, 0xaaaa},
- {false, 0},
- {false, 1},
- });
- RegisterContext context;
- RegisterContextStackPointer(&context) = input.bottom();
- RegisterContextInstructionPointer(&context) = kModuleStart;
- RegisterContextFramePointer(&context) = input.bottom();
- std::vector<Frame> stack = {
- Frame(RegisterContextInstructionPointer(&context), module())};
- EXPECT_EQ(UnwindResult::kUnrecognizedFrame,
- unwinder()->TryUnwind(&context, input.top(), &stack));
- EXPECT_EQ(std::vector<Frame>({
- {kModuleStart, module()},
- {kModuleStart + 0x20, module()},
- {kNonNativeModuleStart + 0x42, non_native_module()},
- }),
- stack);
- }
- // Tests that a V8 frame with an unaligned frame pointer correctly returns
- // kUnrecognizedFrame and not kAborted.
- TEST_F(FramePointerUnwinderTest, NonNativeUnaligned) {
- InputStack input({
- {true, 4}, // fp of frame 1
- {false, kModuleStart + 0x20}, // ip of frame 1
- {false, 0xaaaa},
- {false, 0xaaaa},
- {true, 7}, // fp of frame 2
- {false, kNonNativeModuleStart + 0x42}, // ip of frame 2
- {false, 0xaaaa},
- {true, 10}, // fp of frame 3
- {false, kModuleStart + 0x10}, // ip of frame 3
- {true, 0xaaaa},
- {false, 0},
- {false, 1},
- });
- RegisterContext context;
- RegisterContextStackPointer(&context) = input.bottom();
- RegisterContextInstructionPointer(&context) = kModuleStart;
- RegisterContextFramePointer(&context) = input.bottom();
- std::vector<Frame> stack = {
- Frame(RegisterContextInstructionPointer(&context), module())};
- EXPECT_EQ(UnwindResult::kUnrecognizedFrame,
- unwinder()->TryUnwind(&context, input.top(), &stack));
- }
- } // namespace base
|