// 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/numerics/checked_math.h"
#include "base/profiler/module_cache.h"
#include "base/profiler/native_unwinder.h"
#include "build/build_config.h"
namespace base {
ChromeUnwinderAndroid::ChromeUnwinderAndroid(
const ArmCFITable* cfi_table,
const ModuleCache::Module* chrome_module)
: cfi_table_(cfi_table), chrome_module_(chrome_module) {
DCHECK(cfi_table_);
DCHECK(chrome_module_);
}
ChromeUnwinderAndroid::~ChromeUnwinderAndroid() = default;
bool ChromeUnwinderAndroid::CanUnwindFrom(const Frame& current_frame) const {
return current_frame.module == chrome_module_;
}
UnwindResult ChromeUnwinderAndroid::TryUnwind(RegisterContext* thread_context,
uintptr_t stack_top,
ModuleCache* module_cache,
std::vector* stack) const {
DCHECK(CanUnwindFrom(stack->back()));
do {
const ModuleCache::Module* module = stack->back().module;
uintptr_t pc = RegisterContextInstructionPointer(thread_context);
DCHECK_GE(pc, module->GetBaseAddress());
uintptr_t func_addr = pc - module->GetBaseAddress();
auto entry = cfi_table_->FindEntryForAddress(func_addr);
if (!entry)
return UnwindResult::ABORTED;
if (!Step(thread_context, stack_top, *entry))
return UnwindResult::ABORTED;
stack->emplace_back(RegisterContextInstructionPointer(thread_context),
module_cache->GetModuleForAddress(
RegisterContextInstructionPointer(thread_context)));
} while (CanUnwindFrom(stack->back()));
return UnwindResult::UNRECOGNIZED_FRAME;
}
// static
bool ChromeUnwinderAndroid::Step(RegisterContext* thread_context,
uintptr_t stack_top,
const ArmCFITable::FrameEntry& entry) {
CHECK_NE(RegisterContextStackPointer(thread_context), 0U);
CHECK_LE(RegisterContextStackPointer(thread_context), stack_top);
if (entry.cfa_offset == 0) {
uintptr_t pc = RegisterContextInstructionPointer(thread_context);
uintptr_t return_address = static_cast(thread_context->arm_lr);
if (pc == return_address)
return false;
RegisterContextInstructionPointer(thread_context) = return_address;
} else {
// The rules for unwinding using the CFI information are:
// SP_prev = SP_cur + cfa_offset and
// PC_prev = * (SP_prev - ra_offset).
auto new_sp =
CheckedNumeric(RegisterContextStackPointer(thread_context)) +
CheckedNumeric(entry.cfa_offset);
if (!new_sp.AssignIfValid(&RegisterContextStackPointer(thread_context)) ||
RegisterContextStackPointer(thread_context) >= stack_top) {
return false;
}
if (entry.ra_offset > entry.cfa_offset)
return false;
// Underflow is prevented because |ra_offset| <= |cfa_offset|.
uintptr_t ip_address = (new_sp - CheckedNumeric(entry.ra_offset))
.ValueOrDie();
RegisterContextInstructionPointer(thread_context) =
*reinterpret_cast(ip_address);
}
return true;
}
} // namespace base