blob: 3322b9255c3eccb17cd74c44b7c48a092fe7fcca [file] [log] [blame]
// Copyright 2019 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/chrome_unwinder_android.h"
#include "base/numerics/checked_math.h"
#include "base/profiler/module_cache.h"
#include "build/build_config.h"
namespace base {
ChromeUnwinderAndroid::ChromeUnwinderAndroid(
const ArmCFITable* cfi_table,
uintptr_t chrome_module_base_address)
: cfi_table_(cfi_table),
chrome_module_base_address_(chrome_module_base_address) {
DCHECK(cfi_table_);
}
ChromeUnwinderAndroid::~ChromeUnwinderAndroid() = default;
bool ChromeUnwinderAndroid::CanUnwindFrom(const Frame& current_frame) const {
return current_frame.module &&
current_frame.module->GetBaseAddress() == chrome_module_base_address_;
}
UnwindResult ChromeUnwinderAndroid::TryUnwind(RegisterContext* thread_context,
uintptr_t stack_top,
std::vector<Frame>* stack) {
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) {
if (!Step(thread_context, stack_top, *entry))
return UnwindResult::kAborted;
} else if (stack->size() == 1) {
// Try unwinding by sourcing the return address from the lr register.
if (!StepUsingLrRegister(thread_context, stack_top))
return UnwindResult::kAborted;
} else {
return UnwindResult::kAborted;
}
stack->emplace_back(RegisterContextInstructionPointer(thread_context),
module_cache()->GetModuleForAddress(
RegisterContextInstructionPointer(thread_context)));
} while (CanUnwindFrom(stack->back()));
return UnwindResult::kUnrecognizedFrame;
}
// 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)
return StepUsingLrRegister(thread_context, stack_top);
// 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<uintptr_t>(RegisterContextStackPointer(thread_context)) +
CheckedNumeric<uint16_t>(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<uint16_t>(entry.ra_offset))
.ValueOrDie<uintptr_t>();
RegisterContextInstructionPointer(thread_context) =
*reinterpret_cast<uintptr_t*>(ip_address);
return true;
}
// static
bool ChromeUnwinderAndroid::StepUsingLrRegister(RegisterContext* thread_context,
uintptr_t stack_top) {
CHECK_NE(RegisterContextStackPointer(thread_context), 0U);
CHECK_LE(RegisterContextStackPointer(thread_context), stack_top);
uintptr_t pc = RegisterContextInstructionPointer(thread_context);
uintptr_t return_address = static_cast<uintptr_t>(thread_context->arm_lr);
// The step failed if the pc doesn't change.
if (pc == return_address)
return false;
RegisterContextInstructionPointer(thread_context) = return_address;
return true;
}
} // namespace base