// 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/chrome_unwinder_android.h" #include "base/android/library_loader/anchor_functions.h" #include "base/files/file_util.h" #include "base/profiler/profile_builder.h" #include "base/profiler/stack_buffer.h" #include "base/profiler/stack_copier_signal.h" #include "base/profiler/stack_sampling_profiler_test_util.h" #include "base/profiler/thread_delegate_posix.h" #include "base/test/gtest_util.h" #include "build/build_config.h" #include "testing/gtest/include/gtest/gtest.h" namespace base { namespace { // Input is generated from the CFI file: // STACK CFI INIT 100 100 // STACK CFI 1010 .cfa: sp 8 + .ra: .cfa -4 + ^ const uint16_t cfi_data[] = { // UNW_INDEX size 0x02, 0x0, // UNW_INDEX function_addresses (4 byte rows). 0x100, 0x0, 0x200, 0x0, // UNW_INDEX entry_data_indices (2 byte rows). 0x0, 0xffff, // UNW_DATA table. 0x1, 0x10, 0x9, }; // Utility function to add a single native module during test setup. Returns // a pointer to the provided module. const ModuleCache::Module* AddNativeModule( ModuleCache* cache, std::unique_ptr module) { const ModuleCache::Module* module_ptr = module.get(); cache->AddCustomNativeModule(std::move(module)); return module_ptr; } ArmCFITable::FrameEntry MakeFrameEntry(uint16_t cfa_offset, uint16_t ra_offset) { return ArmCFITable::FrameEntry{ static_cast(cfa_offset * sizeof(uintptr_t)), static_cast(ra_offset * sizeof(uintptr_t))}; } } // namespace // Tests unwind step under normal operation. TEST(ChromeUnwinderAndroidTest, Step) { const std::vector stack_buffer = { 0xFFFF, 0xFFFF, 0xFFFF, 0x1111, 0xFFFF, 0x2222, }; const uintptr_t stack_top = reinterpret_cast(stack_buffer.data() + stack_buffer.size()); RegisterContext context; RegisterContextInstructionPointer(&context) = 0xBEEF; RegisterContextStackPointer(&context) = reinterpret_cast(stack_buffer.data()); EXPECT_TRUE(ChromeUnwinderAndroid::StepForTesting(&context, stack_top, MakeFrameEntry(4, 1))); EXPECT_EQ(RegisterContextInstructionPointer(&context), 0x1111U); EXPECT_EQ(RegisterContextStackPointer(&context), reinterpret_cast(stack_buffer.data() + 4)); EXPECT_TRUE(ChromeUnwinderAndroid::StepForTesting(&context, stack_top, MakeFrameEntry(1, 0))); EXPECT_EQ(RegisterContextInstructionPointer(&context), 0x2222U); EXPECT_EQ(RegisterContextStackPointer(&context), reinterpret_cast(stack_buffer.data() + 5)); } // Tests unwind step using immediate return address. TEST(ChromeUnwinderAndroidTest, StepImmediate) { const std::vector stack_buffer = {0xFFFF, 0xFFFF}; const uintptr_t stack_top = reinterpret_cast(stack_buffer.data() + stack_buffer.size()); RegisterContext context; RegisterContextInstructionPointer(&context) = 0xBEEF; RegisterContextStackPointer(&context) = reinterpret_cast(stack_buffer.data()); context.arm_lr = 0x4444; EXPECT_TRUE(ChromeUnwinderAndroid::StepForTesting(&context, stack_top, MakeFrameEntry(0, 0))); EXPECT_EQ(RegisterContextInstructionPointer(&context), 0x4444U); EXPECT_EQ(RegisterContextStackPointer(&context), reinterpret_cast(stack_buffer.data())); } // Tests that unwinding fails if immediate return address is the current // instruction. TEST(ChromeUnwinderAndroidTest, StepImmediateFail) { const std::vector stack_buffer = {0xFFFF, 0xFFFF}; const uintptr_t stack_top = reinterpret_cast(stack_buffer.data() + stack_buffer.size()); RegisterContext context; RegisterContextInstructionPointer(&context) = 0x1111; RegisterContextStackPointer(&context) = reinterpret_cast(stack_buffer.data()); context.arm_lr = 0x1111; EXPECT_FALSE(ChromeUnwinderAndroid::StepForTesting(&context, stack_top, MakeFrameEntry(0, 0))); } // Tests that unwinding fails if stack is invalid. TEST(ChromeUnwinderAndroidTest, StepInvalidStack) { const std::vector stack_buffer = {0xFFFF}; const uintptr_t stack_top = reinterpret_cast(stack_buffer.data() + stack_buffer.size()); RegisterContext context; EXPECT_CHECK_DEATH({ RegisterContextStackPointer(&context) = 0; ChromeUnwinderAndroid::StepForTesting(&context, stack_top, MakeFrameEntry(1, 0)); }); EXPECT_CHECK_DEATH({ RegisterContextStackPointer(&context) = reinterpret_cast( stack_buffer.data() + stack_buffer.size() + 1); ChromeUnwinderAndroid::StepForTesting(&context, stack_top, MakeFrameEntry(1, 0)); }); } // Tests that unwinding fails if the frame entry is out of bounds. TEST(ChromeUnwinderAndroidTest, StepOutOfBounds) { RegisterContext context; RegisterContextInstructionPointer(&context) = 0xBEEF; constexpr uintptr_t kOverflowOffset = 8; constexpr size_t kStackSize = 4; // It's fine to use a fake stack pointer since the stack won't be // dereferenced. Purposely underflow so that sp + |kOverflowOffset| overflows. RegisterContextStackPointer(&context) = 0 - kOverflowOffset; const uintptr_t stack_top = RegisterContextStackPointer(&context) + kStackSize; // ra_offset exceeds cfa_offset. EXPECT_FALSE(ChromeUnwinderAndroid::StepForTesting(&context, stack_top, MakeFrameEntry(1, 2))); EXPECT_FALSE(ChromeUnwinderAndroid::StepForTesting( &context, stack_top, MakeFrameEntry(1, kOverflowOffset))); // cfa_offset exceeds |stack_top|. EXPECT_FALSE(ChromeUnwinderAndroid::StepForTesting( &context, stack_top, MakeFrameEntry(kStackSize, 0))); // sp + cfa_offset overflows. EXPECT_FALSE(ChromeUnwinderAndroid::StepForTesting( &context, stack_top, MakeFrameEntry(kOverflowOffset, 0))); } TEST(ChromeUnwinderAndroidTest, StepUnderflows) { RegisterContext context; RegisterContextInstructionPointer(&context) = 0xBEEF; // It's fine to use a fake stack pointer since the stack won't be // dereferenced. RegisterContextStackPointer(&context) = 2; const uintptr_t stack_top = RegisterContextStackPointer(&context) + 4; // sp + cfa_offset - ra_offset underflows. EXPECT_FALSE(ChromeUnwinderAndroid::StepForTesting(&context, stack_top, MakeFrameEntry(1, 4))); } TEST(ChromeUnwinderAndroidTest, CanUnwindFrom) { auto cfi_table = ArmCFITable::Parse( {reinterpret_cast(cfi_data), sizeof(cfi_data)}); auto chrome_module = std::make_unique(0x1000, 0x500); auto non_chrome_module = std::make_unique(0x2000, 0x500); ModuleCache module_cache; ChromeUnwinderAndroid unwinder(cfi_table.get(), chrome_module->GetBaseAddress()); unwinder.Initialize(&module_cache); EXPECT_TRUE(unwinder.CanUnwindFrom({0x1100, chrome_module.get()})); EXPECT_FALSE(unwinder.CanUnwindFrom({0x2100, non_chrome_module.get()})); } TEST(ChromeUnwinderAndroidTest, TryUnwind) { auto cfi_table = ArmCFITable::Parse( {reinterpret_cast(cfi_data), sizeof(cfi_data)}); ModuleCache module_cache; const ModuleCache::Module* chrome_module = AddNativeModule( &module_cache, std::make_unique(0x1000, 0x500)); ChromeUnwinderAndroid unwinder(cfi_table.get(), chrome_module->GetBaseAddress()); unwinder.Initialize(&module_cache); std::vector stack_buffer = { 0xFFFF, // .cfa: sp 8 + .ra: .cfa -4 + ^ 0x2000, 0xFFFF, }; uintptr_t stack_top = reinterpret_cast(stack_buffer.data() + stack_buffer.size()); std::vector stack; stack.emplace_back(0x1100, chrome_module); RegisterContext context; RegisterContextInstructionPointer(&context) = 0x1100; RegisterContextStackPointer(&context) = reinterpret_cast(stack_buffer.data()); context.arm_lr = 0x11AA; EXPECT_EQ(UnwindResult::kUnrecognizedFrame, unwinder.TryUnwind(&context, stack_top, &stack)); EXPECT_EQ(std::vector({{0x1100, chrome_module}, {0x11AA, chrome_module}, {0x2000, nullptr}}), stack); } TEST(ChromeUnwinderAndroidTest, TryUnwindAbort) { auto cfi_table = ArmCFITable::Parse( {reinterpret_cast(cfi_data), sizeof(cfi_data)}); ASSERT_TRUE(cfi_table); ModuleCache module_cache; const ModuleCache::Module* chrome_module = AddNativeModule( &module_cache, std::make_unique(0x1000, 0x500)); ChromeUnwinderAndroid unwinder(cfi_table.get(), chrome_module->GetBaseAddress()); unwinder.Initialize(&module_cache); std::vector stack_buffer = { 0xFFFF, }; uintptr_t stack_top = reinterpret_cast(stack_buffer.data() + stack_buffer.size()); std::vector stack; stack.emplace_back(0x1100, chrome_module); RegisterContext context; RegisterContextInstructionPointer(&context) = 0x1100; RegisterContextStackPointer(&context) = reinterpret_cast(stack_buffer.data()); context.arm_lr = 0x1100; // Aborted because ra == pc. EXPECT_EQ(UnwindResult::kAborted, unwinder.TryUnwind(&context, stack_top, &stack)); EXPECT_EQ(std::vector({{0x1100, chrome_module}}), stack); } TEST(ChromeUnwinderAndroidTest, TryUnwindNoData) { auto cfi_table = ArmCFITable::Parse( {reinterpret_cast(cfi_data), sizeof(cfi_data)}); ModuleCache module_cache; const ModuleCache::Module* chrome_module = AddNativeModule( &module_cache, std::make_unique(0x1000, 0x500)); ChromeUnwinderAndroid unwinder(cfi_table.get(), chrome_module->GetBaseAddress()); unwinder.Initialize(&module_cache); std::vector stack_buffer = {0xFFFF}; uintptr_t stack_top = reinterpret_cast(stack_buffer.data() + stack_buffer.size()); std::vector stack; stack.emplace_back(0x1200, chrome_module); RegisterContext context; RegisterContextInstructionPointer(&context) = 0xBEEF; RegisterContextStackPointer(&context) = reinterpret_cast(stack_buffer.data()); context.arm_lr = 0x12AA; // Unwinding will first use arm_lr as fallback because there's no unwind info // for the instruction pointer, and then abort. EXPECT_EQ(UnwindResult::kAborted, unwinder.TryUnwind(&context, stack_top, &stack)); EXPECT_EQ( std::vector({{0x1200, chrome_module}, {0x12AA, chrome_module}}), stack); } } // namespace base