// 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 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::Parse(span cfi_data) { BufferIterator cfi_iterator(cfi_data); const uint32_t* unw_index_count = cfi_iterator.Object(); if (unw_index_count == nullptr || *unw_index_count == 0U) return nullptr; auto function_addresses = cfi_iterator.Span(*unw_index_count); auto entry_data_indices = cfi_iterator.Span(*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( (cfi_iterator.total_size() - cfi_iterator.position()) / sizeof(uint8_t)); return std::make_unique(function_addresses, entry_data_indices, entry_data); } ArmCFITable::ArmCFITable(span function_addresses, span entry_data_indices, span 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::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 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(); 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(*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