| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/profiler/native_unwinder_android.h" |
| |
| #include <sys/mman.h> |
| |
| #include <inttypes.h> |
| #include <stdio.h> // For printf address. |
| #include <string.h> |
| #include <algorithm> |
| #include <iterator> |
| #include <vector> |
| |
| #include "base/android/build_info.h" |
| #include "base/android/jni_android.h" |
| #include "base/functional/bind.h" |
| #include "base/profiler/register_context.h" |
| #include "base/profiler/stack_buffer.h" |
| #include "base/profiler/stack_copier_signal.h" |
| #include "base/profiler/stack_sampler.h" |
| #include "base/profiler/stack_sampling_profiler_java_test_util.h" |
| #include "base/profiler/stack_sampling_profiler_test_util.h" |
| #include "base/profiler/thread_delegate_posix.h" |
| #include "base/test/bind.h" |
| #include "build/build_config.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| extern char __executable_start; |
| |
| namespace base { |
| |
| namespace { |
| |
| // Add a MapInfo with the provided values to |maps|. |
| void AddMapInfo(uint64_t start, |
| uint64_t end, |
| uint64_t offset, |
| uint64_t flags, |
| std::string name, |
| const std::string& binary_build_id, |
| unwindstack::Maps& maps) { |
| maps.Add(start, end, offset, flags, name, /* load_bias = */ 0u); |
| unwindstack::MapInfo& map_info = **std::prev(maps.end()); |
| map_info.SetBuildID(std::move(name)); |
| map_info.set_elf_offset(map_info.offset()); |
| } |
| |
| } // namespace |
| |
| class TestStackCopierDelegate : public StackCopier::Delegate { |
| public: |
| void OnStackCopy() override {} |
| }; |
| |
| std::vector<Frame> CaptureScenario( |
| UnwindScenario* scenario, |
| ModuleCache* module_cache, |
| OnceCallback<void(RegisterContext*, uintptr_t, std::vector<Frame>*)> |
| unwind_callback) { |
| std::vector<Frame> sample; |
| WithTargetThread( |
| scenario, |
| BindLambdaForTesting( |
| [&](SamplingProfilerThreadToken target_thread_token) { |
| auto thread_delegate = |
| ThreadDelegatePosix::Create(target_thread_token); |
| ASSERT_TRUE(thread_delegate); |
| auto stack_copier = |
| std::make_unique<StackCopierSignal>(std::move(thread_delegate)); |
| std::unique_ptr<StackBuffer> stack_buffer = |
| StackSampler::CreateStackBuffer(); |
| |
| RegisterContext thread_context; |
| uintptr_t stack_top; |
| TimeTicks timestamp; |
| TestStackCopierDelegate delegate; |
| bool success = |
| stack_copier->CopyStack(stack_buffer.get(), &stack_top, |
| ×tamp, &thread_context, &delegate); |
| ASSERT_TRUE(success); |
| |
| sample.emplace_back( |
| RegisterContextInstructionPointer(&thread_context), |
| module_cache->GetModuleForAddress( |
| RegisterContextInstructionPointer(&thread_context))); |
| |
| std::move(unwind_callback).Run(&thread_context, stack_top, &sample); |
| })); |
| |
| return sample; |
| } |
| |
| // Checks that the expected information is present in sampled frames. |
| // TODO(https://crbug.com/1147315): After fix, re-enable on all ASAN bots. |
| // TODO(https://crbug.com/1368981): After fix, re-enable on all bots except |
| // if defined(ADDRESS_SANITIZER). |
| TEST(NativeUnwinderAndroidTest, DISABLED_PlainFunction) { |
| UnwindScenario scenario(BindRepeating(&CallWithPlainFunction)); |
| |
| std::unique_ptr<unwindstack::Maps> maps = NativeUnwinderAndroid::CreateMaps(); |
| std::unique_ptr<unwindstack::Memory> memory = |
| NativeUnwinderAndroid::CreateProcessMemory(); |
| |
| ModuleCache module_cache; |
| auto unwinder = |
| std::make_unique<NativeUnwinderAndroid>(maps.get(), memory.get(), 0); |
| |
| unwinder->Initialize(&module_cache); |
| std::vector<Frame> sample = |
| CaptureScenario(&scenario, &module_cache, |
| BindLambdaForTesting([&](RegisterContext* thread_context, |
| uintptr_t stack_top, |
| std::vector<Frame>* sample) { |
| ASSERT_TRUE(unwinder->CanUnwindFrom(sample->back())); |
| UnwindResult result = unwinder->TryUnwind( |
| thread_context, stack_top, sample); |
| EXPECT_EQ(UnwindResult::kCompleted, result); |
| })); |
| |
| // Check that all the modules are valid. |
| for (const auto& frame : sample) |
| EXPECT_NE(nullptr, frame.module); |
| |
| // The stack should contain a full unwind. |
| ExpectStackContains(sample, {scenario.GetWaitForSampleAddressRange(), |
| scenario.GetSetupFunctionAddressRange(), |
| scenario.GetOuterFunctionAddressRange()}); |
| } |
| |
| // Checks that the unwinder handles stacks containing dynamically-allocated |
| // stack memory. |
| // TODO(https://crbug.com/1147315): After fix, re-enable on all ASAN bots. |
| // TODO(https://crbug.com/1368981): After fix, re-enable on all bots except |
| // if defined(ADDRESS_SANITIZER). |
| TEST(NativeUnwinderAndroidTest, DISABLED_Alloca) { |
| UnwindScenario scenario(BindRepeating(&CallWithAlloca)); |
| |
| std::unique_ptr<unwindstack::Maps> maps = NativeUnwinderAndroid::CreateMaps(); |
| std::unique_ptr<unwindstack::Memory> memory = |
| NativeUnwinderAndroid::CreateProcessMemory(); |
| |
| ModuleCache module_cache; |
| auto unwinder = |
| std::make_unique<NativeUnwinderAndroid>(maps.get(), memory.get(), 0); |
| |
| unwinder->Initialize(&module_cache); |
| std::vector<Frame> sample = |
| CaptureScenario(&scenario, &module_cache, |
| BindLambdaForTesting([&](RegisterContext* thread_context, |
| uintptr_t stack_top, |
| std::vector<Frame>* sample) { |
| ASSERT_TRUE(unwinder->CanUnwindFrom(sample->back())); |
| UnwindResult result = unwinder->TryUnwind( |
| thread_context, stack_top, sample); |
| EXPECT_EQ(UnwindResult::kCompleted, result); |
| })); |
| |
| // Check that all the modules are valid. |
| for (const auto& frame : sample) |
| EXPECT_NE(nullptr, frame.module); |
| |
| // The stack should contain a full unwind. |
| ExpectStackContains(sample, {scenario.GetWaitForSampleAddressRange(), |
| scenario.GetSetupFunctionAddressRange(), |
| scenario.GetOuterFunctionAddressRange()}); |
| } |
| |
| // Checks that a stack that runs through another library produces a stack with |
| // the expected functions. |
| // TODO(https://crbug.com/1147315): After fix, re-enable on all ASAN bots. |
| // TODO(https://crbug.com/1368981): After fix, re-enable on all bots except |
| // if defined(ADDRESS_SANITIZER). |
| TEST(NativeUnwinderAndroidTest, DISABLED_OtherLibrary) { |
| NativeLibrary other_library = LoadOtherLibrary(); |
| UnwindScenario scenario( |
| BindRepeating(&CallThroughOtherLibrary, Unretained(other_library))); |
| |
| std::unique_ptr<unwindstack::Maps> maps = NativeUnwinderAndroid::CreateMaps(); |
| std::unique_ptr<unwindstack::Memory> memory = |
| NativeUnwinderAndroid::CreateProcessMemory(); |
| |
| ModuleCache module_cache; |
| auto unwinder = |
| std::make_unique<NativeUnwinderAndroid>(maps.get(), memory.get(), 0); |
| |
| unwinder->Initialize(&module_cache); |
| std::vector<Frame> sample = |
| CaptureScenario(&scenario, &module_cache, |
| BindLambdaForTesting([&](RegisterContext* thread_context, |
| uintptr_t stack_top, |
| std::vector<Frame>* sample) { |
| ASSERT_TRUE(unwinder->CanUnwindFrom(sample->back())); |
| UnwindResult result = unwinder->TryUnwind( |
| thread_context, stack_top, sample); |
| EXPECT_EQ(UnwindResult::kCompleted, result); |
| })); |
| |
| // The stack should contain a full unwind. |
| ExpectStackContains(sample, {scenario.GetWaitForSampleAddressRange(), |
| scenario.GetSetupFunctionAddressRange(), |
| scenario.GetOuterFunctionAddressRange()}); |
| } |
| |
| // Check that unwinding is interrupted for excluded modules. |
| TEST(NativeUnwinderAndroidTest, ExcludeOtherLibrary) { |
| NativeLibrary other_library = LoadOtherLibrary(); |
| UnwindScenario scenario( |
| BindRepeating(&CallThroughOtherLibrary, Unretained(other_library))); |
| |
| std::unique_ptr<unwindstack::Maps> maps = NativeUnwinderAndroid::CreateMaps(); |
| std::unique_ptr<unwindstack::Memory> memory = |
| NativeUnwinderAndroid::CreateProcessMemory(); |
| ModuleCache module_cache; |
| unwindstack::MapInfo* other_library_map = |
| maps->Find(GetAddressInOtherLibrary(other_library)).get(); |
| ASSERT_NE(nullptr, other_library_map); |
| auto unwinder = std::make_unique<NativeUnwinderAndroid>( |
| maps.get(), memory.get(), other_library_map->start()); |
| unwinder->Initialize(&module_cache); |
| |
| std::vector<Frame> sample = |
| CaptureScenario(&scenario, &module_cache, |
| BindLambdaForTesting([&](RegisterContext* thread_context, |
| uintptr_t stack_top, |
| std::vector<Frame>* sample) { |
| ASSERT_TRUE(unwinder->CanUnwindFrom(sample->back())); |
| EXPECT_EQ(UnwindResult::kUnrecognizedFrame, |
| unwinder->TryUnwind(thread_context, stack_top, |
| sample)); |
| EXPECT_FALSE(unwinder->CanUnwindFrom(sample->back())); |
| })); |
| |
| ExpectStackContains(sample, {scenario.GetWaitForSampleAddressRange()}); |
| ExpectStackDoesNotContain(sample, {scenario.GetSetupFunctionAddressRange(), |
| scenario.GetOuterFunctionAddressRange()}); |
| } |
| |
| // Check that unwinding can be resumed after an incomplete unwind. |
| #if defined(ADDRESS_SANITIZER) |
| // TODO(https://crbug.com/1147315): Fix, re-enable. |
| #define MAYBE_ResumeUnwinding DISABLED_ResumeUnwinding |
| #else |
| #define MAYBE_ResumeUnwinding ResumeUnwinding |
| #endif |
| TEST(NativeUnwinderAndroidTest, MAYBE_ResumeUnwinding) { |
| NativeLibrary other_library = LoadOtherLibrary(); |
| UnwindScenario scenario( |
| BindRepeating(&CallThroughOtherLibrary, Unretained(other_library))); |
| |
| std::unique_ptr<unwindstack::Maps> maps = NativeUnwinderAndroid::CreateMaps(); |
| std::unique_ptr<unwindstack::Memory> memory = |
| NativeUnwinderAndroid::CreateProcessMemory(); |
| |
| // Several unwinders are used to unwind different portion of the stack. Since |
| // only 1 unwinder can be registered as a module provider, each unwinder uses |
| // a distinct ModuleCache. This tests that NativeUnwinderAndroid can pick up |
| // from a state in the middle of the stack. This emulates having |
| // NativeUnwinderAndroid work with other unwinders, but doesn't reproduce what |
| // happens in production. |
| ModuleCache module_cache_for_all; |
| auto unwinder_for_all = |
| std::make_unique<NativeUnwinderAndroid>(maps.get(), memory.get(), 0); |
| unwinder_for_all->Initialize(&module_cache_for_all); |
| |
| ModuleCache module_cache_for_native; |
| auto unwinder_for_native = std::make_unique<NativeUnwinderAndroid>( |
| maps.get(), memory.get(), |
| reinterpret_cast<uintptr_t>(&__executable_start)); |
| unwinder_for_native->Initialize(&module_cache_for_native); |
| |
| ModuleCache module_cache_for_chrome; |
| unwindstack::MapInfo* other_library_map = |
| maps->Find(GetAddressInOtherLibrary(other_library)).get(); |
| ASSERT_NE(nullptr, other_library_map); |
| auto unwinder_for_chrome = std::make_unique<NativeUnwinderAndroid>( |
| maps.get(), memory.get(), other_library_map->start()); |
| unwinder_for_chrome->Initialize(&module_cache_for_chrome); |
| |
| std::vector<Frame> sample = CaptureScenario( |
| &scenario, &module_cache_for_native, |
| BindLambdaForTesting([&](RegisterContext* thread_context, |
| uintptr_t stack_top, |
| std::vector<Frame>* sample) { |
| // |unwinder_for_native| unwinds through native frames, but stops at |
| // chrome frames. It might not contain SampleAddressRange. |
| ASSERT_TRUE(unwinder_for_native->CanUnwindFrom(sample->back())); |
| EXPECT_EQ( |
| UnwindResult::kUnrecognizedFrame, |
| unwinder_for_native->TryUnwind(thread_context, stack_top, sample)); |
| EXPECT_FALSE(unwinder_for_native->CanUnwindFrom(sample->back())); |
| |
| ExpectStackDoesNotContain(*sample, |
| {scenario.GetSetupFunctionAddressRange(), |
| scenario.GetOuterFunctionAddressRange()}); |
| size_t prior_stack_size = sample->size(); |
| |
| // |unwinder_for_chrome| unwinds through Chrome frames, but stops at |
| // |other_library|. It won't contain SetupFunctionAddressRange. |
| ASSERT_TRUE(unwinder_for_chrome->CanUnwindFrom(sample->back())); |
| EXPECT_EQ( |
| UnwindResult::kUnrecognizedFrame, |
| unwinder_for_chrome->TryUnwind(thread_context, stack_top, sample)); |
| EXPECT_FALSE(unwinder_for_chrome->CanUnwindFrom(sample->back())); |
| EXPECT_LT(prior_stack_size, sample->size()); |
| ExpectStackContains(*sample, {scenario.GetWaitForSampleAddressRange()}); |
| ExpectStackDoesNotContain(*sample, |
| {scenario.GetSetupFunctionAddressRange(), |
| scenario.GetOuterFunctionAddressRange()}); |
| |
| // |unwinder_for_all| should complete unwinding through all frames. |
| ASSERT_TRUE(unwinder_for_all->CanUnwindFrom(sample->back())); |
| EXPECT_EQ( |
| UnwindResult::kCompleted, |
| unwinder_for_all->TryUnwind(thread_context, stack_top, sample)); |
| })); |
| |
| // The stack should contain a full unwind. |
| ExpectStackContains(sample, {scenario.GetWaitForSampleAddressRange(), |
| scenario.GetSetupFunctionAddressRange(), |
| scenario.GetOuterFunctionAddressRange()}); |
| } |
| |
| // Checks that java frames can be unwound through. |
| // Disabled, see: https://crbug.com/1076997 |
| TEST(NativeUnwinderAndroidTest, DISABLED_JavaFunction) { |
| auto* build_info = base::android::BuildInfo::GetInstance(); |
| // Due to varying availability of compiled java unwind tables, unwinding is |
| // only expected to succeed on > SDK_VERSION_MARSHMALLOW. |
| bool can_always_unwind = |
| build_info->sdk_int() > base::android::SDK_VERSION_MARSHMALLOW; |
| |
| UnwindScenario scenario(base::BindRepeating(callWithJavaFunction)); |
| |
| std::unique_ptr<unwindstack::Maps> maps = NativeUnwinderAndroid::CreateMaps(); |
| std::unique_ptr<unwindstack::Memory> memory = |
| NativeUnwinderAndroid::CreateProcessMemory(); |
| auto unwinder = |
| std::make_unique<NativeUnwinderAndroid>(maps.get(), memory.get(), 0); |
| |
| ModuleCache module_cache; |
| unwinder->Initialize(&module_cache); |
| std::vector<Frame> sample = |
| CaptureScenario(&scenario, &module_cache, |
| BindLambdaForTesting([&](RegisterContext* thread_context, |
| uintptr_t stack_top, |
| std::vector<Frame>* sample) { |
| ASSERT_TRUE(unwinder->CanUnwindFrom(sample->back())); |
| UnwindResult result = unwinder->TryUnwind( |
| thread_context, stack_top, sample); |
| if (can_always_unwind) |
| EXPECT_EQ(UnwindResult::kCompleted, result); |
| })); |
| |
| // Check that all the modules are valid. |
| for (const auto& frame : sample) |
| EXPECT_NE(nullptr, frame.module); |
| |
| // The stack should contain a full unwind. |
| if (can_always_unwind) { |
| ExpectStackContains(sample, {scenario.GetWaitForSampleAddressRange(), |
| scenario.GetSetupFunctionAddressRange(), |
| scenario.GetOuterFunctionAddressRange()}); |
| } |
| } |
| |
| TEST(NativeUnwinderAndroidTest, UnwindStackMemoryTest) { |
| std::vector<uint8_t> input = {1, 2, 3, 4, 5}; |
| uintptr_t begin = reinterpret_cast<uintptr_t>(input.data()); |
| uintptr_t end = reinterpret_cast<uintptr_t>(input.data() + input.size()); |
| UnwindStackMemoryAndroid memory(begin, end); |
| |
| const auto check_read_fails = [&](uintptr_t addr, size_t size) { |
| std::vector<uint8_t> output(size); |
| EXPECT_EQ(0U, memory.Read(addr, output.data(), size)); |
| }; |
| const auto check_read_succeeds = [&](uintptr_t addr, size_t size) { |
| std::vector<uint8_t> output(size); |
| EXPECT_EQ(size, memory.Read(addr, output.data(), size)); |
| EXPECT_EQ( |
| 0, memcmp(reinterpret_cast<const uint8_t*>(addr), output.data(), size)); |
| }; |
| |
| check_read_fails(begin - 1, 1); |
| check_read_fails(begin - 1, 2); |
| check_read_fails(end, 1); |
| check_read_fails(end, 2); |
| check_read_fails(end - 1, 2); |
| |
| check_read_succeeds(begin, 1); |
| check_read_succeeds(begin, 5); |
| check_read_succeeds(end - 1, 1); |
| } |
| |
| // Checks the debug basename is the whole name for a non-ELF module. |
| TEST(NativeUnwinderAndroidTest, ModuleDebugBasenameForNonElf) { |
| unwindstack::Maps maps; |
| |
| AddMapInfo(0x1000u, 0x2000u, 0u, PROT_READ | PROT_EXEC, "[foo / bar]", {0xAA}, |
| maps); |
| |
| ModuleCache module_cache; |
| |
| std::unique_ptr<unwindstack::Memory> memory = |
| NativeUnwinderAndroid::CreateProcessMemory(); |
| auto unwinder = |
| std::make_unique<NativeUnwinderAndroid>(&maps, memory.get(), 0); |
| unwinder->Initialize(&module_cache); |
| |
| const ModuleCache::Module* module = module_cache.GetModuleForAddress(0x1000u); |
| |
| ASSERT_TRUE(module); |
| EXPECT_EQ("[foo / bar]", module->GetDebugBasename().value()); |
| } |
| |
| // Checks that modules are only created for executable memory regions. |
| TEST(NativeUnwinderAndroidTest, ModulesCreatedOnlyForExecutableRegions) { |
| unwindstack::Maps maps; |
| AddMapInfo(0x1000u, 0x2000u, 0u, PROT_READ | PROT_EXEC, "[a]", {0xAA}, maps); |
| AddMapInfo(0x2000u, 0x3000u, 0u, PROT_READ, "[b]", {0xAB}, maps); |
| AddMapInfo(0x3000u, 0x4000u, 0u, PROT_READ | PROT_EXEC, "[c]", {0xAC}, maps); |
| |
| std::unique_ptr<unwindstack::Memory> memory = |
| NativeUnwinderAndroid::CreateProcessMemory(); |
| |
| ModuleCache module_cache; |
| auto unwinder = |
| std::make_unique<NativeUnwinderAndroid>(&maps, memory.get(), 0); |
| unwinder->Initialize(&module_cache); |
| |
| const ModuleCache::Module* module1 = |
| module_cache.GetModuleForAddress(0x1000u); |
| const ModuleCache::Module* module2 = |
| module_cache.GetModuleForAddress(0x2000u); |
| const ModuleCache::Module* module3 = |
| module_cache.GetModuleForAddress(0x3000u); |
| |
| ASSERT_TRUE(module1); |
| EXPECT_EQ(0x1000u, module1->GetBaseAddress()); |
| EXPECT_EQ(nullptr, module2); |
| ASSERT_TRUE(module3); |
| EXPECT_EQ(0x3000u, module3->GetBaseAddress()); |
| } |
| |
| } // namespace base |