[go: nahoru, domu]

blob: 780e518aedf19ad593fa22b9cb41b297f61540f2 [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 "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::STACK_POINTER_OUT_OF_BOUNDS;
}
} 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::STACK_POINTER_OUT_OF_BOUNDS;
}
} 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::STACK_POINTER_OUT_OF_BOUNDS;
}
}
if (!PopRegister(thread_context, 14)) {
return UnwindInstructionResult::STACK_POINTER_OUT_OF_BOUNDS;
}
} else if (GetTopBits(*instruction, 4) == 0b1000) {
// 1000iiii iiiiiiii
// Pop up to 12 integer registers under masks {r15-r12}, {r11-r4}
const uint32_t register_bitmask =
((*instruction & 0xf) << 8) + *(instruction + 1);
// 10000000 00000000 is reserved for 'Refused to unwind'.
DCHECK_NE(register_bitmask, 0ul);
instruction += 2;
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::STACK_POINTER_OUT_OF_BOUNDS;
}
}
}
// 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::COMPLETED;
} 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::STACK_POINTER_OUT_OF_BOUNDS;
}
} else {
NOTREACHED();
}
return UnwindInstructionResult::INSTRUCTION_PENDING;
}
const uint8_t* GetFirstUnwindInstructionFromInstructionOffset(
const uint8_t* unwind_instruction_table,
const uint8_t* function_offset_table,
uint16_t function_offset_table_byte_index,
uint32_t instruction_offset_from_function_start) {
const uint8_t* current_function_offset_table_position =
&function_offset_table[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 <= instruction_offset_from_function_start)
return &unwind_instruction_table[unwind_table_index];
} while (true);
NOTREACHED();
return nullptr;
}
} // namespace base