blob: f2652e4f1d26d01c348b80b491161bb31d30244d [file] [log] [blame]
// Copyright 2021 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_unwind_table_android.h"
#include <algorithm>
#include "base/check_op.h"
#include "base/notreached.h"
#include "base/numerics/checked_math.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]));
}
// Pops the value on the top of stack out and assign it to target register.
// This is equivalent to arm instruction `Pop r[n]` where n = `register_index`.
// Returns whether the pop is successful.
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;
}
// Decodes the given bytes as an ULEB128 format number and advances the bytes
// pointer by the size of ULEB128.
//
// This function assumes the given bytes are in valid ULEB128
// format and the decoded number should not overflow `uintptr_t` type.
uintptr_t DecodeULEB128(const uint8_t*& bytes) {
uintptr_t value = 0;
unsigned shift = 0;
do {
DCHECK_LE(shift, sizeof(uintptr_t) * 8); // ULEB128 must not overflow.
value += (*bytes & 0x7f) << shift;
shift += 7;
} while (*bytes++ & 0x80);
return value;
}
uint8_t GetTopBits(uint8_t byte, unsigned bits) {
DCHECK_LE(bits, 8u);
return byte >> (8 - bits);
}
} // namespace
UnwindInstructionResult ExecuteUnwindInstruction(
const uint8_t*& instruction,
bool& pc_was_updated,
RegisterContext* thread_context) {
if (GetTopBits(*instruction, 2) == 0b00) {
// 00xxxxxx
// vsp = vsp + (xxxxxx << 2) + 4. Covers range 0x04-0x100 inclusive.
const uintptr_t offset = ((*instruction++ & 0b00111111) << 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) {
// 01xxxxxx
// vsp = vsp - (xxxxxx << 2) - 4. Covers range 0x04-0x100 inclusive.
const uintptr_t offset = ((*instruction++ & 0b00111111) << 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) {
// 1001nnnn (nnnn != 13,15)
// Set vsp = r[nnnn].
const uint8_t register_index = *instruction++ & 0b00001111;
DCHECK_NE(register_index, 13); // Must not set sp to sp.
DCHECK_NE(register_index, 15); // Must not set sp to pc.
// Note: We shouldn't have cases that are setting caller-saved registers
// using this instruction.
DCHECK_GE(register_index, 4);
RegisterContextStackPointer(thread_context) =
*GetRegisterPointer(thread_context, register_index);
} else if (GetTopBits(*instruction, 5) == 0b10101) {
// 10101nnn
// Pop r4-r[4+nnn], r14
const uint8_t max_register_index = (*instruction++ & 0b00000111) + 4;
for (int 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) {
// 10000000 00000000
// Refuse to unwind.
instruction += 2;
return UnwindInstructionResult::kAborted;
} else if (GetTopBits(*instruction, 4) == 0b1000) {
const uint32_t register_bitmask =
((*instruction & 0xf) << 8) + *(instruction + 1);
instruction += 2;
// 1000iiii iiiiiiii
// Pop up to 12 integer registers under masks {r15-r12}, {r11-r4}
for (int register_index = 4; register_index < 16; register_index++) {
if (register_bitmask & (1 << (register_index - 4))) {
if (!PopRegister(thread_context, register_index)) {
return UnwindInstructionResult::kAborted;
}
}
}
// If we set pc (r15) with value on stack, we should no longer copy lr to
// pc on COMPLETE.
pc_was_updated |= register_bitmask & (1 << (15 - 4));
} else if (*instruction == 0b10110000) {
// Finish
// Code 0xb0, Finish, copies VRS[r14] to VRS[r15] and also
// indicates that no further instructions are to be processed for this
// frame.
instruction++;
// Only copy lr to pc when pc is not updated by other instructions before.
if (!pc_was_updated)
thread_context->arm_pc = thread_context->arm_lr;
return UnwindInstructionResult::kCompleted;
} else if (*instruction == 0b10110010) {
// 10110010 uleb128
// vsp = vsp + 0x204 + (uleb128 << 2)
// (for vsp increments of 0x104-0x200, use 00xxxxxx twice)
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;
}
const uint8_t* GetFirstUnwindInstructionFromFunctionOffsetTableIndex(
const uint8_t* unwind_instruction_table,
const uint8_t* function_offset_table,
const FunctionOffsetTableIndex& index) {
DCHECK_GE(index.instruction_offset_from_function_start, 0);
const uint8_t* current_function_offset_table_position =
&function_offset_table[index.function_offset_table_byte_index];
do {
const uintptr_t function_offset =
DecodeULEB128(current_function_offset_table_position);
const uintptr_t unwind_table_index =
DecodeULEB128(current_function_offset_table_position);
// Each function always ends at 0 offset. It is guaranteed to find an entry
// as long as the function offset table is well-structured.
if (function_offset <=
static_cast<uint32_t>(index.instruction_offset_from_function_start))
return &unwind_instruction_table[unwind_table_index];
} while (true);
NOTREACHED();
return nullptr;
}
const absl::optional<FunctionOffsetTableIndex>
GetFunctionTableIndexFromInstructionOffset(
span<const uint32_t> page_start_instructions,
span<const FunctionTableEntry> function_offset_table_indices,
uint32_t instruction_offset_from_text_section_start) {
DCHECK(!page_start_instructions.empty());
DCHECK(!function_offset_table_indices.empty());
// First function on first page should always start from 0 offset.
DCHECK_EQ(function_offset_table_indices.front()
.function_start_address_page_instruction_offset,
0ul);
const uint16_t page_number = instruction_offset_from_text_section_start >> 17;
const uint16_t page_instruction_offset =
(instruction_offset_from_text_section_start >> 1) & 0xffff; // 16 bits.
// Invalid instruction_offset_from_text_section_start:
// instruction_offset_from_text_section_start falls after the last page.
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() +
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() +
page_start_instructions[page_number + 1];
// `std::upper_bound` finds first element that > target in range
// [function_table_entry_start, function_table_entry_end).
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;
});
// Offsets the element found by 1 to get the biggest element that <= target.
const auto entry_location = first_larger_entry_location - 1;
// When all offsets in current range > page_instruction_offset (including when
// there is no entry in current range), the `FunctionTableEntry` we are
// looking for is not within the function_offset_table_indices range we are
// inspecting, because the function is too long that it spans multiple pages.
//
// We need to locate the previous entry on function_offset_table_indices and
// find its corresponding page_table index.
//
// Example:
// +--------------------+--------------------+
// | <-----2 byte-----> | <-----2 byte-----> |
// +--------------------+--------------------+
// | Page Offset | Offset Table Index |
// +--------------------+--------------------+-----
// | 10 | XXX | |
// +--------------------+--------------------+ |
// | ... | ... |Page 0x100
// +--------------------+--------------------+ |
// | 65500 | ZZZ | |
// +--------------------+--------------------+----- Page 0x101 is empty
// | 200 | AAA | |
// +--------------------+--------------------+ |
// | ... | ... |Page 0x102
// +--------------------+--------------------+ |
// | 65535 | BBB | |
// +--------------------+--------------------+-----
//
// Example:
// For
// - page_number = 0x100, page_instruction_offset >= 65535
// - page_number = 0x101, all page_instruction_offset
// - page_number = 0x102, page_instruction_offset < 200
// We should be able to map them all to entry [65500, ZZZ] in page 0x100.
// Finds the page_number that corresponds to `entry_location`. The page
// might not be the page we are inspecting, when the function spans over
// multiple pages.
uint16_t function_start_page_number = page_number;
while (function_offset_table_indices.begin() +
page_start_instructions[function_start_page_number] >
entry_location) {
// First page in page table must not be empty.
DCHECK_NE(function_start_page_number, 0);
function_start_page_number--;
};
const uint32_t function_start_address_instruction_offset =
(function_start_page_number << 16) +
entry_location->function_start_address_page_instruction_offset;
const int instruction_offset_from_function_start =
(instruction_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,
};
}
} // namespace base