blob: 473aaf79de9fadf41ca357951fe9384e50456c70 [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 "src/developer/debug/zxdb/console/console_context.h"
#include <inttypes.h>
#include <lib/syslog/cpp/macros.h>
#include "src/developer/debug/zxdb/client/breakpoint.h"
#include "src/developer/debug/zxdb/client/filter.h"
#include "src/developer/debug/zxdb/client/frame.h"
#include "src/developer/debug/zxdb/client/process.h"
#include "src/developer/debug/zxdb/client/session.h"
#include "src/developer/debug/zxdb/client/setting_schema_definition.h"
#include "src/developer/debug/zxdb/client/source_file_provider_impl.h"
#include "src/developer/debug/zxdb/client/symbol_server.h"
#include "src/developer/debug/zxdb/client/target.h"
#include "src/developer/debug/zxdb/client/thread.h"
#include "src/developer/debug/zxdb/console/command.h"
#include "src/developer/debug/zxdb/console/command_utils.h"
#include "src/developer/debug/zxdb/console/console.h"
#include "src/developer/debug/zxdb/console/format_context.h"
#include "src/developer/debug/zxdb/console/format_exception.h"
#include "src/developer/debug/zxdb/console/format_location.h"
#include "src/developer/debug/zxdb/console/format_node_console.h"
#include "src/developer/debug/zxdb/console/format_target.h"
#include "src/developer/debug/zxdb/console/output_buffer.h"
#include "src/developer/debug/zxdb/symbols/location.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace zxdb {
namespace {
// We want to display full information for some exceptions like page faults, but debugger exceptions
// like single step and debug breakpoint exceptions don't need thhe full treatment to reduce noice
// when stepping.
bool ShouldDisplayFullExceptionInfo(const StopInfo& info) {
if (info.exception_type == debug_ipc::ExceptionType::kNone ||
info.exception_type == debug_ipc::ExceptionType::kHardwareBreakpoint ||
info.exception_type == debug_ipc::ExceptionType::kSoftwareBreakpoint ||
info.exception_type == debug_ipc::ExceptionType::kWatchpoint ||
info.exception_type == debug_ipc::ExceptionType::kSingleStep ||
info.exception_type == debug_ipc::ExceptionType::kSynthetic)
return false;
return true;
}
} // namespace
ConsoleContext::ConsoleContext(Session* session) : session_(session) {
session->AddObserver(this);
session->AddDownloadObserver(this);
session->AddBreakpointObserver(this);
session->target_observers().AddObserver(this);
session->process_observers().AddObserver(this);
session->thread_observers().AddObserver(this);
session->system().AddObserver(this);
// Pick up any previously created targets. This will normally just be the
// default one.
for (Target* target : session->system().GetTargets())
DidCreateTarget(target);
for (Job* job : session->system().GetJobs())
DidCreateJob(job);
for (SymbolServer* symbol_server : session->system().GetSymbolServers())
DidCreateSymbolServer(symbol_server);
pretty_stack_manager_ = fxl::MakeRefCounted<PrettyStackManager>();
// TODO(bug 43549) this should be loaded from a configuration file somehow associated with the
// user's build instead of being hardcoded. This call can then be deleted.
pretty_stack_manager_->LoadDefaultMatchers();
}
ConsoleContext::~ConsoleContext() {
// Unregister for all observers.
session_->system().RemoveObserver(this);
session_->target_observers().RemoveObserver(this);
session_->process_observers().RemoveObserver(this);
session_->thread_observers().RemoveObserver(this);
session_->RemoveBreakpointObserver(this);
session_->RemoveDownloadObserver(this);
session_->RemoveObserver(this);
}
int ConsoleContext::IdForTarget(const Target* target) const {
const auto& found = target_to_id_.find(target);
if (found == target_to_id_.end()) {
FX_NOTREACHED();
return 0;
}
return found->second;
}
int ConsoleContext::IdForJob(const Job* job) const {
const auto& found = job_to_id_.find(job);
if (found == job_to_id_.end()) {
FX_NOTREACHED();
return 0;
}
return found->second;
}
int ConsoleContext::IdForThread(const Thread* thread) const {
const TargetRecord* record =
const_cast<ConsoleContext*>(this)->GetTargetRecord(thread->GetProcess()->GetTarget());
if (!record)
return 0;
auto found = record->thread_to_id.find(thread);
if (found == record->thread_to_id.end()) {
FX_NOTREACHED();
return 0;
}
return found->second;
}
int ConsoleContext::IdForFrame(const Frame* frame) const {
// Find the frame in the thread's backtrace. We don't have to worry about
// whether the frames have been synced, since if there is a frame here,
// we know it's present in the thread's list.
Thread* thread = frame->GetThread();
const Stack& stack = thread->GetStack();
for (size_t i = 0; i < stack.size(); i++) {
if (stack[i] == frame)
return static_cast<int>(i);
}
FX_NOTREACHED(); // Should have found the frame.
return 0;
}
int ConsoleContext::IdForSymbolServer(const SymbolServer* symbol_server) const {
const auto& found = symbol_server_to_id_.find(symbol_server);
if (found == symbol_server_to_id_.end()) {
FX_NOTREACHED();
return 0;
}
return found->second;
}
int ConsoleContext::IdForBreakpoint(const Breakpoint* breakpoint) const {
FX_DCHECK(!breakpoint->IsInternal())
<< "Should not be trying to get the ID of internal breakpoints. The "
"client layer should filter these out.";
auto found = breakpoint_to_id_.find(breakpoint);
if (found == breakpoint_to_id_.end()) {
FX_NOTREACHED();
return 0;
}
return found->second;
}
int ConsoleContext::IdForFilter(const Filter* filter) const {
auto found = filter_to_id_.find(filter);
if (found == filter_to_id_.end()) {
FX_NOTREACHED();
return 0;
}
return found->second;
}
void ConsoleContext::SetActiveJob(const Job* job) {
auto found = job_to_id_.find(job);
if (found == job_to_id_.end()) {
FX_NOTREACHED();
return;
}
active_job_id_ = found->second;
}
int ConsoleContext::GetActiveJobId() const { return active_job_id_; }
Job* ConsoleContext::GetActiveJob() const {
auto found = id_to_job_.find(active_job_id_);
if (found == id_to_job_.end())
return nullptr;
return found->second.job;
}
void ConsoleContext::SetActiveTarget(const Target* target) {
auto found = target_to_id_.find(target);
if (found == target_to_id_.end()) {
FX_NOTREACHED();
return;
}
active_target_id_ = found->second;
}
int ConsoleContext::GetActiveTargetId() const { return active_target_id_; }
Target* ConsoleContext::GetActiveTarget() const {
auto found = id_to_target_.find(active_target_id_);
if (found == id_to_target_.end())
return nullptr;
return found->second.target;
}
void ConsoleContext::SetActiveSymbolServer(const SymbolServer* symbol_server) {
auto found = symbol_server_to_id_.find(symbol_server);
if (found == symbol_server_to_id_.end()) {
FX_NOTREACHED();
return;
}
active_symbol_server_id_ = found->second;
}
int ConsoleContext::GetActiveSymbolServerId() const { return active_symbol_server_id_; }
SymbolServer* ConsoleContext::GetActiveSymbolServer() const {
auto found = id_to_symbol_server_.find(active_symbol_server_id_);
if (found == id_to_symbol_server_.end())
return nullptr;
return found->second;
}
void ConsoleContext::SetActiveThreadForTarget(const Thread* thread) {
TargetRecord* record = GetTargetRecord(thread->GetProcess()->GetTarget());
if (!record)
return;
auto found = record->thread_to_id.find(thread);
if (found == record->thread_to_id.end()) {
FX_NOTREACHED();
return;
}
record->active_thread_id = found->second;
}
int ConsoleContext::GetActiveThreadIdForTarget(const Target* target) {
const TargetRecord* record = GetTargetRecord(target);
if (!record) {
FX_NOTREACHED();
return 0;
}
return record->active_thread_id;
}
Thread* ConsoleContext::GetActiveThreadForTarget(const Target* target) {
const TargetRecord* record = GetTargetRecord(target);
if (!record) {
FX_NOTREACHED();
return nullptr;
}
auto found = record->id_to_thread.find(record->active_thread_id);
if (found == record->id_to_thread.end())
return nullptr;
return found->second.thread;
}
void ConsoleContext::SetActiveFrameForThread(const Frame* frame) {
ThreadRecord* record = GetThreadRecord(frame->GetThread());
if (!record) {
FX_NOTREACHED();
return;
}
record->active_frame_id = IdForFrame(frame);
}
void ConsoleContext::SetActiveFrameIdForThread(const Thread* thread, int id) {
ThreadRecord* record = GetThreadRecord(thread);
if (!record) {
FX_NOTREACHED();
return;
}
record->active_frame_id = id;
}
int ConsoleContext::GetActiveFrameIdForThread(const Thread* thread) const {
const ThreadRecord* record = GetThreadRecord(thread);
if (!record) {
FX_NOTREACHED();
return 0;
}
// Should be a valid frame index in the thread (or no frames and == 0).
FX_DCHECK((thread->GetStack().empty() && record->active_frame_id == 0) ||
(record->active_frame_id >= 0 &&
record->active_frame_id < static_cast<int>(thread->GetStack().size())));
return record->active_frame_id;
}
void ConsoleContext::SetActiveBreakpoint(const Breakpoint* breakpoint) {
int id = IdForBreakpoint(breakpoint);
if (id != 0)
active_breakpoint_id_ = id;
}
int ConsoleContext::GetActiveBreakpointId() const { return active_breakpoint_id_; }
Breakpoint* ConsoleContext::GetActiveBreakpoint() const {
if (active_breakpoint_id_ == 0)
return nullptr;
auto found = id_to_breakpoint_.find(active_breakpoint_id_);
if (found == id_to_breakpoint_.end()) {
FX_NOTREACHED();
return nullptr;
}
return found->second;
}
void ConsoleContext::SetActiveFilter(const Filter* filter) {
int id = IdForFilter(filter);
if (id != 0)
active_filter_id_ = id;
}
int ConsoleContext::GetActiveFilterId() const { return active_filter_id_; }
Filter* ConsoleContext::GetActiveFilter() const {
if (active_filter_id_ == 0)
return nullptr;
auto found = id_to_filter_.find(active_filter_id_);
if (found == id_to_filter_.end()) {
FX_NOTREACHED();
return nullptr;
}
return found->second;
}
SourceAffinity ConsoleContext::GetSourceAffinityForThread(const Thread* thread) const {
const ThreadRecord* record = GetThreadRecord(thread);
if (!record)
return SourceAffinity::kSource;
return record->source_affinity;
}
void ConsoleContext::SetSourceAffinityForThread(const Thread* thread,
SourceAffinity source_affinity) {
if (source_affinity == SourceAffinity::kNone)
return; // Don't change anything, previous command still stands.
ThreadRecord* record = GetThreadRecord(thread);
if (!record)
return;
record->source_affinity = source_affinity;
}
void ConsoleContext::OutputThreadContext(const Thread* thread, const StopInfo& info) const {
Target* target = thread->GetProcess()->GetTarget();
Console* console = Console::get();
OutputBuffer out;
if (ShouldDisplayFullExceptionInfo(info)) {
out.Append(FormatException(this, thread, info.exception_record));
out.Append("\n");
}
out.Append("🛑 ");
// Only print out the process/thread when there's more than one.
if (id_to_target_.size() > 1) {
out.Append("process ");
out.Append(Syntax::kSpecial, std::to_string(IdForTarget(target)));
out.Append(" ");
}
if (thread->GetProcess()->GetThreads().size() > 1) {
out.Append("thread ");
out.Append(Syntax::kSpecial, std::to_string(IdForThread(thread)));
out.Append(" ");
}
// Stop reason.
if (!info.hit_breakpoints.empty()) {
out.Append(DescribeHitBreakpoints(info.hit_breakpoints));
} else if (info.exception_type == debug_ipc::ExceptionType::kGeneral) {
// Show exception type for non-debug exceptions. Most exceptions are generated by the debugger
// internally so skip those to avoid noise.
out.Append(fxl::StringPrintf("on %s exception ",
debug_ipc::ExceptionTypeToString(info.exception_type)));
}
// Frame (current position will always be frame 0).
const Stack& stack = thread->GetStack();
if (stack.empty()) {
out.Append(" (no location information)\n");
console->Output(out);
} else {
const Location& location = stack[0]->GetLocation();
FormatLocationOptions location_options(thread->GetProcess()->GetTarget());
location_options.func.name.bold_last = true;
out.Append(FormatLocation(location, location_options));
if (location.has_symbols()) {
out.Append("\n");
} else {
out.Append(" (no symbol info)\n");
}
console->Output(out);
Err err = OutputSourceContext(
thread->GetProcess(),
std::make_unique<SourceFileProviderImpl>(thread->GetProcess()->GetTarget()->settings()),
location, GetSourceAffinityForThread(thread));
if (err.has_error())
console->Output(err);
}
}
void ConsoleContext::ScheduleDisplayExpressions(Thread* thread) const {
std::vector<std::string> exprs = thread->settings().GetList(ClientSettings::Thread::kDisplay);
if (exprs.empty())
return;
// Thread stops should always have a frame.
const Stack& stack = thread->GetStack();
if (stack.empty())
return;
const Frame* frame = stack[0];
fxl::RefPtr<EvalContext> eval_context = frame->GetEvalContext();
// When something is printed every time, assume the user wants to see relatively little detail.
ConsoleFormatOptions options;
options.verbosity = ConsoleFormatOptions::Verbosity::kMinimal;
options.wrapping = ConsoleFormatOptions::Wrapping::kSmart;
options.pointer_expand_depth = 2;
Console::get()->Output(FormatExpressionsForConsole(exprs, options, eval_context));
}
Err ConsoleContext::FillOutCommand(Command* cmd) const {
// Job.
Err result = FillOutJob(cmd);
if (result.has_error())
return result;
// Target.
const TargetRecord* target_record = nullptr;
result = FillOutTarget(cmd, &target_record);
if (result.has_error())
return result;
// Thread.
const ThreadRecord* thread_record = nullptr;
result = FillOutThread(cmd, target_record, &thread_record);
if (result.has_error())
return result;
// Frame.
result = FillOutFrame(cmd, thread_record);
if (result.has_error())
return result;
// Breakpoint.
result = FillOutBreakpoint(cmd);
if (result.has_error())
return result;
// Filter.
result = FillOutFilter(cmd);
if (result.has_error())
return result;
// SymbolServer.
result = FillOutSymbolServer(cmd);
if (result.has_error())
return result;
return Err();
}
void ConsoleContext::HandleNotification(NotificationType type, const std::string& msg) {
OutputBuffer out;
auto preamble = fxl::StringPrintf("[%s] ", NotificationTypeToString(type));
switch (type) {
case NotificationType::kError:
out.Append(Syntax::kError, std::move(preamble));
[[fallthrough]];
case NotificationType::kWarning:
out.Append(Syntax::kWarning, std::move(preamble));
[[fallthrough]];
case NotificationType::kProcessEnteredLimbo:
[[fallthrough]];
case NotificationType::kProcessStdout:
[[fallthrough]];
case NotificationType::kProcessStderr:
break;
case NotificationType::kNone: // None is a no-op.
return;
}
out.Append(msg);
Console::get()->Output(std::move(out));
}
void ConsoleContext::HandlePreviousConnectedProcesses(
const std::vector<debug_ipc::ProcessRecord>& processes) {
OutputBuffer out(OutputBuffer{Syntax::kHeading, "Previously connected processes:\n"});
for (auto& process : processes) {
out.Append(
fxl::StringPrintf("%" PRIu64 ": %s\n", process.process_koid, process.process_name.c_str()));
}
out.Append(OutputBuffer{Syntax::kComment, "Type \"attach <pid>\" to reconnect.\n"});
Console::get()->Output(std::move(out));
}
void ConsoleContext::HandleProcessesInLimbo(
const std::vector<debug_ipc::ProcessRecord>& processes) {
OutputBuffer out(OutputBuffer{Syntax::kHeading, "Processes attached from limbo:\n"});
for (auto& process : processes) {
out.Append(fxl::StringPrintf(" %" PRIu64 ": %s\n", process.process_koid,
process.process_name.c_str()));
}
out.Append(OutputBuffer{
Syntax::kComment,
"Type \"detach <pid>\" to send back to Process Limbo if attached,\n"
"type \"detach <pid>\" again to terminate the process if not attached, or\n"
"type \"process <process context #> kill\" to terminate the process if attached.\n"
"See \"help jitd\" for more information on Just-In-Time-Debugging.\n"});
Console::get()->Output(std::move(out));
}
void ConsoleContext::DidCreateJob(Job* job) {
// TODO(anmittal): Add observer if required.
int new_id = next_job_id_;
next_job_id_++;
JobRecord record;
record.job_id = new_id;
record.job = job;
id_to_job_[new_id] = std::move(record);
job_to_id_[job] = new_id;
// Set the active job only if there's none already.
if (active_job_id_ == 0)
active_job_id_ = new_id;
}
void ConsoleContext::WillDestroyJob(Job* job) {
auto found_job = job_to_id_.find(job);
if (found_job == job_to_id_.end()) {
FX_NOTREACHED();
return;
}
int id = found_job->second;
// Clear any active job if it's the deleted one.
if (active_job_id_ == id)
active_job_id_ = 0;
id_to_job_.erase(id);
job_to_id_.erase(found_job);
}
void ConsoleContext::DidCreateBreakpoint(Breakpoint* breakpoint) {
int id = next_breakpoint_id_;
next_breakpoint_id_++;
id_to_breakpoint_[id] = breakpoint;
breakpoint_to_id_[breakpoint] = id;
}
void ConsoleContext::WillDestroyBreakpoint(Breakpoint* breakpoint) {
auto found_breakpoint = breakpoint_to_id_.find(breakpoint);
if (found_breakpoint == breakpoint_to_id_.end()) {
FX_NOTREACHED();
return;
}
int id = found_breakpoint->second;
// Clear any active breakpoint if it's the deleted one.
if (active_breakpoint_id_ == id)
active_breakpoint_id_ = 0;
id_to_breakpoint_.erase(id);
breakpoint_to_id_.erase(found_breakpoint);
}
void ConsoleContext::DidCreateFilter(Filter* filter) {
int id = next_filter_id_;
next_filter_id_++;
id_to_filter_[id] = filter;
filter_to_id_[filter] = id;
}
void ConsoleContext::WillDestroyFilter(Filter* filter) {
auto found = filter_to_id_.find(filter);
if (found == filter_to_id_.end()) {
FX_NOTREACHED();
return;
}
if (active_filter_id_ == found->second)
active_filter_id_ = 0;
id_to_filter_.erase(found->second);
filter_to_id_.erase(found);
}
void ConsoleContext::DidCreateSymbolServer(SymbolServer* symbol_server) {
int id = next_symbol_server_id_;
next_symbol_server_id_++;
id_to_symbol_server_[id] = symbol_server;
symbol_server_to_id_[symbol_server] = id;
if (active_symbol_server_id_ == 0) {
active_symbol_server_id_ = id;
}
}
void ConsoleContext::DidCreateTarget(Target* target) {
int new_id = next_target_id_;
next_target_id_++;
TargetRecord record;
record.target_id = new_id;
record.target = target;
id_to_target_[new_id] = std::move(record);
target_to_id_[target] = new_id;
// Set the active target only if there's none already.
if (active_target_id_ == 0)
active_target_id_ = new_id;
}
void ConsoleContext::WillDestroyTarget(Target* target) {
int deleted_target_id = 0;
{
TargetRecord* record = GetTargetRecord(target);
if (!record) {
FX_NOTREACHED();
return;
}
deleted_target_id = record->target_id;
// There should be no threads by the time we erase the target mapping.
FX_DCHECK(record->id_to_thread.empty());
FX_DCHECK(record->thread_to_id.empty());
target_to_id_.erase(target);
id_to_target_.erase(deleted_target_id);
// *record is now invalid.
}
if (active_target_id_ == deleted_target_id) {
// Need to update the default target ID.
if (id_to_target_.empty()) {
// This should only happen in the shutting-down case.
active_target_id_ = 0;
} else {
// Just pick the first target to be the active one. It might be nice to
// have an ordering of which one the user had selected previously in
// case they're toggling between two.
active_target_id_ = id_to_target_.begin()->first;
}
}
}
void ConsoleContext::DidCreateProcess(Process* process, bool autoattached_to_new_process,
uint64_t timestamp) {
TargetRecord* record = GetTargetRecord(process->GetTarget());
if (!record) {
FX_NOTREACHED();
return;
}
// Restart the thread ID counting when the process starts in case this
// target was previously running (we want to restart numbering every time).
record->next_thread_id = 1;
OutputBuffer out;
switch (process->start_type()) {
case Process::StartType::kAttach:
out.Append("Attached ");
break;
case Process::StartType::kComponent:
case Process::StartType::kLaunch:
out.Append("Launched ");
break;
}
out.Append(FormatTarget(this, process->GetTarget()));
bool pause_on_attach =
session()->system().settings().GetBool(ClientSettings::System::kPauseOnAttach);
if (autoattached_to_new_process && pause_on_attach) {
out.Append(Syntax::kComment,
"\n The process is currently in an initializing state. You can set pending\n"
" breakpoints (symbols haven't been loaded yet) and \"continue\".");
}
Console::get()->Output(out);
}
void ConsoleContext::WillDestroyProcess(Process* process, DestroyReason reason, int exit_code,
uint64_t timestamp) {
TargetRecord* record = GetTargetRecord(process->GetTarget());
if (!record) {
FX_NOTREACHED();
return;
}
int process_index = IdForTarget(process->GetTarget());
Console* console = Console::get();
std::string msg;
switch (reason) {
case ProcessObserver::DestroyReason::kExit:
msg = fxl::StringPrintf("Process %d exited with code %d.", process_index, exit_code);
break;
case ProcessObserver::DestroyReason::kDetach:
msg = fxl::StringPrintf("Process %d detached.", process_index);
break;
case ProcessObserver::DestroyReason::kKill:
msg = fxl::StringPrintf("Process %d killed.", process_index);
break;
}
console->Output(msg);
}
void ConsoleContext::DidCreateThread(Thread* thread) {
TargetRecord* record = GetTargetRecord(thread->GetProcess()->GetTarget());
if (!record) {
FX_NOTREACHED();
return;
}
int thread_id = record->next_thread_id;
record->next_thread_id++;
record->id_to_thread[thread_id].thread = thread;
record->thread_to_id[thread] = thread_id;
// Only make a new thread the default if there is no current thread,
// otherwise the context will be swapping out from under the user as the
// program runs.
if (record->active_thread_id == 0)
record->active_thread_id = thread_id;
}
void ConsoleContext::WillDestroyThread(Thread* thread) {
TargetRecord* record = GetTargetRecord(thread->GetProcess()->GetTarget());
if (!record) {
FX_NOTREACHED();
return;
}
auto found_thread_to_id = record->thread_to_id.find(thread);
if (found_thread_to_id == record->thread_to_id.end()) {
FX_NOTREACHED();
return;
}
int thread_id = found_thread_to_id->second;
record->id_to_thread.erase(found_thread_to_id->second);
record->thread_to_id.erase(found_thread_to_id);
// Update the active thread if the currently active one is being deleted.
if (thread_id == record->active_thread_id) {
// Just pick the first thread to be the active one. It might be nice to
// have an ordering of which one the user had selected previously in
// case they're toggling between two.
if (record->id_to_thread.empty()) {
record->active_thread_id = 0;
} else {
record->active_thread_id = record->id_to_thread.begin()->first;
}
}
}
void ConsoleContext::OnSymbolLoadFailure(Process* process, const Err& err) {
Console::get()->Output(err);
}
// For comparison, GDB's printout for a breakpoint hit is:
//
// Breakpoint 1, main () at eraseme.c:4
// 4 printf("Hello\n");
//
// And LLDB's is:
//
// * thread #1: tid = 33767, 0x000055555555463e a.out`main + 4 at
// eraseme.c:4, name = 'a.out', stop reason = breakpoint 1.1
// frame #0: 0x000055555555463e a.out`main + 4 at eraseme.c:4
// 1 #include <stdio.h>
// 2
// 3 int main() {
// -> 4 printf("Hello\n");
// 5 return 1;
// 6 }
//
// When stepping, GDB prints out only the 2nd line with source info, and LLDB
// prints out the whole thing with "step over" for "stop reason".
void ConsoleContext::OnThreadStopped(Thread* thread, const StopInfo& info) {
// The stopped, process, thread, and frame should be active.
Target* target = thread->GetProcess()->GetTarget();
SetActiveTarget(target);
SetActiveThreadForTarget(thread);
SetActiveFrameIdForThread(thread, 0);
SetActiveBreakpointForStop(info);
// Show the location information.
OutputThreadContext(thread, info);
ScheduleDisplayExpressions(thread);
}
void ConsoleContext::OnThreadFramesInvalidated(Thread* thread) {
ThreadRecord* record = GetThreadRecord(thread);
if (!record) {
FX_NOTREACHED();
return;
}
// Reset the active frame.
record->active_frame_id = 0;
}
void ConsoleContext::OnDownloadsStarted() { Console::get()->Output("Downloading symbols..."); }
void ConsoleContext::OnDownloadsStopped(size_t success, size_t fail) {
Console::get()->Output(
fxl::StringPrintf("Symbol downloading complete. %zu succeeded, %zu failed.", success, fail));
}
void ConsoleContext::OnBreakpointMatched(Breakpoint* breakpoint, bool user_requested) {
if (user_requested)
return; // Don't need to notify for user-requested changes.
BreakpointSettings settings = breakpoint->GetSettings();
size_t matched_locs = breakpoint->GetLocations().size();
OutputBuffer out("Breakpoint ");
out.Append(Syntax::kSpecial, std::to_string(IdForBreakpoint(breakpoint)));
out.Append(fxl::StringPrintf(" now matching %zu addrs for ", matched_locs));
out.Append(FormatInputLocations(settings.locations));
Console::get()->Output(out);
}
void ConsoleContext::OnBreakpointUpdateFailure(Breakpoint* breakpoint, const Err& err) {
Console* console = Console::get();
if (breakpoint->IsInternal()) {
// Although the user didn't explicitly set this breakpoint, they presumably were involved in
// some operation that caused it to be made. Notify of the error so they know it's not working.
console->Output(Err("Error updating internal breakpoint:\n" + err.msg()));
} else {
OutputBuffer out;
out.Append("Error updating ");
out.Append(FormatBreakpoint(this, breakpoint, false));
out.Append(err);
console->Output(out);
}
}
ConsoleContext::TargetRecord* ConsoleContext::GetTargetRecord(int target_id) {
return const_cast<TargetRecord*>(
const_cast<const ConsoleContext*>(this)->GetTargetRecord(target_id));
}
const ConsoleContext::TargetRecord* ConsoleContext::GetTargetRecord(int target_id) const {
auto found_to_record = id_to_target_.find(target_id);
if (found_to_record == id_to_target_.end()) {
FX_NOTREACHED();
return nullptr;
}
return &found_to_record->second;
}
ConsoleContext::TargetRecord* ConsoleContext::GetTargetRecord(const Target* target) {
return const_cast<TargetRecord*>(
const_cast<const ConsoleContext*>(this)->GetTargetRecord(target));
}
const ConsoleContext::TargetRecord* ConsoleContext::GetTargetRecord(const Target* target) const {
auto found_to_id = target_to_id_.find(target);
if (found_to_id == target_to_id_.end()) {
FX_NOTREACHED();
return nullptr;
}
return GetTargetRecord(found_to_id->second);
}
ConsoleContext::ThreadRecord* ConsoleContext::GetThreadRecord(const Thread* thread) {
// Share implementation with the non-const version.
return const_cast<ThreadRecord*>(
const_cast<const ConsoleContext*>(this)->GetThreadRecord(thread));
}
const ConsoleContext::ThreadRecord* ConsoleContext::GetThreadRecord(const Thread* thread) const {
const TargetRecord* target_record = GetTargetRecord(thread->GetProcess()->GetTarget());
if (!target_record) {
FX_NOTREACHED();
return nullptr;
}
auto found_thread_to_id = target_record->thread_to_id.find(thread);
if (found_thread_to_id == target_record->thread_to_id.end()) {
FX_NOTREACHED();
return nullptr;
}
int thread_id = found_thread_to_id->second;
auto found_id_to_thread = target_record->id_to_thread.find(thread_id);
if (found_thread_to_id == target_record->thread_to_id.end()) {
FX_NOTREACHED();
return nullptr;
}
return &found_id_to_thread->second;
}
Err ConsoleContext::FillOutJob(Command* cmd) const {
int job_id = cmd->GetNounIndex(Noun::kJob);
if (job_id == Command::kNoIndex) {
// No index: use the active one (may or may not exist).
job_id = active_job_id_;
auto found_job = id_to_job_.find(job_id);
if (found_job == id_to_job_.end()) {
// When there are no jobs, the active ID should be 0.
FX_DCHECK(job_id == 0);
} else {
cmd->set_job(found_job->second.job);
}
return Err();
}
// Explicit index given, look it up.
auto found_job = id_to_job_.find(job_id);
if (found_job == id_to_job_.end()) {
return Err(ErrType::kInput, fxl::StringPrintf("There is no job %d.", job_id));
}
cmd->set_job(found_job->second.job);
return Err();
}
Err ConsoleContext::FillOutTarget(Command* cmd, TargetRecord const** out_target_record) const {
int target_id = cmd->GetNounIndex(Noun::kProcess);
if (target_id == Command::kNoIndex) {
// No index: use the active one (which should always exist).
target_id = active_target_id_;
auto found_target = id_to_target_.find(target_id);
FX_DCHECK(found_target != id_to_target_.end());
cmd->set_target(found_target->second.target);
FX_DCHECK(cmd->target()); // Default target should always exist.
*out_target_record = GetTargetRecord(target_id);
return Err();
}
// Explicit index given, look it up.
auto found_target = id_to_target_.find(target_id);
if (found_target == id_to_target_.end()) {
return Err(ErrType::kInput, fxl::StringPrintf("There is no process %d.", target_id));
}
cmd->set_target(found_target->second.target);
*out_target_record = GetTargetRecord(target_id);
return Err();
}
Err ConsoleContext::FillOutThread(Command* cmd, const TargetRecord* target_record,
ThreadRecord const** out_thread_record) const {
int thread_id = cmd->GetNounIndex(Noun::kThread);
const ThreadRecord* thread_record = nullptr;
if (thread_id == Command::kNoIndex) {
// No thread specified, use the default one.
thread_id = target_record->active_thread_id;
auto found_thread = target_record->id_to_thread.find(thread_id);
if (found_thread == target_record->id_to_thread.end()) {
// When there are no threads, the active thread ID will be 0 and that's
// fine. But if it's nonzero, the thread should always be valid.
FX_DCHECK(thread_id == 0);
} else {
thread_record = &found_thread->second;
cmd->set_thread(thread_record->thread);
}
*out_thread_record = thread_record;
return Err();
}
// Explicit index given, look it up.
auto found_thread = target_record->id_to_thread.find(thread_id);
if (found_thread == target_record->id_to_thread.end()) {
if (target_record->id_to_thread.empty()) {
return Err(ErrType::kInput, "There are no threads in the process.");
}
return Err(ErrType::kInput,
fxl::StringPrintf("There is no thread %d in the process.", thread_id));
}
thread_record = &found_thread->second;
cmd->set_thread(thread_record->thread);
*out_thread_record = thread_record;
return Err();
}
Err ConsoleContext::FillOutFrame(Command* cmd, const ThreadRecord* thread_record) const {
int frame_id = cmd->GetNounIndex(Noun::kFrame);
if (frame_id == Command::kNoIndex) {
// No index: use the active one (if any).
if (thread_record) {
auto& stack = thread_record->thread->GetStack();
frame_id = thread_record->active_frame_id;
if (frame_id >= 0 && frame_id < static_cast<int>(stack.size())) {
cmd->set_frame(stack[frame_id]);
} else if (!stack.empty()) {
// Invalid frame index, default to 0th frame.
frame_id = 0;
cmd->set_frame(stack[0]);
}
}
return Err();
}
// Frame index specified, use it.
if (!thread_record)
return Err(ErrType::kInput, "There is no thread to have frames.");
Stack& stack = thread_record->thread->GetStack();
if (frame_id >= 0 && frame_id < static_cast<int>(stack.size())) {
// References a valid frame. Now check that the frame index references
// the top physical frame (or one of its inline expansions above it) or
// all frames are synced.
bool top_physical_frame = true;
for (int i = 0; i < frame_id; i++) {
if (!stack[i]->IsInline()) {
top_physical_frame = false;
break;
}
}
if (top_physical_frame || stack.has_all_frames()) {
cmd->set_frame(stack[frame_id]);
return Err();
}
}
// Invalid frame specified. The full backtrace list is populated on
// demand. It could be if the frames aren't synced for the thread we
// could delay processing this command and get the frames, but we're not
// set up to do that (this function is currently synchronous). Instead
// if we detect the list isn't populated and the user requested one
// that's out-of-range, request they manually sync the list.
//
// Check for the presence of any frames because the thread might not be
// in a state to have frames (i.e. it's running).
if (!stack.empty() && !thread_record->thread->GetStack().has_all_frames()) {
return Err(ErrType::kInput,
"The frames for this thread haven't been synced.\n"
"Use \"frame\" to list the frames before selecting one to "
"populate the frame list.");
}
return Err(ErrType::kInput,
"Invalid frame index.\n"
"Use \"frame\" to list available ones.");
}
Err ConsoleContext::FillOutBreakpoint(Command* cmd) const {
int breakpoint_id = cmd->GetNounIndex(Noun::kBreakpoint);
if (breakpoint_id == Command::kNoIndex) {
// No index: use the active one (which may not exist).
cmd->set_breakpoint(GetActiveBreakpoint());
return Err();
}
// Explicit index given, look it up.
auto found_breakpoint = id_to_breakpoint_.find(breakpoint_id);
if (found_breakpoint == id_to_breakpoint_.end()) {
return Err(ErrType::kInput, fxl::StringPrintf("There is no breakpoint %d.", breakpoint_id));
}
cmd->set_breakpoint(found_breakpoint->second);
return Err();
}
Err ConsoleContext::FillOutFilter(Command* cmd) const {
int filter_id = cmd->GetNounIndex(Noun::kFilter);
if (filter_id == Command::kNoIndex) {
// No index: use the active one (which may not exist).
cmd->set_filter(GetActiveFilter());
return Err();
}
// Explicit index given, look it up.
auto found_filter = id_to_filter_.find(filter_id);
if (found_filter == id_to_filter_.end()) {
return Err(ErrType::kInput, fxl::StringPrintf("There is no filter %d.", filter_id));
}
cmd->set_filter(found_filter->second);
return Err();
}
Err ConsoleContext::FillOutSymbolServer(Command* cmd) const {
int symbol_server_id = cmd->GetNounIndex(Noun::kSymServer);
if (symbol_server_id == Command::kNoIndex) {
// No index: use the active one (which may not exist).
cmd->set_sym_server(GetActiveSymbolServer());
return Err();
}
// Explicit index given, look it up.
auto found_symbol_server = id_to_symbol_server_.find(symbol_server_id);
if (found_symbol_server == id_to_symbol_server_.end()) {
return Err(ErrType::kInput,
fxl::StringPrintf("There is no symbol server %d.", symbol_server_id));
}
cmd->set_sym_server(found_symbol_server->second);
return Err();
}
OutputBuffer ConsoleContext::DescribeHitBreakpoints(
const std::vector<fxl::WeakPtr<Breakpoint>>& hits) const {
// Do two passes since some of the weak pointers may be gone.
std::vector<int> ids;
for (const auto& hit : hits) {
if (hit)
ids.push_back(IdForBreakpoint(hit.get()));
}
OutputBuffer out;
if (ids.empty())
return out;
out.Append("on bp ");
for (size_t i = 0; i < ids.size(); i++) {
out.Append(Syntax::kSpecial, std::to_string(ids[i]));
if (i < ids.size() - 1)
out.Append(", ");
}
out.Append(" ");
return out;
}
void ConsoleContext::SetActiveBreakpointForStop(const StopInfo& info) {
// There can be multiple breakpoints at the same address. Use the one with the largest ID since
// it will be the one set most recently.
int max_id = 0;
const Breakpoint* bp = nullptr;
for (const auto& weak_bp : info.hit_breakpoints) {
if (!weak_bp || weak_bp->IsInternal())
continue;
int bp_id = IdForBreakpoint(weak_bp.get());
if (bp_id > max_id) {
max_id = bp_id;
bp = weak_bp.get();
}
}
if (bp)
SetActiveBreakpoint(bp);
}
} // namespace zxdb