| // 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/client/substatement.h" | |
| #include <lib/syslog/cpp/macros.h> | |
| #include <algorithm> | |
| #include "src/developer/debug/shared/message_loop.h" | |
| #include "src/developer/debug/zxdb/client/arch_info.h" | |
| #include "src/developer/debug/zxdb/client/disassembler.h" | |
| #include "src/developer/debug/zxdb/client/memory_dump.h" | |
| #include "src/developer/debug/zxdb/client/process.h" | |
| #include "src/developer/debug/zxdb/client/session.h" | |
| #include "src/developer/debug/zxdb/symbols/line_details.h" | |
| #include "src/developer/debug/zxdb/symbols/location.h" | |
| #include "src/developer/debug/zxdb/symbols/process_symbols.h" | |
| // Because of the way GDB works, Clang and GCC both emit separate "statements" for each line of | |
| // a multiline conditional. We would prefer if DWARF line table "IsStmt" entries mapped to | |
| // language statements. | |
| // | |
| // As a result, out substatement extraction only works on a single line. If you have a complex | |
| // multiline statement, each line of that will be separate and to get to the substatement you want | |
| // you'll have to first step to the right line. | |
| // | |
| // Typically (at least in debug mode), statements are executed "bottom up". For a 3-line statement, | |
| // there will be line entries for line 1 (initial stuff), then 3, 2, and back to 1 again. We could | |
| // try to be smarter and consider all statements in between two references of the same line, or | |
| // going backwards, as part of the same toplevel statement. This would allow us to handle these | |
| // unoptimized multiline C/C++ statements better. But optimized code would become much less | |
| // predictable and we'll have to test carefully. | |
| namespace zxdb { | |
| namespace { | |
| // Appends all inline functions which start in the given location. The Location should identify both | |
| // the address and the block the address is contained in. The result will be sorted by call address. | |
| std::vector<SubstatementCall> GetInlineCallsForLocation(const ProcessSymbols* symbols, | |
| const Location& loc) { | |
| std::vector<SubstatementCall> result; | |
| const Function* func = loc.symbol().Get()->As<Function>(); | |
| if (!func) | |
| return result; | |
| // Move to the deepest code block for the address in question. Don't go into inlines since we're | |
| // currently going to search for the inline calls in the range. | |
| const CodeBlock* block = func->GetMostSpecificChild(loc.symbol_context(), loc.address(), false); | |
| if (!block) | |
| return result; | |
| TargetPointer relative_address = loc.symbol_context().AbsoluteToRelative(loc.address()); | |
| // Check for inlines that are children of the current block for ones that start at the current | |
| // line. | |
| for (const auto& child : block->inner_blocks()) { | |
| const Function* call = child.Get()->As<Function>(); | |
| if (!call || !call->is_inline()) | |
| continue; | |
| if (call->code_ranges().empty()) | |
| continue; // No code for this call, not sure why. | |
| // To count as a call from the current line, the call must start after the current address | |
| // (the user could have stepped half through a bunch of inlines and we don't want to show the | |
| // ones already passed), and the call line must match. | |
| TargetPointer relative_call_addr = call->code_ranges()[0].begin(); | |
| if (relative_call_addr >= relative_address && call->call_line() == loc.file_line()) { | |
| // This inline starts in the address range, count it. | |
| SubstatementCall& added = result.emplace_back(); | |
| added.call_addr = loc.symbol_context().RelativeToAbsolute(relative_call_addr); | |
| added.call_dest = added.call_addr; | |
| added.inline_call = RefPtrTo(call); | |
| } | |
| } | |
| std::sort(result.begin(), result.end()); | |
| return result; | |
| } | |
| // Sanity threshold to avoid doing too many queries if the symbols are corrupt, very different | |
| // than we expect, or exceptionally long. This is in bytes. | |
| constexpr uint64_t kMaxRangeSize = 1024; | |
| // Checks all addresses in the given address range and adds the ranges that map to the given | |
| // file_line to the output. This will also check for ranges that begin in the range but end outside | |
| // of it. | |
| // | |
| // The stop_on_no_match flag indicates that adding line entries should stop as soon as a line is | |
| // found that doesn't match the guven line (excepting compiler-generated "line 0" entries). This | |
| // is used to greedily add all matching ranges. | |
| // | |
| // This just does many individual queries. This could be done faster using the line table directly | |
| // since then we can go through it linearly for the range we care about. But that approach makes the | |
| // querying more complex and so far this has not shown to be too slow. | |
| void AppendAddressRangesForLineInRange(Process* process, const FileLine& file_line, | |
| const AddressRange& range, bool stop_on_no_match, | |
| AddressRanges::RangeVector* out) { | |
| TargetPointer cur = range.begin(); | |
| while (cur < range.end() && cur - range.begin() < kMaxRangeSize) { | |
| LineDetails line_details = process->GetSymbols()->LineDetailsForAddress(cur); | |
| if (!line_details.is_valid()) | |
| return; | |
| AddressRange extent = line_details.GetExtent(); | |
| if (extent.empty()) | |
| return; | |
| if (line_details.file_line() == file_line) { | |
| out->push_back(extent); | |
| } else if (line_details.file_line().line() != 0 && stop_on_no_match) { | |
| return; // Found a non-matching line, done. | |
| } | |
| cur = extent.end(); | |
| } | |
| } | |
| } // namespace | |
| AddressRange GetAddressRangeForLineSubstatements(Process* process, const Location& loc); | |
| void GetSubstatementCallsForLine( | |
| Process* process, const Location& loc, | |
| fit::function<void(const Err&, std::vector<SubstatementCall>)> cb) { | |
| std::vector<SubstatementCall> inlines = GetInlineCallsForLocation(process->GetSymbols(), loc); | |
| // Each inline can have multiple non-contiguous ranges, possibly interleaved with other inline | |
| // calls. We need to consider the code not covered by any inline, so extract all inline ranges | |
| // into one structure. The inline ranges should not overlap since each of these inlines is at the | |
| // same lexicial scope. | |
| AddressRanges::RangeVector inline_range_vector; | |
| for (const auto& inline_call : inlines) { | |
| // These code ranges will be module-relative addresses. | |
| const auto& cur_ranges = inline_call.inline_call->code_ranges(); | |
| inline_range_vector.insert(inline_range_vector.end(), cur_ranges.begin(), cur_ranges.end()); | |
| } | |
| // This representation of all the inline ranges is sorted absolute addresses. | |
| AddressRanges inline_ranges = loc.symbol_context().RelativeToAbsolute( | |
| AddressRanges(AddressRanges::kNonCanonical, std::move(inline_range_vector))); | |
| // The code ranges we care about are all the bits between the inline functions we just identified | |
| // that map the current file/line. | |
| AddressRanges::RangeVector line_code_ranges; | |
| // Check the range in between each inline. Count starting from the current address. | |
| TargetPointer prev_end = loc.address(); | |
| for (size_t i = 0; i < inline_ranges.size(); i++) { | |
| AddressRange cur_range(prev_end, inline_ranges[i].begin()); | |
| if (!cur_range.empty()) { | |
| AppendAddressRangesForLineInRange(process, loc.file_line(), cur_range, false, | |
| &line_code_ranges); | |
| } | |
| prev_end = inline_ranges[i].end(); | |
| } | |
| // The address immediately following the last inline call also counts as a place to query since | |
| // the last inline could be followed by a function call on the same line. If there are no inlines, | |
| // this location will just be the code range we're querying. We query from there to the end of | |
| // the enclosing function, but tell the Append... function to stop as soon as it finds a | |
| // non-matching line entry. | |
| TargetPointer end_inline_address = | |
| inline_ranges.empty() ? loc.address() : inline_ranges.back().end(); | |
| // Compute the end of the function to know where to stop searching. | |
| TargetPointer function_end = end_inline_address + 1; // Default to querying one byte. | |
| if (const Function* func = loc.symbol().Get()->As<Function>()) { | |
| // There can be more than one discontiguous address range for the function, use the one | |
| // that contains the address we're starting the query from. It's theoretically possible the | |
| // range we want to query covers a discontiguous memory region, but ignore that case since it | |
| // makes everything much more complicated. | |
| AddressRanges function_ranges = func->GetAbsoluteCodeRanges(loc.symbol_context()); | |
| if (std::optional<AddressRange> range = function_ranges.GetRangeContaining(end_inline_address)) | |
| function_end = range->end(); | |
| } | |
| AppendAddressRangesForLineInRange(process, loc.file_line(), | |
| AddressRange(end_inline_address, function_end), true, | |
| &line_code_ranges); | |
| // Make all of the matching ranges in a canonical form. | |
| AddressRanges line_code(AddressRanges::kNonCanonical, std::move(line_code_ranges)); | |
| if (line_code.empty()) { | |
| // No code for this line to disassemble. All we have are the inlines (if any). | |
| debug::MessageLoop::Current()->PostTask( | |
| FROM_HERE, [cb = std::move(cb), inlines = std::move(inlines)]() mutable { | |
| cb(Err(), std::move(inlines)); | |
| }); | |
| return; | |
| } | |
| AddressRange extent = line_code.GetExtent(); | |
| process->ReadMemory( | |
| extent.begin(), extent.size(), | |
| [arch_info = &process->session()->arch_info(), | |
| weak_symbols = process->GetSymbols()->GetWeakPtr(), loc, line_code = std::move(line_code), | |
| inlines = std::move(inlines), cb = std::move(cb)](const Err& in_err, MemoryDump dump) { | |
| if (in_err.has_error()) | |
| return cb(in_err, {}); | |
| if (!weak_symbols) | |
| return cb(Err("Process destroyed."), {}); | |
| std::vector<SubstatementCall> result = | |
| GetSubstatementCallsForMemory(arch_info, weak_symbols.get(), loc, line_code, dump); | |
| // Merge in inline calls. | |
| result.insert(result.end(), inlines.begin(), inlines.end()); | |
| std::sort(result.begin(), result.end()); | |
| cb(Err(), std::move(result)); | |
| }); | |
| } | |
| std::vector<SubstatementCall> GetSubstatementCallsForMemory(const ArchInfo* arch_info, | |
| const ProcessSymbols* symbols, | |
| const Location& loc, | |
| const AddressRanges& ranges, | |
| const MemoryDump& mem) { | |
| Disassembler disassembler; | |
| if (disassembler.Init(arch_info).has_error()) | |
| return {}; | |
| Disassembler::Options options; | |
| std::vector<Disassembler::Row> rows; | |
| disassembler.DisassembleDump(mem, mem.address(), options, 0, &rows); | |
| std::vector<SubstatementCall> result; | |
| for (const auto& row : rows) { | |
| if ((row.type == Disassembler::InstructionType::kCallDirect || | |
| row.type == Disassembler::InstructionType::kCallIndirect) && | |
| ranges.InRange(row.address)) { | |
| auto& call = result.emplace_back(); | |
| call.call_addr = row.address; | |
| call.call_dest = row.call_dest; // Will be nullopt for indirect calls. | |
| } | |
| } | |
| return result; | |
| } | |
| } // namespace zxdb |