blob: b28b3b67093face8c04dea1461514ac50fab1cda [file] [log] [blame]
// Copyright 2018 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 "garnet/bin/zxdb/symbols/find_line.h"
#include "garnet/bin/zxdb/symbols/line_table.h"
#include "lib/fxl/logging.h"
#include "llvm/DebugInfo/DWARF/DWARFUnit.h"
namespace zxdb {
namespace {
enum class FileChecked { kUnchecked = 0, kMatch, kNoMatch };
} // namespace
std::vector<LineMatch> GetAllLineTableMatchesInUnit(
const LineTable& line_table, const std::string& full_path, int line) {
std::vector<LineMatch> result;
// The file table usually has a bunch of entries not referenced by the line
// table (these are usually for declarations of things).
std::vector<FileChecked> checked;
checked.resize(line_table.GetNumFileNames(), FileChecked::kUnchecked);
// Once we find a file match, assume there aren't any others so we don't need
// to keep looking up file names.
bool file_match_found = false;
// The |best_line| is the line number of the smallest line in the file
// we've found >= to the search line. The |result| contains all lines
// we've encountered in the unit so far that match this.
constexpr int kWorstLine = std::numeric_limits<int>::max();
int best_line = kWorstLine;
// Rows in the line table.
for (const llvm::DWARFDebugLine::Row& row : line_table.GetRows()) {
// EndSequence doesn't correspond to a line. Its purpose is to mark invalid
// code regions (say, padding between functions). Because of the format
// of the table, it will duplicate the line and column numbers from the
// previous row so it looks valid, but these are meaningless. Skip these
// rows.
if (!row.IsStmt || row.EndSequence)
continue;
auto file_id = row.File; // 1-based!
if (file_id < 1 && file_id > checked.size())
continue; // Symbols are corrupt.
auto file_index = file_id - 1; // 0-based for indexing into array.
if (!file_match_found && checked[file_index] == FileChecked::kUnchecked) {
// Look up effective file name and see if it's a match.
if (auto file_name = line_table.GetFileNameByIndex(file_id)) {
if (full_path == *file_name) {
file_match_found = true;
checked[file_index] = FileChecked::kMatch;
} else {
checked[file_index] = FileChecked::kNoMatch;
}
} else {
checked[file_index] = FileChecked::kNoMatch;
}
}
if (checked[file_index] == FileChecked::kMatch) {
int row_line = static_cast<int>(row.Line);
if (line <= row_line) {
// All lines >= to the line in question are possibilities.
if (row_line < best_line) {
// Found a new best match, clear all existing ones.
best_line = row_line;
result.clear();
}
if (row_line == best_line) {
// Accumulate all matching results.
auto subroutine = line_table.GetSubroutineForRow(row);
result.emplace_back(
row.Address, row_line,
subroutine.isValid() ? subroutine.getOffset() : 0);
}
}
}
}
return result;
}
std::vector<LineMatch> GetBestLineMatches(
const std::vector<LineMatch>& matches) {
// The lowest line is tbe "best" match because GetAllLineTableMatchesInUnit()
// returns the next row for all pairs that cross the line in question. The
// lowest of the "next" rows will be the closest line.
auto min_elt_iter = std::min_element(
matches.begin(), matches.end(),
[](const LineMatch& a, const LineMatch& b) { return a.line < b.line; });
// This will be populated with all matches for the line equal to the best
// one (one line can match many addresses depending on inlining and code
// reodering).
//
// We only want one per inlined function instance. One function can have a
// line split into multiple line entries (possibly disjoint or not) and we
// want only the first one (by address). But if the same helper is inlined
// into many places (or even twice into the same function), we want to catch
// all of those places.
//
// By indexing by the [inlined] subroutine DIE offset, we can ensure there
// is only one match per subroutine, and resolve collisions by address.
std::map<uint32_t, size_t> die_to_match_index;
for (size_t i = 0; i < matches.size(); i++) {
const LineMatch& match = matches[i];
if (match.line != min_elt_iter->line)
continue; // Not a match.
auto existing = die_to_match_index.find(match.function_die_offset);
if (existing == die_to_match_index.end()) {
// New entry for this function.
die_to_match_index[match.function_die_offset] = i;
} else {
// Duplicate in the same function, pick the lowest address.
const LineMatch& existing_match = matches[existing->second];
if (match.address < existing_match.address)
die_to_match_index[match.function_die_offset] = i; // New one better.
}
}
// Convert back to a result vector.
std::vector<LineMatch> result;
result.reserve(die_to_match_index.size());
for (const auto & [ die, match_index ] : die_to_match_index)
result.push_back(matches[match_index]);
return result;
}
} // namespace zxdb