160 lines
6.3 KiB
C++
160 lines
6.3 KiB
C++
|
// Copyright 2019 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/arm_cfi_table.h"
|
||
|
|
||
|
#include <algorithm>
|
||
|
|
||
|
namespace base {
|
||
|
|
||
|
namespace {
|
||
|
|
||
|
// The value of index when the function does not have unwind information.
|
||
|
constexpr uint32_t kNoUnwindInformation = 0xFFFF;
|
||
|
|
||
|
// The mask on the CFI row data that is used to get the high 14 bits and
|
||
|
// multiply it by 4 to get CFA offset. Since the last 2 bits are masked out, a
|
||
|
// shift is not necessary.
|
||
|
constexpr uint16_t kCFAMask = 0xfffc;
|
||
|
|
||
|
// The mask on the CFI row data that is used to get the low 2 bits and multiply
|
||
|
// it by 4 to get the return address offset.
|
||
|
constexpr uint16_t kReturnAddressMask = 0x3;
|
||
|
constexpr uint16_t kReturnAddressShift = 2;
|
||
|
|
||
|
// The CFI data in UNW_DATA table starts with number of rows (N) encoded as
|
||
|
// uint16_t, followed by N 4 byte rows. The CFIDataRow represents a single row
|
||
|
// of CFI data of a function in the table. Since we cast the memory at the
|
||
|
// address after the address of number of rows into an array of CFIDataRow, the
|
||
|
// size of the struct should be 4 bytes and the order of the members is fixed
|
||
|
// according to the given format. The first 2 bytes is the address of function
|
||
|
// and last 2 bytes is the CFI data for the offset.
|
||
|
struct CFIDataRow {
|
||
|
// The address of the instruction as an offset from the start of the
|
||
|
// function.
|
||
|
uint16_t addr_offset;
|
||
|
// Represents the CFA and RA offsets to get information about next stack
|
||
|
// frame. This is the CFI data at the point before executing the instruction
|
||
|
// at |addr_offset| from the start of the function.
|
||
|
uint16_t cfi_data;
|
||
|
|
||
|
// Helper functions to convert the to ArmCFITable::FrameEntry
|
||
|
size_t ra_offset() const {
|
||
|
return (cfi_data & kReturnAddressMask) << kReturnAddressShift;
|
||
|
}
|
||
|
size_t cfa_offset() const { return cfi_data & kCFAMask; }
|
||
|
};
|
||
|
|
||
|
static_assert(sizeof(CFIDataRow) == 4,
|
||
|
"The CFIDataEntry struct must be exactly 4 bytes to ensure "
|
||
|
"correct parsing of input data");
|
||
|
|
||
|
} // namespace
|
||
|
|
||
|
// static
|
||
|
std::unique_ptr<ArmCFITable> ArmCFITable::Parse(span<const uint8_t> cfi_data) {
|
||
|
BufferIterator<const uint8_t> cfi_iterator(cfi_data);
|
||
|
|
||
|
const uint32_t* unw_index_count = cfi_iterator.Object<uint32_t>();
|
||
|
if (unw_index_count == nullptr || *unw_index_count == 0U)
|
||
|
return nullptr;
|
||
|
|
||
|
auto function_addresses = cfi_iterator.Span<uint32_t>(*unw_index_count);
|
||
|
auto entry_data_indices = cfi_iterator.Span<uint16_t>(*unw_index_count);
|
||
|
if (function_addresses.size() != *unw_index_count ||
|
||
|
entry_data_indices.size() != *unw_index_count)
|
||
|
return nullptr;
|
||
|
|
||
|
// The UNW_DATA table data is right after the end of UNW_INDEX table.
|
||
|
auto entry_data = cfi_iterator.Span<uint8_t>(
|
||
|
(cfi_iterator.total_size() - cfi_iterator.position()) / sizeof(uint8_t));
|
||
|
return std::make_unique<ArmCFITable>(function_addresses, entry_data_indices,
|
||
|
entry_data);
|
||
|
}
|
||
|
|
||
|
ArmCFITable::ArmCFITable(span<const uint32_t> function_addresses,
|
||
|
span<const uint16_t> entry_data_indices,
|
||
|
span<const uint8_t> entry_data)
|
||
|
: function_addresses_(function_addresses),
|
||
|
entry_data_indices_(entry_data_indices),
|
||
|
entry_data_(entry_data) {
|
||
|
DCHECK_EQ(function_addresses.size(), entry_data_indices.size());
|
||
|
}
|
||
|
|
||
|
ArmCFITable::~ArmCFITable() = default;
|
||
|
|
||
|
Optional<ArmCFITable::FrameEntry> ArmCFITable::FindEntryForAddress(
|
||
|
uintptr_t address) const {
|
||
|
DCHECK(!function_addresses_.empty());
|
||
|
|
||
|
// Find the required function address in UNW_INDEX as the last function lower
|
||
|
// or equal to |address| (the value right before the result of upper_bound(),
|
||
|
// if any).
|
||
|
auto func_it = std::upper_bound(function_addresses_.begin(),
|
||
|
function_addresses_.end(), address);
|
||
|
// If no function comes before |address|, no CFI entry is returned.
|
||
|
if (func_it == function_addresses_.begin())
|
||
|
return nullopt;
|
||
|
--func_it;
|
||
|
|
||
|
uint32_t func_start_addr = *func_it;
|
||
|
size_t row_num = func_it - function_addresses_.begin();
|
||
|
uint16_t index = entry_data_indices_[row_num];
|
||
|
DCHECK_LE(func_start_addr, address);
|
||
|
|
||
|
if (index == kNoUnwindInformation)
|
||
|
return nullopt;
|
||
|
|
||
|
// The unwind data for the current function is at a 2 bytes offset of the
|
||
|
// index found in UNW_INDEX table.
|
||
|
if (entry_data_.size() <= index * sizeof(uint16_t))
|
||
|
return nullopt;
|
||
|
BufferIterator<const uint8_t> entry_iterator(entry_data_);
|
||
|
entry_iterator.Seek(index * sizeof(uint16_t));
|
||
|
|
||
|
// The value of first 2 bytes is the CFI data row count for the function.
|
||
|
const uint16_t* row_count = entry_iterator.Object<uint16_t>();
|
||
|
if (row_count == nullptr)
|
||
|
return nullopt;
|
||
|
// And the actual CFI rows start after 2 bytes from the |unwind_data|. Cast
|
||
|
// the data into an array of CFIUnwindDataRow since the struct is designed to
|
||
|
// represent each row. We should be careful to read only |row_count| number of
|
||
|
// elements in the array.
|
||
|
auto function_cfi = entry_iterator.Span<CFIDataRow>(*row_count);
|
||
|
if (function_cfi.size() != *row_count)
|
||
|
return nullopt;
|
||
|
|
||
|
FrameEntry last_frame_entry = {0, 0};
|
||
|
// Iterate through all function entries to find a range covering |address|.
|
||
|
// In practice, the majority of functions contain very few entries.
|
||
|
for (const auto& entry : function_cfi) {
|
||
|
// The return address of the function is the instruction that is not yet
|
||
|
// been executed. The CFI row specifies the unwind info before executing the
|
||
|
// given instruction. If the given address is equal to the instruction
|
||
|
// offset, then use the current row. Or use the row with highest address
|
||
|
// less than the given address.
|
||
|
if (func_start_addr + entry.addr_offset > address)
|
||
|
break;
|
||
|
|
||
|
uint32_t cfa_offset = entry.cfa_offset();
|
||
|
if (cfa_offset == 0)
|
||
|
return nullopt;
|
||
|
last_frame_entry.cfa_offset = cfa_offset;
|
||
|
|
||
|
uint32_t ra_offset = entry.ra_offset();
|
||
|
// The RA offset of the last specified row should be used, if unspecified.
|
||
|
// Update |last_ra_offset| only if valid for this row. Otherwise, tthe last
|
||
|
// valid |last_ra_offset| is used. TODO(ssid): This should be fixed in the
|
||
|
// format and we should always output ra offset.
|
||
|
if (ra_offset)
|
||
|
last_frame_entry.ra_offset = ra_offset;
|
||
|
|
||
|
if (last_frame_entry.ra_offset == 0)
|
||
|
return nullopt;
|
||
|
}
|
||
|
|
||
|
return last_frame_entry;
|
||
|
}
|
||
|
|
||
|
} // namespace base
|