blob: 4aa3e0abd7a8ca00e72341c6efc707850491f21a [file] [log] [blame]
// Copyright 2019 The Fuchsia 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 "src/developer/debug/zxdb/symbols/dwarf_location.h"
#include <limits>
#include "llvm/DebugInfo/DWARF/DWARFContext.h"
#include "llvm/DebugInfo/DWARF/DWARFDebugLoc.h"
#include "llvm/DebugInfo/DWARF/DWARFSection.h"
#include "llvm/DebugInfo/DWARF/DWARFUnit.h"
#include "src/developer/debug/zxdb/common/data_extractor.h"
namespace zxdb {
namespace {
// Reads a DWARF 5 counted location description from the given data extractor. Returns nullopt on
// failure.
std::optional<DwarfExpr> ExtractCountedLocationDescription(DataExtractor& ext,
const UncachedLazySymbol& source) {
// A counted location description consists of a ULEB128 integer giving the byte length of the
// following location description.
auto length_or = ext.ReadUleb128();
if (!length_or)
return std::nullopt;
// Empty expressions are valid and just mean to skip this entry.
if (*length_or == 0)
return DwarfExpr({}, source);
// Expression data.
std::vector<uint8_t> expression;
expression.resize(*length_or);
if (!ext.ReadBytes(*length_or, expression.data()))
return std::nullopt;
return DwarfExpr(std::move(expression), source);
}
// Reads a DWARF 5 counted location description from the given data extractor, and appends it
// for the given range to the given output vector. Returns true on success.
bool AppendCountedLocationDescriptionEntry(TargetPointer begin, TargetPointer end,
DataExtractor& ext,
std::vector<VariableLocation::Entry>& output_list,
const UncachedLazySymbol& source) {
std::optional<DwarfExpr> expr = ExtractCountedLocationDescription(ext, source);
if (!expr)
return false;
// Empty expressions are valid and just mean to skip this entry, so we don't bother adding it.
if (expr->data().empty())
return true;
// Skip invalid ranges and don't bother adding empty ranges. Check this after reading the
// expression to advance the data extractor.
if (begin >= end)
return true;
VariableLocation::Entry& dest = output_list.emplace_back();
dest.range = AddressRange(begin, end);
dest.expression = std::move(*expr);
return true;
}
// Decodes a reference to a DWARF 4 location list. This is a DW_FORM_sec_offset type into the
// .debug_loc section that contains the location list.
VariableLocation DecodeDwarf4LocationReference(const llvm::DWARFUnit* unit,
const llvm::DWARFFormValue& form,
const UncachedLazySymbol& source) {
llvm::DWARFContext& context = unit->getContext();
const llvm::DWARFObject& object = context.getDWARFObj();
const llvm::DWARFSection& debug_loc_section = object.getLocSection();
if (debug_loc_section.Data.empty()) {
// LLVM dumpLocation() falls back on the DWARFObject::getLocDWOSection() call in this case. We
// don't support DWOs yet so just fail.
return VariableLocation();
}
auto offset_or = form.getAsSectionOffset();
if (!offset_or)
return VariableLocation();
if (*offset_or >= debug_loc_section.Data.size())
return VariableLocation(); // Off the end.
containers::array_view<uint8_t> section_data(
reinterpret_cast<const uint8_t*>(debug_loc_section.Data.data()),
debug_loc_section.Data.size());
// Interpret the resulting list.
auto base_address = const_cast<llvm::DWARFUnit*>(unit)->getBaseAddress();
return DecodeDwarf4LocationList(base_address ? base_address->Address : 0,
section_data.subview(*offset_or), source);
}
// Decodes a DWARF 5 location which is a DW_FORM_loclistx-type offset into the .debug_loclist
// section that contains the location list.
VariableLocation DecodeDwarf5LocationReference(llvm::DWARFUnit* unit,
const llvm::DWARFFormValue& form,
const UncachedLazySymbol& source) {
llvm::DWARFContext& context = unit->getContext();
const llvm::DWARFObject& object = context.getDWARFObj();
const llvm::DWARFSection& debug_loclists_section = object.getLoclistsSection();
if (debug_loclists_section.Data.empty()) {
// LLVM dumpLocation() falls back on the DWARFObject::getLocDWOSection() call in this case. We
// don't support DWOs yet so just fail.
return VariableLocation();
}
// Compute the byte offset into the .debug_loclists section of the location list.
uint64_t list_offset = 0;
switch (form.getForm()) {
case llvm::dwarf::DW_FORM_sec_offset: {
// A byte offset into the .debug_loclists section.
list_offset = *form.getAsSectionOffset();
break;
}
case llvm::dwarf::DW_FORM_loclistx: {
// The form value is an index into the unit's location list table.
uint64_t list_index = *form.getAsSectionOffset();
// The unit's DW_AT_loclists_base attribute specifies the byte offset within the
// .debug_loclists section of the unit's location list table. The getLoclistOffset() call
// combines the list_index and the unit's table offset, and reads the resulting offset to get
// the the byte offset of the location list we want relative to the .debug_loclists section.
auto list_offset_or = unit->getLoclistOffset(list_index);
if (!list_offset_or)
return VariableLocation();
list_offset = *list_offset_or;
break;
}
default: {
// Unsupported.
return VariableLocation();
}
}
containers::array_view<uint8_t> section_data(
reinterpret_cast<const uint8_t*>(debug_loclists_section.Data.data()),
debug_loclists_section.Data.size());
// Callback that converts an index into the .debug_addr table into the corresponding address.
fit::function<std::optional<TargetPointer>(uint64_t)> index_to_addr =
[unit](uint64_t index) -> std::optional<TargetPointer> {
auto sectioned_addr_or = unit->getAddrOffsetSectionItem(index);
if (!sectioned_addr_or)
return std::nullopt;
// We do not support any platforms with segmented addresses so only return the regular address.
return sectioned_addr_or->Address;
};
auto base_address = const_cast<llvm::DWARFUnit*>(unit)->getBaseAddress();
return DecodeDwarf5LocationList(base_address ? base_address->Address : 0,
section_data.subview(list_offset), index_to_addr, source);
}
} // namespace
VariableLocation DecodeVariableLocation(llvm::DWARFUnit* unit, const llvm::DWARFFormValue& form,
const UncachedLazySymbol& source) {
if (form.isFormClass(llvm::DWARFFormValue::FC_Block) ||
form.isFormClass(llvm::DWARFFormValue::FC_Exprloc)) {
// These forms are both a block of data which is interpreted as a DWARF expression. There is no
// validity range for this so assume the expression is valid as long as the variable is in
// scope.
llvm::ArrayRef<uint8_t> block = *form.getAsBlock();
return VariableLocation(DwarfExpr(std::vector<uint8_t>(block.begin(), block.end()), source));
}
if (unit->getVersion() < 5) {
// DWARF 4 location list.
return DecodeDwarf4LocationReference(unit, form, source);
}
// Assume everything newer is a DWARF 5 location list.
return DecodeDwarf5LocationReference(unit, form, source);
}
VariableLocation DecodeDwarf4LocationList(TargetPointer unit_base_addr,
containers::array_view<uint8_t> data,
const UncachedLazySymbol& source) {
DataExtractor ext(data);
std::vector<VariableLocation::Entry> entries;
// These location list begin and end values are "relative to the applicable base address of the
// compilation unit referencing this location list."
//
// The "applicable base address" is either the unit's base address, or, if there was a "base
// address selection entry", the nearest preceeding one.
//
// This value tracks the current applicable base address.
TargetPointer base_address = unit_base_addr;
// Base address selection entries are identifier by a start address with the max value.
constexpr TargetPointer kBaseAddressSelectionTag = std::numeric_limits<TargetPointer>::max();
while (!ext.done()) {
auto begin = ext.Read<TargetPointer>();
if (!begin)
return VariableLocation();
auto end = ext.Read<TargetPointer>();
if (!end)
return VariableLocation();
if (*begin == kBaseAddressSelectionTag) {
// New base address, read it and we're done with this entry.
base_address = *end;
continue;
}
if (*begin == 0 && *end == 0)
break; // End-of-list entry.
// Non-"base address selection entries" are followed by a 2-byte length, followed by the
// DWARF expression of that length.
auto expression_len = ext.Read<uint16_t>();
if (!expression_len)
return VariableLocation();
// Empty expressions are valid and just mean to skip this entry, so we don't bother adding it.
if (*expression_len == 0)
continue;
// Expression data.
std::vector<uint8_t> expression;
expression.resize(*expression_len);
if (!ext.ReadBytes(*expression_len, expression.data()))
return VariableLocation();
if (*begin >= *end)
continue; // Invalid or empty range, don't add/
VariableLocation::Entry& dest = entries.emplace_back();
dest.range = AddressRange(base_address + *begin, base_address + *end);
dest.expression = DwarfExpr(std::move(expression), source);
}
return VariableLocation(std::move(entries));
}
VariableLocation DecodeDwarf5LocationList(
TargetPointer unit_base_addr, containers::array_view<uint8_t> data,
fit::function<std::optional<TargetPointer>(uint64_t)>& index_to_addr,
const UncachedLazySymbol& source) {
DataExtractor ext(data);
std::vector<VariableLocation::Entry> entries;
// The offset_pair type uses addresses relative to the closest preceding base address in the same
// location lis. It defaults to the compilation unit's base address if there is no explicit one.
//
// This value tracks the current applicable base address.
TargetPointer base_address = unit_base_addr;
// The default location expression, if found.
std::optional<DwarfExpr> default_expr;
while (!ext.done()) {
// The first byte of the location list entry is the entry kind.
auto type_or = ext.Read<uint8_t>();
if (!type_or)
return VariableLocation();
switch (*type_or) {
case llvm::dwarf::DW_LLE_end_of_list: {
return VariableLocation(std::move(entries), std::move(default_expr));
}
case llvm::dwarf::DW_LLE_base_address: {
// One target address operand that indicates the new base address.
auto new_base_or = ext.Read<TargetPointer>();
if (!new_base_or)
return VariableLocation();
base_address = *new_base_or;
break;
}
case llvm::dwarf::DW_LLE_base_addressx: {
// Like base_address but the operand us a ULEB128 index into the .debug_addr section
// that indicates the new base address.
auto new_base_i_or = ext.ReadUleb128();
if (!new_base_i_or)
return VariableLocation();
// Convert index to address.
auto new_base_or = index_to_addr(*new_base_i_or);
if (!new_base_or)
return VariableLocation();
base_address = *new_base_or;
break;
}
case llvm::dwarf::DW_LLE_start_end: {
// Two target address operands of the start and end address of the entry. Followed by a
// counted location description for that range.
auto start_or = ext.Read<TargetPointer>();
auto end_or = ext.Read<TargetPointer>();
if (!start_or || !end_or ||
!AppendCountedLocationDescriptionEntry(*start_or, *end_or, ext, entries, source))
return VariableLocation();
break;
}
case llvm::dwarf::DW_LLE_startx_endx: {
// Like start_end but the two operands are ULEB128 indices into the .debug_addr section that
// indicates the start and end addresses of the entry.
auto start_i_or = ext.ReadUleb128();
auto end_i_or = ext.ReadUleb128();
if (!start_i_or || !end_i_or)
return VariableLocation();
// Convert indices to addresses.
auto start_or = index_to_addr(*start_i_or);
auto end_or = index_to_addr(*end_i_or);
if (!start_or || !end_or ||
!AppendCountedLocationDescriptionEntry(*start_or, *end_or, ext, entries, source))
return VariableLocation();
break;
}
case llvm::dwarf::DW_LLE_start_length: {
// One target address operand of the start, and a ULEB length that indicates the address
// range of the entry. Followed by a counted location description.
auto start_or = ext.Read<TargetPointer>();
auto length_or = ext.ReadUleb128();
if (!start_or || !length_or ||
!AppendCountedLocationDescriptionEntry(*start_or, *start_or + *length_or, ext, entries,
source))
return VariableLocation();
break;
}
case llvm::dwarf::DW_LLE_startx_length: {
// Like start_length but the first operand is a ULEB128 index into the .debug_addr section.
auto start_i_or = ext.ReadUleb128();
if (!start_i_or)
return VariableLocation();
// Convert index to address.
auto start_or = index_to_addr(*start_i_or);
auto length_or = ext.ReadUleb128();
if (!start_or || !length_or ||
!AppendCountedLocationDescriptionEntry(*start_or, *start_or + *length_or, ext, entries,
source))
return VariableLocation();
break;
}
case llvm::dwarf::DW_LLE_offset_pair: {
// Two ULEB128 operands indicating offsets from the base_address of the range of the entry.
// Followed by a counted location description.
auto start_off_or = ext.ReadUleb128();
auto end_off_or = ext.ReadUleb128();
if (!start_off_or || !end_off_or ||
!AppendCountedLocationDescriptionEntry(
base_address + *start_off_or, base_address + *end_off_or, ext, entries, source))
return VariableLocation();
break;
}
case llvm::dwarf::DW_LLE_default_location: {
// A counted location description that applies when no other ranges do.
default_expr = ExtractCountedLocationDescription(ext, source);
if (!default_expr)
return VariableLocation(); // Default expression was corrupt.
break;
}
}
}
// Got to the end of the data without seeing an end-of-list marker, declare corrupt.
return VariableLocation();
}
} // namespace zxdb