Skip to content

Commit

Permalink
[Clank SSM]: Implement NativeUnwinderAndroid.
Browse files Browse the repository at this point in the history
This CL implements NativeUnwinderAndroid & tests for android
unwinding support. It also enables StackSamplingProfilerTest
on Android.

A new target source_set is added, native_unwinder_android
that contains NativeUnwinderAndroid.
StackSamplingProfilerTest depends on it for android.

Bug: 989102
Change-Id: Ie38fd99ca5fb053e1881d0977924b70a6fbc1e9b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2055743
Commit-Queue: Etienne Pierre-Doray <etiennep@chromium.org>
Reviewed-by: Nico Weber <thakis@chromium.org>
Reviewed-by: Mike Wittman <wittman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#759283}
  • Loading branch information
Etienne Pierre-doray authored and Commit Bot committed Apr 15, 2020
1 parent 4770e1d commit 38e3a62
Show file tree
Hide file tree
Showing 12 changed files with 835 additions and 95 deletions.
50 changes: 45 additions & 5 deletions base/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -1213,8 +1213,6 @@ jumbo_component("base") {
"message_loop/message_pump_android.h",
"os_compat_android.cc",
"os_compat_android.h",
"profiler/native_unwinder_android.cc",
"profiler/native_unwinder_android.h",
"profiler/stack_sampler_android.cc",
"threading/platform_thread_android.cc",
"trace_event/cpufreq_monitor_android.cc",
Expand Down Expand Up @@ -2386,16 +2384,34 @@ if (is_win) {
}
}

if (is_win || is_mac) {
if (current_cpu == "x64" || (current_cpu == "arm64" && is_win)) {
# Must be a shared library so that it can be unloaded during testing.
if (is_win || is_mac || is_android) {
if (current_cpu == "x64" || (current_cpu == "arm64" && is_win) ||
is_android) {
# Must be a loadable module so that it can be loaded/unloaded at runtime
# during testing.
loadable_module("base_profiler_test_support_library") {
testonly = true
sources = [ "profiler/test_support_library.cc" ]
}
}
}

if (is_android) {
source_set("native_unwinder_android") {
sources = [
"profiler/native_unwinder_android.cc",
"profiler/native_unwinder_android.h",
"profiler/unwindstack_internal_android.cc",
"profiler/unwindstack_internal_android.h",
]

include_dirs = [ "//third_party/libunwindstack/src/libunwindstack/include" ]

public_deps = [ ":base" ]
deps = [ "//third_party/libunwindstack" ]
}
}

source_set("base_stack_sampling_profiler_test_util") {
testonly = true
sources = [
Expand Down Expand Up @@ -2951,6 +2967,7 @@ test("base_unittests") {
"android/sys_utils_unittest.cc",
"android/unguessable_token_android_unittest.cc",
"os_compat_android_unittest.cc",
"profiler/native_unwinder_android_unittest.cc",
"trace_event/cpufreq_monitor_android_unittest.cc",
"trace_event/java_heap_dump_provider_android_unittest.cc",
]
Expand All @@ -2964,8 +2981,13 @@ test("base_unittests") {
deps += [
":base_java",
":base_java_unittest_support",
":base_profiler_test_support_java",
":base_profiler_test_support_jni_headers",
":base_profiler_test_support_library",
":native_unwinder_android",
"//base/test:test_support_java",
]
include_dirs = [ "//third_party/libunwindstack/src/libunwindstack/include" ]
}

if (icu_use_data_file) {
Expand Down Expand Up @@ -3660,6 +3682,24 @@ if (is_android) {
]
}

generate_jni("base_profiler_test_support_jni_headers") {
testonly = true
sources =
[ "android/javatests/src/org/chromium/base/profiler/TestSupport.java" ]
}

android_library("base_profiler_test_support_java") {
testonly = true
sources =
[ "android/javatests/src/org/chromium/base/profiler/TestSupport.java" ]

annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ]
deps = [
"//base:base_java",
"//base:jni_java",
]
}

generate_build_config_srcjar("base_build_config_gen") {
use_final_fields = false
}
Expand Down
1 change: 1 addition & 0 deletions base/DEPS
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ include_rules = [
"+third_party/lss",
"+third_party/modp_b64",
"+third_party/tcmalloc",
"+third_party/libunwindstack/src/libunwindstack/include",

# These are implicitly brought in from the root, and we don't want them.
"-ipc",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright 2020 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.

package org.chromium.base.profiler;

import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.NativeMethods;

/**
* Helper to run code through JNI layer to test JNI unwinding.
*/
@JNINamespace("base")
public final class TestSupport {
@CalledByNative
public static void callWithJavaFunction(long context) {
TestSupportJni.get().invokeCallbackFunction(context);
}

@NativeMethods
interface Natives {
void invokeCallbackFunction(long context);
}
}
208 changes: 204 additions & 4 deletions base/profiler/native_unwinder_android.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,225 @@

#include "base/profiler/native_unwinder_android.h"

#include <string>
#include <vector>

#include <sys/mman.h>

#include "third_party/libunwindstack/src/libunwindstack/include/unwindstack/Elf.h"
#include "third_party/libunwindstack/src/libunwindstack/include/unwindstack/Maps.h"
#include "third_party/libunwindstack/src/libunwindstack/include/unwindstack/Memory.h"

#include "base/memory/ptr_util.h"
#include "base/profiler/module_cache.h"
#include "base/profiler/native_unwinder.h"
#include "base/profiler/profile_builder.h"
#include "base/profiler/unwindstack_internal_android.h"
#include "build/build_config.h"

#if defined(ARCH_CPU_ARM_FAMILY) && defined(ARCH_CPU_32_BITS)
#include "third_party/libunwindstack/src/libunwindstack/include/unwindstack/MachineArm.h"
#include "third_party/libunwindstack/src/libunwindstack/include/unwindstack/RegsArm.h"
#elif defined(ARCH_CPU_ARM_FAMILY) && defined(ARCH_CPU_64_BITS)
#include "third_party/libunwindstack/src/libunwindstack/include/unwindstack/MachineArm64.h"
#include "third_party/libunwindstack/src/libunwindstack/include/unwindstack/RegsArm64.h"
#endif // #if defined(ARCH_CPU_ARM_FAMILY) && defined(ARCH_CPU_32_BITS)

namespace base {
namespace {

class AndroidModule : public ModuleCache::Module {
public:
AndroidModule(unwindstack::MapInfo* map_info)
: start_(map_info->start),
size_(map_info->end - map_info->start),
build_id_(map_info->GetBuildID()),
name_(map_info->name) {}
~AndroidModule() override = default;

uintptr_t GetBaseAddress() const override { return start_; }

std::string GetId() const override { return build_id_; }

FilePath GetDebugBasename() const override { return FilePath(name_); }

// Gets the size of the module.
size_t GetSize() const override { return size_; }

// True if this is a native module.
bool IsNative() const override { return true; }

const uintptr_t start_;
const size_t size_;
const std::string build_id_;
const std::string name_;
};

std::unique_ptr<unwindstack::Regs> CreateFromRegisterContext(
RegisterContext* thread_context) {
#if defined(ARCH_CPU_ARM_FAMILY) && defined(ARCH_CPU_32_BITS)
return WrapUnique<unwindstack::Regs>(unwindstack::RegsArm::Read(
reinterpret_cast<void*>(&thread_context->arm_r0)));
#elif defined(ARCH_CPU_ARM_FAMILY) && defined(ARCH_CPU_64_BITS)
return WrapUnique<unwindstack::Regs>(unwindstack::RegsArm64::Read(
reinterpret_cast<void*>(&thread_context->regs[0])));
#else // #if defined(ARCH_CPU_ARM_FAMILY) && defined(ARCH_CPU_32_BITS)
NOTREACHED();
return nullptr;
#endif // #if defined(ARCH_CPU_ARM_FAMILY) && defined(ARCH_CPU_32_BITS)
}

void CopyToRegisterContext(unwindstack::Regs* regs,
RegisterContext* thread_context) {
#if defined(ARCH_CPU_ARM_FAMILY) && defined(ARCH_CPU_32_BITS)
memcpy(reinterpret_cast<void*>(&thread_context->arm_r0), regs->RawData(),
unwindstack::ARM_REG_LAST * sizeof(uint32_t));
#elif defined(ARCH_CPU_ARM_FAMILY) && defined(ARCH_CPU_64_BITS)
memcpy(reinterpret_cast<void*>(&thread_context->regs[0]), regs->RawData(),
unwindstack::ARM64_REG_LAST * sizeof(uint32_t));
#else // #if defined(ARCH_CPU_ARM_FAMILY) && defined(ARCH_CPU_32_BITS)
NOTREACHED();
#endif // #if defined(ARCH_CPU_ARM_FAMILY) && defined(ARCH_CPU_32_BITS)
}

} // namespace

// static
std::unique_ptr<unwindstack::Maps> NativeUnwinderAndroid::CreateMaps() {
auto maps = std::make_unique<unwindstack::LocalMaps>();
if (maps->Parse())
return maps;
return nullptr;
}

// static
std::unique_ptr<unwindstack::Memory>
NativeUnwinderAndroid::CreateProcessMemory() {
return std::make_unique<unwindstack::MemoryLocal>();
}

void NativeUnwinderAndroid::AddInitialModulesFromMaps(
const unwindstack::Maps& memory_regions_map,
ModuleCache* module_cache) {
for (const auto& region : memory_regions_map) {
// Only add executable regions.
if (!(region->flags & PROT_EXEC))
continue;
module_cache->AddCustomNativeModule(
std::make_unique<AndroidModule>(region.get()));
}
}

NativeUnwinderAndroid::NativeUnwinderAndroid(
unwindstack::Maps* memory_regions_map,
unwindstack::Memory* process_memory,
uintptr_t exclude_module_with_base_address)
: memory_regions_map_(memory_regions_map),
process_memory_(process_memory),
exclude_module_with_base_address_(exclude_module_with_base_address) {}

NativeUnwinderAndroid::~NativeUnwinderAndroid() = default;

void NativeUnwinderAndroid::AddInitialModules(ModuleCache* module_cache) {
AddInitialModulesFromMaps(*memory_regions_map_, module_cache);
}

bool NativeUnwinderAndroid::CanUnwindFrom(const Frame& current_frame) const {
return false;
return current_frame.module && current_frame.module->IsNative() &&
current_frame.module->GetBaseAddress() !=
exclude_module_with_base_address_;
}

UnwindResult NativeUnwinderAndroid::TryUnwind(RegisterContext* thread_context,
uintptr_t stack_top,
ModuleCache* module_cache,
std::vector<Frame>* stack) const {
return UnwindResult::ABORTED;
auto regs = CreateFromRegisterContext(thread_context);
DCHECK(regs);
unwindstack::ArchEnum arch = regs->Arch();

do {
uint64_t cur_pc = regs->pc();
uint64_t cur_sp = regs->sp();
unwindstack::MapInfo* map_info = memory_regions_map_->Find(cur_pc);
if (map_info == nullptr ||
map_info->flags & unwindstack::MAPS_FLAGS_DEVICE_MAP) {
break;
}

unwindstack::Elf* elf =
map_info->GetElf({process_memory_, [](unwindstack::Memory*) {}}, arch);
if (!elf->valid())
break;

UnwindStackMemoryAndroid stack_memory(cur_sp, stack_top);
uintptr_t rel_pc = elf->GetRelPc(cur_pc, map_info);
bool finished = false;
bool stepped =
elf->Step(rel_pc, rel_pc, regs.get(), &stack_memory, &finished);
if (stepped && finished)
return UnwindResult::COMPLETED;

if (!stepped) {
// Stepping failed. Try unwinding using return address.
if (stack->size() == 1) {
if (!regs->SetPcFromReturnAddress(&stack_memory))
return UnwindResult::ABORTED;
} else {
break;
}
}

// If the pc and sp didn't change, then consider everything stopped.
if (cur_pc == regs->pc() && cur_sp == regs->sp())
return UnwindResult::ABORTED;

// Exclusive range of expected stack pointer values after the unwind.
struct {
uintptr_t start;
uintptr_t end;
} expected_stack_pointer_range = {cur_sp, stack_top};
if (regs->sp() < expected_stack_pointer_range.start ||
regs->sp() >= expected_stack_pointer_range.end) {
return UnwindResult::ABORTED;
}

if (regs->dex_pc() != 0) {
// Add a frame to represent the dex file.
EmitDexFrame(regs->dex_pc(), module_cache, stack);

// Clear the dex pc so that we don't repeat this frame later.
regs->set_dex_pc(0);
}

// Add the frame to |stack|.
const ModuleCache::Module* module =
module_cache->GetModuleForAddress(regs->pc());
stack->emplace_back(regs->pc(), module);
} while (CanUnwindFrom(stack->back()));

// Restore registers necessary for further unwinding in |thread_context|.
CopyToRegisterContext(regs.get(), thread_context);
return UnwindResult::UNRECOGNIZED_FRAME;
}

std::unique_ptr<Unwinder> CreateNativeUnwinder(ModuleCache* module_cache) {
return std::make_unique<NativeUnwinderAndroid>();
void NativeUnwinderAndroid::EmitDexFrame(uintptr_t dex_pc,
ModuleCache* module_cache,
std::vector<Frame>* stack) const {
const ModuleCache::Module* module = module_cache->GetModuleForAddress(dex_pc);
if (!module) {
// The region containing |dex_pc| may not be in |module_cache| since it's
// usually not executable (.dex file). Since non-executable regions
// are used much less commonly, it's lazily added here instead of from
// AddInitialModules().
unwindstack::MapInfo* map_info = memory_regions_map_->Find(dex_pc);
if (map_info) {
auto new_module = std::make_unique<AndroidModule>(map_info);
module = new_module.get();
module_cache->AddCustomNativeModule(std::move(new_module));
}
}
stack->emplace_back(dex_pc, module);
}

} // namespace base
Loading

0 comments on commit 38e3a62

Please sign in to comment.