123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414 |
- // 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_unwinder_android_v2.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]));
- }
- // 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 & 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);
- }
- } // namespace
- ChromeUnwinderAndroidV2::ChromeUnwinderAndroidV2(
- 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 ChromeUnwinderAndroidV2::CanUnwindFrom(const Frame& current_frame) const {
- return current_frame.module &&
- current_frame.module->GetBaseAddress() == chrome_module_base_address_;
- }
- UnwindResult ChromeUnwinderAndroidV2::TryUnwind(
- RegisterContext* thread_context,
- uintptr_t stack_top,
- std::vector<Frame>* stack) const {
- 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);
- // Validate SP is properly aligned across frames.
- // See
- // https://community.arm.com/arm-community-blogs/b/architectures-and-processors-blog/posts/using-the-stack-in-aarch32-and-aarch64
- // for SP alignment rules.
- if (!IsAligned(new_sp, 2 * sizeof(uintptr_t))) {
- return UnwindResult::kAborted;
- }
- // Validate that SP does not decrease across frames.
- const bool is_leaf_frame = stack->size() == 1;
- // Each frame unwind is expected to only pop from stack memory, which will
- // cause sp to increase.
- // Non-Leaf frames are expected to at least pop lr off stack, so sp is
- // expected to strictly increase for non-leaf frames.
- if (new_sp <= (is_leaf_frame ? frame_initial_sp - 1 : frame_initial_sp)) {
- return UnwindResult::kAborted;
- }
- // For leaf functions, if SP does not change, PC must change, otherwise,
- // the overall execution state will be the same before/after the frame
- // unwind.
- 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) {
- // 00xxxxxx
- // vsp = vsp + (xxxxxx << 2) + 4. Covers range 0x04-0x100 inclusive.
- 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) {
- // 01xxxxxx
- // vsp = vsp - (xxxxxx << 2) - 4. Covers range 0x04-0x100 inclusive.
- 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) {
- // 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++ & 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) {
- // 10000000 00000000
- // Refuse to unwind.
- instruction += 2;
- return UnwindInstructionResult::kAborted;
- } else if (GetTopBits(*instruction, 4) == 0b1000) {
- const uint32_t register_bitmask =
- ((*instruction & 0xfu) << 8) + *(instruction + 1);
- instruction += 2;
- // 1000iiii iiiiiiii
- // Pop up to 12 integer registers under masks {r15-r12}, {r11-r4}
- 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;
- }
- }
- }
- // 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;
- }
- 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);
- // 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>(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());
- // 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_byte_offset_from_text_section_start >> 17;
- const uint16_t page_instruction_offset =
- (instruction_byte_offset_from_text_section_start >> 1) &
- 0xffff; // 16 bits.
- // Invalid instruction_byte_offset_from_text_section_start:
- // instruction_byte_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() +
- 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]);
- // `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() +
- checked_cast<ptrdiff_t>(
- 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 =
- (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,
- };
- }
- } // namespace base
|