#include "base/profiler/chrome_unwinder_android.h"
#include <algorithm>
#include "base/check_op.h"
#include "base/memory/aligned_memory.h"
#include "base/notreached.h"
#include "base/numerics/checked_math.h"
#include "base/profiler/chrome_unwind_info_android.h"
namespace base {
namespace {
uintptr_t* GetRegisterPointer(RegisterContext* context,
uint8_t register_index) {
DCHECK_LE(register_index, 15);
static unsigned long RegisterContext::*const registers[16] = {
&RegisterContext::arm_r0, &RegisterContext::arm_r1,
&RegisterContext::arm_r2, &RegisterContext::arm_r3,
&RegisterContext::arm_r4, &RegisterContext::arm_r5,
&RegisterContext::arm_r6, &RegisterContext::arm_r7,
&RegisterContext::arm_r8, &RegisterContext::arm_r9,
&RegisterContext::arm_r10, &RegisterContext::arm_fp,
&RegisterContext::arm_ip, &RegisterContext::arm_sp,
&RegisterContext::arm_lr, &RegisterContext::arm_pc,
};
return reinterpret_cast<uintptr_t*>(&(context->*registers[register_index]));
}
bool PopRegister(RegisterContext* context, uint8_t register_index) {
const uintptr_t sp = RegisterContextStackPointer(context);
const uintptr_t stacktop_value = *reinterpret_cast<uintptr_t*>(sp);
const auto new_sp = CheckedNumeric<uintptr_t>(sp) + sizeof(uintptr_t);
const bool success =
new_sp.AssignIfValid(&RegisterContextStackPointer(context));
if (success)
*GetRegisterPointer(context, register_index) = stacktop_value;
return success;
}
uintptr_t DecodeULEB128(const uint8_t*& bytes) {
uintptr_t value = 0;
unsigned shift = 0;
do {
DCHECK_LE(shift, sizeof(uintptr_t) * 8);
value += (*bytes & 0x7fu) << shift;
shift += 7;
} while (*bytes++ & 0x80);
return value;
}
uint8_t GetTopBits(uint8_t byte, unsigned bits) {
DCHECK_LE(bits, 8u);
return byte >> (8 - bits);
}
}
ChromeUnwinderAndroid::ChromeUnwinderAndroid(
const ChromeUnwindInfoAndroid& unwind_info,
uintptr_t chrome_module_base_address,
uintptr_t text_section_start_address)
: unwind_info_(unwind_info),
chrome_module_base_address_(chrome_module_base_address),
text_section_start_address_(text_section_start_address) {
DCHECK_GT(text_section_start_address_, chrome_module_base_address_);
}
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()));
uintptr_t frame_initial_sp = RegisterContextStackPointer(thread_context);
const uintptr_t unwind_initial_pc =
RegisterContextInstructionPointer(thread_context);
do {
const uintptr_t pc = RegisterContextInstructionPointer(thread_context);
const uintptr_t instruction_byte_offset_from_text_section_start =
pc - text_section_start_address_;
const absl::optional<FunctionOffsetTableIndex> function_offset_table_index =
GetFunctionTableIndexFromInstructionOffset(
unwind_info_.page_table, unwind_info_.function_table,
instruction_byte_offset_from_text_section_start);
if (!function_offset_table_index) {
return UnwindResult::kAborted;
}
const uint32_t current_unwind_instruction_index =
GetFirstUnwindInstructionIndexFromFunctionOffsetTableEntry(
&unwind_info_
.function_offset_table[function_offset_table_index
->function_offset_table_byte_index],
function_offset_table_index
->instruction_offset_from_function_start);
const uint8_t* current_unwind_instruction =
&unwind_info_
.unwind_instruction_table[current_unwind_instruction_index];
UnwindInstructionResult instruction_result;
bool pc_was_updated = false;
do {
instruction_result = ExecuteUnwindInstruction(
current_unwind_instruction, pc_was_updated, thread_context);
const uintptr_t sp = RegisterContextStackPointer(thread_context);
if (sp > stack_top || sp < frame_initial_sp ||
!IsAligned(sp, sizeof(uintptr_t))) {
return UnwindResult::kAborted;
}
} while (instruction_result ==
UnwindInstructionResult::kInstructionPending);
if (instruction_result == UnwindInstructionResult::kAborted) {
return UnwindResult::kAborted;
}
DCHECK_EQ(instruction_result, UnwindInstructionResult::kCompleted);
const uintptr_t new_sp = RegisterContextStackPointer(thread_context);
if (!IsAligned(new_sp, 2 * sizeof(uintptr_t))) {
return UnwindResult::kAborted;
}
const bool is_leaf_frame = stack->size() == 1;
if (new_sp <= (is_leaf_frame ? frame_initial_sp - 1 : frame_initial_sp)) {
return UnwindResult::kAborted;
}
if (is_leaf_frame && new_sp == frame_initial_sp &&
RegisterContextInstructionPointer(thread_context) ==
unwind_initial_pc) {
return UnwindResult::kAborted;
}
frame_initial_sp = new_sp;
stack->emplace_back(RegisterContextInstructionPointer(thread_context),
module_cache()->GetModuleForAddress(
RegisterContextInstructionPointer(thread_context)));
} while (CanUnwindFrom(stack->back()));
return UnwindResult::kUnrecognizedFrame;
}
UnwindInstructionResult ExecuteUnwindInstruction(
const uint8_t*& instruction,
bool& pc_was_updated,
RegisterContext* thread_context) {
if (GetTopBits(*instruction, 2) == 0b00) {
const uintptr_t offset = ((*instruction++ & 0b00111111u) << 2) + 4;
const auto new_sp =
CheckedNumeric<uintptr_t>(RegisterContextStackPointer(thread_context)) +
offset;
if (!new_sp.AssignIfValid(&RegisterContextStackPointer(thread_context))) {
return UnwindInstructionResult::kAborted;
}
} else if (GetTopBits(*instruction, 2) == 0b01) {
const uintptr_t offset = ((*instruction++ & 0b00111111u) << 2) + 4;
const auto new_sp =
CheckedNumeric<uintptr_t>(RegisterContextStackPointer(thread_context)) -
offset;
if (!new_sp.AssignIfValid(&RegisterContextStackPointer(thread_context))) {
return UnwindInstructionResult::kAborted;
}
} else if (GetTopBits(*instruction, 4) == 0b1001) {
const uint8_t register_index = *instruction++ & 0b00001111;
DCHECK_NE(register_index, 13);
DCHECK_NE(register_index, 15);
DCHECK_GE(register_index, 4);
RegisterContextStackPointer(thread_context) =
*GetRegisterPointer(thread_context, register_index);
} else if (GetTopBits(*instruction, 5) == 0b10101) {
const uint8_t max_register_index = (*instruction++ & 0b00000111u) + 4;
for (uint8_t n = 4; n <= max_register_index; n++) {
if (!PopRegister(thread_context, n)) {
return UnwindInstructionResult::kAborted;
}
}
if (!PopRegister(thread_context, 14)) {
return UnwindInstructionResult::kAborted;
}
} else if (*instruction == 0b10000000 && *(instruction + 1) == 0) {
instruction += 2;
return UnwindInstructionResult::kAborted;
} else if (GetTopBits(*instruction, 4) == 0b1000) {
const uint32_t register_bitmask =
((*instruction & 0xfu) << 8) + *(instruction + 1);
instruction += 2;
for (uint8_t register_index = 4; register_index < 16; register_index++) {
if (register_bitmask & (1 << (register_index - 4))) {
if (!PopRegister(thread_context, register_index)) {
return UnwindInstructionResult::kAborted;
}
}
}
pc_was_updated |= register_bitmask & (1 << (15 - 4));
} else if (*instruction == 0b10110000) {
instruction++;
if (!pc_was_updated)
thread_context->arm_pc = thread_context->arm_lr;
return UnwindInstructionResult::kCompleted;
} else if (*instruction == 0b10110010) {
instruction++;
const auto new_sp =
CheckedNumeric<uintptr_t>(RegisterContextStackPointer(thread_context)) +
(CheckedNumeric<uintptr_t>(DecodeULEB128(instruction)) << 2) + 0x204;
if (!new_sp.AssignIfValid(&RegisterContextStackPointer(thread_context))) {
return UnwindInstructionResult::kAborted;
}
} else {
NOTREACHED();
}
return UnwindInstructionResult::kInstructionPending;
}
uintptr_t GetFirstUnwindInstructionIndexFromFunctionOffsetTableEntry(
const uint8_t* function_offset_table_entry,
int instruction_offset_from_function_start) {
DCHECK_GE(instruction_offset_from_function_start, 0);
const uint8_t* current_function_offset_table_position =
function_offset_table_entry;
do {
const uintptr_t function_offset =
DecodeULEB128(current_function_offset_table_position);
const uintptr_t unwind_table_index =
DecodeULEB128(current_function_offset_table_position);
if (function_offset <=
static_cast<uint32_t>(instruction_offset_from_function_start))
return unwind_table_index;
} while (true);
NOTREACHED();
return 0;
}
const absl::optional<FunctionOffsetTableIndex>
GetFunctionTableIndexFromInstructionOffset(
span<const uint32_t> page_start_instructions,
span<const FunctionTableEntry> function_offset_table_indices,
uint32_t instruction_byte_offset_from_text_section_start) {
DCHECK(!page_start_instructions.empty());
DCHECK(!function_offset_table_indices.empty());
DCHECK_EQ(function_offset_table_indices.front()
.function_start_address_page_instruction_offset,
0ul);
const uint16_t page_number =
instruction_byte_offset_from_text_section_start >> 17;
const uint16_t page_instruction_offset =
(instruction_byte_offset_from_text_section_start >> 1) &
0xffff;
if (page_number >= page_start_instructions.size()) {
return absl::nullopt;
}
const span<const FunctionTableEntry>::const_iterator
function_table_entry_start =
function_offset_table_indices.begin() +
checked_cast<ptrdiff_t>(page_start_instructions[page_number]);
const span<const FunctionTableEntry>::const_iterator
function_table_entry_end =
page_number == page_start_instructions.size() - 1
? function_offset_table_indices.end()
: function_offset_table_indices.begin() +
checked_cast<ptrdiff_t>(
page_start_instructions[page_number + 1]);
const auto first_larger_entry_location = std::upper_bound(
function_table_entry_start, function_table_entry_end,
page_instruction_offset,
[](uint16_t page_instruction_offset, const FunctionTableEntry& entry) {
return page_instruction_offset <
entry.function_start_address_page_instruction_offset;
});
const auto entry_location = first_larger_entry_location - 1;
uint16_t function_start_page_number = page_number;
while (function_offset_table_indices.begin() +
checked_cast<ptrdiff_t>(
page_start_instructions[function_start_page_number]) >
entry_location) {
DCHECK_NE(function_start_page_number, 0);
function_start_page_number--;
};
const uint32_t function_start_address_instruction_offset =
(uint32_t{function_start_page_number} << 16) +
entry_location->function_start_address_page_instruction_offset;
const int instruction_offset_from_function_start =
static_cast<int>((instruction_byte_offset_from_text_section_start >> 1) -
function_start_address_instruction_offset);
DCHECK_GE(instruction_offset_from_function_start, 0);
return FunctionOffsetTableIndex{
instruction_offset_from_function_start,
entry_location->function_offset_table_byte_index,
};
}
}