| // 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 "src/developer/debug/zxdb/symbols/find_line.h" |
| |
| #include <lib/syslog/cpp/macros.h> |
| |
| #include "llvm/DebugInfo/DWARF/DWARFUnit.h" |
| #include "src/developer/debug/zxdb/symbols/function.h" |
| #include "src/developer/debug/zxdb/symbols/line_table.h" |
| #include "src/developer/debug/zxdb/symbols/symbol_context.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). |
| // |
| // The extra "+1" is required because the file name indices count from 1. The 0-index file name |
| // index implicitly takes the file name from the compilation unit. |
| std::vector<FileChecked> checked; |
| checked.resize(line_table.GetNumFileNames() + 1, FileChecked::kUnchecked); |
| |
| // 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. |
| size_t num_sequences = line_table.GetNumSequences(); |
| for (size_t sequence_i = 0; sequence_i < num_sequences; sequence_i++) { |
| containers::array_view<llvm::DWARFDebugLine::Row> sequence = |
| line_table.GetSequenceAt(sequence_i); |
| |
| for (const auto& row : sequence) { |
| if (!row.IsStmt || row.EndSequence) |
| continue; |
| |
| auto file_id = row.File; |
| if (file_id >= checked.size()) |
| continue; // Symbols are corrupt. |
| |
| // Note: sometimes the same file can be encoded multiple times or in different ways in the |
| // same line table, so don't assume just because we found it that no other files match. |
| if (checked[file_id] == 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) { |
| checked[file_id] = FileChecked::kMatch; |
| } else { |
| checked[file_id] = FileChecked::kNoMatch; |
| } |
| } else { |
| checked[file_id] = FileChecked::kNoMatch; |
| } |
| } |
| |
| if (checked[file_id] == 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.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<uint64_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; |
| } |
| |
| size_t GetFunctionPrologueSize(const LineTable& line_table, const Function* function) { |
| const AddressRanges& code_ranges = function->code_ranges(); |
| if (code_ranges.empty()) |
| return 0; |
| uint64_t code_range_begin = code_ranges.front().begin(); |
| |
| // The function and line table are all defined in terms of relative addresses. |
| SymbolContext rel_context = SymbolContext::ForRelativeAddresses(); |
| |
| LineTable::FoundRow found = line_table.GetRowForAddress(rel_context, code_range_begin); |
| if (found.empty()) |
| return 0; |
| size_t first_row = found.index; |
| |
| // Give up after this many line table entries. If prologue_end isn't found by then, assume there's |
| // no specifically marked prologue. Normally it will be the 2nd entry. |
| constexpr size_t kMaxSearchCount = 4; |
| |
| // Search for a line in the function with |prologue_end| explicitly marked. |
| size_t prologue_end_index = first_row; |
| bool found_marked_end = false; |
| for (size_t i = 0; i < kMaxSearchCount && first_row + i < found.sequence.size(); i++) { |
| if (!code_ranges.InRange(found.sequence[first_row + i].Address.Address)) |
| break; // Outside the function. |
| |
| if (found.sequence[first_row + i].PrologueEnd) { |
| // Found match. |
| prologue_end_index = first_row + i; |
| found_marked_end = true; |
| break; |
| } |
| } |
| |
| if (!found_marked_end) { |
| // GCC doesn't seem to generate prologue_end annotations in many cases. There, the first line |
| // table entry row is interpreted as the prologue so the end is the following one. |
| if (prologue_end_index < found.sequence.size() - 1) |
| prologue_end_index++; |
| } |
| |
| // There can be compiler-generated code immediately following the prologue annotated by "line 0". |
| // Count this as prologue also. |
| while (prologue_end_index < found.sequence.size() && found.sequence[prologue_end_index].Line == 0) |
| prologue_end_index++; |
| |
| // Sanity check: None of those previous operations should have left us outside of the function's |
| // code or outside of a known instruction (there's an end_sequence marker). If it did, this line |
| // table looks different than we expect and we don't report a prologue. |
| if (!code_ranges.InRange(found.sequence[prologue_end_index].Address.Address) || |
| found.sequence[prologue_end_index].EndSequence) |
| return 0; |
| |
| return found.sequence[prologue_end_index].Address.Address - code_range_begin; |
| } |
| |
| } // namespace zxdb |