blob: 17598c41e10ac81fa548e303e8118fff0723960d [file] [log] [blame]
// Copyright 2018 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 "components/tracing/common/stack_unwinder_android.h"
#include "base/android/jni_generator/jni_generator_helper.h"
#include "base/synchronization/waitable_event.h"
#include "base/task/post_task.h"
#include "base/test/scoped_task_environment.h"
#include "base/trace_event/cfi_backtrace_android.h"
#include "jni/UnwindTestHelper_jni.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace tracing {
namespace {
const size_t kMaxStackFrames = 40;
class StackUnwinderTest : public testing::Test {
public:
StackUnwinderTest() : testing::Test() {}
~StackUnwinderTest() override {}
void SetUp() override {
unwinder_.Initialize();
base::trace_event::CFIBacktraceAndroid::GetInitializedInstance()
->AllocateCacheForCurrentThread();
}
StackUnwinderAndroid* unwinder() { return &unwinder_; }
private:
StackUnwinderAndroid unwinder_;
base::test::ScopedTaskEnvironment scoped_task_environment_;
DISALLOW_COPY_AND_ASSIGN(StackUnwinderTest);
};
uintptr_t GetCurrentPC() {
return reinterpret_cast<uintptr_t>(__builtin_return_address(0));
}
} // namespace
TEST_F(StackUnwinderTest, UnwindCurrentThread) {
const void* frames[kMaxStackFrames];
size_t result = unwinder()->TraceStack(frames, kMaxStackFrames);
EXPECT_GT(result, 0u);
// Since we are starting from chrome library function (this), all the unwind
// frames will be chrome frames.
for (size_t i = 0; i < result; ++i) {
EXPECT_TRUE(
base::trace_event::CFIBacktraceAndroid::GetInitializedInstance()
->is_chrome_address(reinterpret_cast<uintptr_t>(frames[i])));
}
}
TEST_F(StackUnwinderTest, UnwindOtherThread) {
base::WaitableEvent unwind_finished_event;
auto task_runner = base::CreateSingleThreadTaskRunnerWithTraits(
{base::MayBlock(), base::TaskPriority::BEST_EFFORT});
auto callback = [](StackUnwinderAndroid* unwinder, base::PlatformThreadId tid,
base::WaitableEvent* unwind_finished_event,
uintptr_t test_pc) {
const void* frames[kMaxStackFrames];
auto stack_buffer = base::NativeStackSampler::CreateStackBuffer();
EXPECT_GT(stack_buffer->size(), 0u);
size_t result =
unwinder->TraceStack(tid, stack_buffer.get(), frames, kMaxStackFrames);
EXPECT_GT(result, 0u);
bool current_function_found = false;
for (size_t i = 0; i < result; ++i) {
uintptr_t addr = reinterpret_cast<uintptr_t>(frames[i]);
if (addr != 0)
EXPECT_TRUE(unwinder->IsAddressMapped(addr));
if (addr >= test_pc && addr < test_pc + 100)
current_function_found = true;
}
EXPECT_TRUE(current_function_found);
unwind_finished_event->Signal();
};
// Post task on background thread to unwind the current thread.
task_runner->PostTask(FROM_HERE,
base::BindOnce(callback, base::Unretained(unwinder()),
base::PlatformThread::CurrentId(),
&unwind_finished_event, GetCurrentPC()));
// While the background thread is trying to unwind make some slow framework
// calls (malloc) so that the current thread can be stopped in framework
// library functions on stack.
while (true) {
std::vector<int> temp;
temp.reserve(kMaxStackFrames);
usleep(100);
if (unwind_finished_event.IsSignaled())
break;
}
}
TEST_F(StackUnwinderTest, UnwindOtherThreadOnJNICall) {
auto task_runner = base::CreateSingleThreadTaskRunnerWithTraits(
{base::MayBlock(), base::TaskPriority::BEST_EFFORT});
auto callback = [](StackUnwinderAndroid* unwinder, base::PlatformThreadId tid,
uintptr_t test_pc) {
const void* frames[kMaxStackFrames];
auto stack_buffer = base::NativeStackSampler::CreateStackBuffer();
EXPECT_GT(stack_buffer->size(), 0u);
size_t result =
unwinder->TraceStack(tid, stack_buffer.get(), frames, kMaxStackFrames);
bool found_jni = false;
uintptr_t jni_address =
reinterpret_cast<uintptr_t>(&Java_UnwindTestHelper_blockCurrentThread);
EXPECT_GT(result, 2u);
for (size_t i = 0; i < result; ++i) {
uintptr_t addr = reinterpret_cast<uintptr_t>(frames[i]);
EXPECT_TRUE(unwinder->IsAddressMapped(addr));
// Check if address is near |jni_address|.
if (addr > jni_address && addr < jni_address + 50)
found_jni = true;
}
EXPECT_TRUE(found_jni);
JNIEnv* env = base::android::AttachCurrentThread();
Java_UnwindTestHelper_unblockAllThreads(env);
};
// Post task on background thread to unwind the current thread.
task_runner->PostDelayedTask(
FROM_HERE,
base::BindOnce(callback, base::Unretained(unwinder()),
base::PlatformThread::CurrentId(), GetCurrentPC()),
base::TimeDelta::FromMilliseconds(10));
JNIEnv* env = base::android::AttachCurrentThread();
Java_UnwindTestHelper_blockCurrentThread(env);
}
TEST_F(StackUnwinderTest, JNIMarkerConsistent) {
jni_generator::JniJavaCallContextUnchecked marker;
JNIEnv* env = base::android::AttachCurrentThread();
std::atomic<jmethodID> method(nullptr);
uintptr_t sp, pc;
asm volatile("mov %0, sp" : "=r"(sp));
asm volatile("mov %0, pc" : "=r"(pc));
marker.Init<base::android::MethodID::TYPE_STATIC>(
env, org_chromium_tracing_UnwindTestHelper_clazz(env),
"blockCurrentThread", "()V", &method);
EXPECT_EQ(marker.sp, sp);
// The |marker.pc| recorded should be close from where we recorded |pc|.
EXPECT_NEAR(marker.pc, pc, 50);
}
} // namespace tracing