blob: 14948fd7a641eadfe455537b053bfc9acfeec626 [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/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"
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()->AsFunction();
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()->AsFunction();
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;
}
// 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.
//
// If expand_after_line_0 is set, if a query returns "line 0" from the line table (compiler
// generated code), it will consider the code immediately following it, even if that code is outside
// of the input range. This is used when the range being queried is open-ended and you just want
// all contiguous matching lines starting at a given address without being tripped up by a "line 0"
// entry at the beginning.
//
// 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 expand_after_line_0,
AddressRanges::RangeVector* out) {
// Sanity threshold to avoid doing too many queries if the symbols are corrupt or very different
// than we expect.
constexpr uint64_t kMaxRangeSize = 1024;
bool allow_next_query_out_of_range = false; // Used to implement expand_after_line_0. See above.
TargetPointer cur = range.begin();
while (allow_next_query_out_of_range ||
(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);
allow_next_query_out_of_range = line_details.file_line().line() == 0 && expand_after_line_0;
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. Since the Append... funciton counts
// any ranges beginning in the range we give it, we only need to supply a 1-byte range.
TargetPointer end_inline_address =
inline_ranges.empty() ? loc.address() : inline_ranges.back().end();
AppendAddressRangesForLineInRange(process, loc.file_line(),
AddressRange(end_inline_address, end_inline_address + 1), 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_ipc::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.call_dest && ranges.InRange(row.address)) {
auto& call = result.emplace_back();
call.call_addr = row.address;
call.call_dest = *row.call_dest;
}
}
return result;
}
} // namespace zxdb