blob: 71bc2af75faad501d475fa4da7f29c675c4d4862 [file] [log] [blame]
// Copyright 2020 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/commands/verb_pause.h"
#include "src/developer/debug/zxdb/client/process.h"
#include "src/developer/debug/zxdb/client/session.h"
#include "src/developer/debug/zxdb/client/system.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_target.h"
#include "src/developer/debug/zxdb/console/output_buffer.h"
#include "src/developer/debug/zxdb/console/string_util.h"
#include "src/developer/debug/zxdb/console/verbs.h"
namespace zxdb {
namespace {
constexpr int kClearSwitch = 1;
const char kPauseShortHelp[] = "pause / pa: Pause a thread or process.";
const char kPauseHelp[] =
R"(pause / pa
When a thread or process is running, "pause" will stop execution so state
can be inspected or the thread single-stepped.
See "continue" to resume a paused thread or process.
The behavior will depend upon the context specified.
- By itself, "pause" will pause all threads of all processes that are
currently running.
- When a process is specified ("process 2 pause" for an explicit process
or "process pause" for the current process), only the threads in that
process will be paused. Other debugged processes currently running will
remain so.
- When a thread is specified ("thread 1 pause" for an explicit thread
or "thread pause" for the current thread), only that thread will be
paused. Other threads in that process and other processes currently
running will remain so.
Options
--clear-state | -c
Additionally clears all stepping state. Without this flag, any previous
step operations that have not completed will be resumed when the thread
is continued.
Examples of stepping state are the "finish" or "until" commands that may
take some time to complete. If you run "pause" without "-c" and then
"continue", the uncompleted "finish" or "until" commands will still be
active and will automatically stop execution when their condition has been
fulfilled. The "-c" option will cancel these pending step operations.
Examples
pa
pause
Pause all processes and threads.
pr pa
process pause
process 4 pause
Pause all threads of a process (the current process is implicit if
no process index is specified).
t pa
thread pause
pr 2 t 4 pa
process 2 thread 4 pause
Pause only one thread (the current process and thread are implicit
if no index is specified).
)";
Err PauseThread(ConsoleContext* context, Thread* thread, bool clear_state) {
// Only save the thread (for printing source info) if it's the current thread.
Target* target = thread->GetProcess()->GetTarget();
bool show_source =
context->GetActiveTarget() == target && context->GetActiveThreadForTarget(target) == thread;
if (clear_state)
thread->CancelAllThreadControllers();
thread->Pause([weak_thread = thread->GetWeakPtr(), show_source]() {
if (!weak_thread)
return;
Console* console = Console::get();
if (show_source) {
// Output the full source location.
console->context().OutputThreadContext(weak_thread.get(), StopInfo());
} else {
// Not current, just output the one-line description.
OutputBuffer out("Paused ");
out.Append(FormatThread(&console->context(), weak_thread.get()));
console->Output(out);
}
});
return Err();
}
// Source information on this thread will be printed out on completion. The current thread may be
// null.
Err PauseTarget(ConsoleContext* context, Target* target, Thread* current_thread, bool clear_state) {
Process* process = target->GetProcess();
if (!process)
return Err("Process not running, can't pause.");
// Only save the thread (for printing source info) if it's the current thread.
fxl::WeakPtr<Thread> weak_thread;
if (current_thread && context->GetActiveTarget() == target &&
context->GetActiveThreadForTarget(target) == current_thread)
weak_thread = current_thread->GetWeakPtr();
if (clear_state)
process->CancelAllThreadControllers();
process->Pause([weak_process = process->GetWeakPtr(), weak_thread]() {
if (!weak_process)
return;
Console* console = Console::get();
OutputBuffer out("Paused");
out.Append(FormatTarget(&console->context(), weak_process->GetTarget()));
console->Output(out);
if (weak_thread) {
// Thread is current, show current location.
console->context().OutputThreadContext(weak_thread.get(), StopInfo());
}
});
return Err();
}
// Source information on this thread will be printed out on completion.
Err PauseSystem(System* system, bool clear_state) {
if (Err err = VerifySystemHasRunningProcess(system); err.has_error())
return err;
if (clear_state)
system->CancelAllThreadControllers();
system->Pause([weak_system = system->GetWeakPtr()]() {
// Provide messaging about the system pause.
if (!weak_system)
return;
OutputBuffer out;
Console* console = Console::get();
// Find the current thread for outputting context. The current thread may have changed from
// when the command was initiated so always use the current one. In addition, pausing a program
// immediately after starting or attaching to it won't always sync the threads so there might
// be no thread context on the original "pause" command.
Thread* thread = nullptr;
if (const Target* target = console->context().GetActiveTarget())
thread = console->context().GetActiveThreadForTarget(target); // May be null if not running.
// Collect the status of all running processes.
int paused_process_count = 0;
for (const Target* target : weak_system->GetTargets()) {
if (const Process* process = target->GetProcess()) {
paused_process_count++;
out.Append(" " + GetBullet() + " ");
out.Append(FormatTarget(&console->context(), target));
out.Append("\n");
}
}
// Skip the process list if there's only one and we're showing the thread info below. Otherwise
// the one thing paused is duplicated twice and this is the most common case.
if (paused_process_count > 1 || !thread) {
console->Output("Paused:\n");
console->Output(out);
console->Output("\n");
}
// Follow with the source context of the current thread if there is one.
if (thread)
console->context().OutputThreadContext(thread, StopInfo());
});
return Err();
}
Err RunVerbPause(ConsoleContext* context, const Command& cmd) {
if (Err err = cmd.ValidateNouns({Noun::kGlobal, Noun::kProcess, Noun::kThread}); err.has_error())
return err;
bool clear_state = cmd.HasSwitch(kClearSwitch);
if (cmd.HasNoun(Noun::kThread))
return PauseThread(context, cmd.thread(), clear_state);
if (cmd.HasNoun(Noun::kProcess))
return PauseTarget(context, cmd.target(), cmd.thread(), clear_state);
return PauseSystem(&context->session()->system(), clear_state);
}
} // namespace
VerbRecord GetPauseVerbRecord() {
VerbRecord pause(&RunVerbPause, {"pause", "pa"}, kPauseShortHelp, kPauseHelp,
CommandGroup::kProcess);
pause.switches.push_back(SwitchRecord(kClearSwitch, false, "clear-state", 'c'));
return pause;
}
} // namespace zxdb