blob: c53cb70ae4f834620be0b8a321e428fd6584e319 [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 <inttypes.h>
#include "src/developer/debug/shared/message_loop.h"
#include "src/developer/debug/zxdb/client/finish_thread_controller.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/step_into_specific_thread_controller.h"
#include "src/developer/debug/zxdb/client/step_into_thread_controller.h"
#include "src/developer/debug/zxdb/client/step_over_thread_controller.h"
#include "src/developer/debug/zxdb/client/substatement.h"
#include "src/developer/debug/zxdb/client/thread.h"
#include "src/developer/debug/zxdb/client/until_thread_controller.h"
#include "src/developer/debug/zxdb/common/err.h"
#include "src/developer/debug/zxdb/console/command.h"
#include "src/developer/debug/zxdb/console/command_utils.h"
#include "src/developer/debug/zxdb/console/commands/verb_steps.h"
#include "src/developer/debug/zxdb/console/console.h"
#include "src/developer/debug/zxdb/console/format_frame.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_register.h"
#include "src/developer/debug/zxdb/console/format_table.h"
#include "src/developer/debug/zxdb/console/format_target.h"
#include "src/developer/debug/zxdb/console/input_location_parser.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"
#include "src/developer/debug/zxdb/expr/eval_context_impl.h"
#include "src/developer/debug/zxdb/expr/expr.h"
#include "src/developer/debug/zxdb/symbols/code_block.h"
#include "src/developer/debug/zxdb/symbols/function.h"
#include "src/developer/debug/zxdb/symbols/location.h"
#include "src/developer/debug/zxdb/symbols/process_symbols.h"
#include "src/developer/debug/zxdb/symbols/variable.h"
#include "src/developer/debug/zxdb/symbols/visit_scopes.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace zxdb {
namespace {
constexpr int kStepIntoUnsymbolized = 1;
constexpr int kVerboseFormat = 2;
constexpr int kForceAllTypes = 3;
constexpr int kForceNumberChar = 4;
constexpr int kForceNumberSigned = 5;
constexpr int kForceNumberUnsigned = 6;
constexpr int kForceNumberHex = 7;
constexpr int kMaxArraySize = 8;
constexpr int kRawOutput = 9;
constexpr int kVerboseBacktrace = 10;
// If the system has at least one running process, returns true. If not, returns false and sets the
// err.
//
// When doing global things like System::Continue(), it will succeed if there are no running
// programs (it will successfully continue all 0 processes). This is confusing to the user so this
// function is used to check first.
bool VerifySystemHasRunningProcess(System* system, Err* err) {
for (const Target* target : system->GetTargets()) {
if (target->GetProcess())
return true;
}
*err = Err("No processes are running.");
return false;
}
// Populates the formatting options with the given command's switches.
Err GetConsoleFormatOptions(const Command& cmd, ConsoleFormatOptions* options) {
// These defaults currently don't have exposed options. A pointer expand depth of one allows
// local variables and "this" to be expanded without expanding anything else. Often pointed-to
// classes are less useful and can be very large.
options->pointer_expand_depth = 1;
options->max_depth = 16;
// All current users of this want the smart form.
//
// This keeps the default wrap columns at 80. We can consider querying the actual console width.
// But very long lines start putting many struct members on the same line which gets increasingly
// difficult to read. 80 columns feels reasonably close to how much you can take in at once.
//
// Note also that this doesn't stricly wrap the output to 80 columns. Long type names or values
// will still use the full width and will be wrapped by the console. This wrapping only affects
// the splitting of items across lines.
options->wrapping = ConsoleFormatOptions::Wrapping::kSmart;
// Verbosity.
if (cmd.HasSwitch(kForceAllTypes))
options->verbosity = ConsoleFormatOptions::Verbosity::kAllTypes;
else if (cmd.HasSwitch(kVerboseFormat))
options->verbosity = ConsoleFormatOptions::Verbosity::kMedium;
else
options->verbosity = ConsoleFormatOptions::Verbosity::kMinimal;
// Array size.
if (cmd.HasSwitch(kMaxArraySize)) {
int size = 0;
Err err = StringToInt(cmd.GetSwitchValue(kMaxArraySize), &size);
if (err.has_error())
return err;
options->max_array_size = static_cast<uint32_t>(size);
}
// Mapping from command-line parameter to format enum.
constexpr size_t kFormatCount = 4;
static constexpr std::pair<int, ConsoleFormatOptions::NumFormat> kFormats[kFormatCount] = {
{kForceNumberChar, ConsoleFormatOptions::NumFormat::kChar},
{kForceNumberUnsigned, ConsoleFormatOptions::NumFormat::kUnsigned},
{kForceNumberSigned, ConsoleFormatOptions::NumFormat::kSigned},
{kForceNumberHex, ConsoleFormatOptions::NumFormat::kHex}};
int num_type_overrides = 0;
for (const auto& cur : kFormats) {
if (cmd.HasSwitch(cur.first)) {
num_type_overrides++;
options->num_format = cur.second;
}
}
// Disable pretty-printing.
if (cmd.HasSwitch(kRawOutput))
options->enable_pretty_printing = false;
if (num_type_overrides > 1)
return Err("More than one type override (-c, -d, -u, -x) specified.");
return Err();
}
#define FORMAT_VALUE_SWITCHES \
" --max-array=<number>\n" \
" Specifies the maximum array size to print. By default this is\n" \
" 256. Specifying large values will slow things down and make the\n" \
" output harder to read, but the default is sometimes insufficient.\n" \
" This also applies to strings.\n" \
"\n" \
" -r\n" \
" --raw\n" \
" Bypass pretty-printers and show the raw type information.\n" \
"\n" \
" -t\n" \
" --types\n" \
" Force type printing on. The type of every value printed will be\n" \
" explicitly shown. Implies -v.\n" \
"\n" \
" -v\n" \
" --verbose\n" \
" Don't elide type names. Show reference addresses and pointer\n" \
" types.\n" \
"\n" \
"Number formatting options\n" \
"\n" \
" Force numeric values to be of specific types with these options:\n" \
"\n" \
" -c Character\n" \
" -d Signed decimal\n" \
" -u Unsigned decimal\n" \
" -x Unsigned hexadecimal\n"
// backtrace ---------------------------------------------------------------------------------------
const char kBacktraceShortHelp[] = "backtrace / bt: Print a backtrace.";
const char kBacktraceHelp[] =
R"(backtrace / bt
Prints a backtrace of the thread, including function parameters.
To see just function names and line numbers, use "frame" or just "f".
Arguments
-r
--raw
Expands frames that were collapsed by the "pretty" stack formatter.
-t
--types
Include all type information for function parameters.
-v
--verbose
Include extra stack frame information:
• Full template lists and function parameter types.
• Instruction pointer.
• Stack pointer.
• Stack frame base pointer.
Examples
t 2 bt
thread 2 backtrace
)";
Err DoBacktrace(ConsoleContext* context, const Command& cmd) {
if (Err err = cmd.ValidateNouns({Noun::kProcess, Noun::kThread}); err.has_error())
return err;
if (!cmd.thread())
return Err("There is no thread to have frames.");
FormatStackOptions opts;
if (!cmd.HasSwitch(kRawOutput))
opts.pretty_stack = context->pretty_stack_manager();
opts.frame.loc = FormatLocationOptions(cmd.target());
opts.frame.loc.show_params = cmd.HasSwitch(kForceAllTypes);
opts.frame.loc.func.name.elide_templates = true;
opts.frame.loc.func.name.bold_last = true;
opts.frame.loc.func.params = FormatFunctionNameOptions::kElideParams;
opts.frame.detail = FormatFrameOptions::kParameters;
if (cmd.HasSwitch(kVerboseBacktrace)) {
opts.frame.detail = FormatFrameOptions::kVerbose;
opts.frame.loc.func.name.elide_templates = false;
opts.frame.loc.func.params = FormatFunctionNameOptions::kParamTypes;
}
// These are minimal since there is often a lot of data.
opts.frame.variable.verbosity = ConsoleFormatOptions::Verbosity::kMinimal;
opts.frame.variable.verbosity = cmd.HasSwitch(kForceAllTypes)
? ConsoleFormatOptions::Verbosity::kAllTypes
: ConsoleFormatOptions::Verbosity::kMinimal;
opts.frame.variable.pointer_expand_depth = 1;
opts.frame.variable.max_depth = 3;
// Always force update the stack. Various things can have changed and when the user requests
// a stack we want to be sure things are correct.
Console::get()->Output(FormatStack(cmd.thread(), true, opts));
return Err();
}
// continue ----------------------------------------------------------------------------------------
const char kContinueShortHelp[] = "continue / c: Continue a suspended thread or process.";
const char kContinueHelp[] =
R"(continue / c
When a thread is stopped at an exception or a breakpoint, "continue" will
continue execution.
See "pause" to stop a running thread or process.
The behavior will depend upon the context specified.
- By itself, "continue" will continue all threads of all processes that are
currently stopped.
- When a process is specified ("process 2 continue" for an explicit process
or "process continue" for the current process), only the threads in that
process will be continued. Other debugged processes currently stopped will
remain so.
- When a thread is specified ("thread 1 continue" for an explicit thread
or "thread continue" for the current thread), only that thread will be
continued. Other threads in that process and other processes currently
stopped will remain so.
TODO(brettw) it might be nice to have a --other flag that would continue
all threads other than the specified one (which the user might want to step
while everything else is going).
Examples
c
continue
Continue all processes and threads.
pr c
process continue
process 4 continue
Continue all threads of a process (the current process is implicit if
no process index is specified).
t c
thread continue
pr 2 t 4 c
process 2 thread 4 continue
Continue only one thread (the current process and thread are implicit
if no index is specified).
)";
Err DoContinue(ConsoleContext* context, const Command& cmd) {
Err err = cmd.ValidateNouns({Noun::kProcess, Noun::kThread});
if (err.has_error())
return err;
if (cmd.HasNoun(Noun::kThread)) {
cmd.thread()->Continue();
} else if (cmd.HasNoun(Noun::kProcess)) {
Process* process = cmd.target()->GetProcess();
if (!process)
return Err("Process not running, can't continue.");
process->Continue();
} else {
if (!VerifySystemHasRunningProcess(&context->session()->system(), &err))
return err;
context->session()->system().Continue();
}
return Err();
}
// down --------------------------------------------------------------------------------------------
const char kDownShortHelp[] = "down: Move down the stack";
const char kDownHelp[] =
R"(down
Switch the active frame to the one below (forward in time from) the current.
Examples
down
Move one frame down the stack
t 1 down
Move down the stack on thread 1
)";
// Shows the given frame for when it changes. This encapsulates the formatting options.
void OutputFrameInfoForChange(const Frame* frame, int id) {
FormatFrameOptions opts;
opts.loc.func.name.elide_templates = true;
opts.loc.func.name.bold_last = true;
opts.loc.func.params = FormatFunctionNameOptions::kElideParams;
opts.variable.verbosity = ConsoleFormatOptions::Verbosity::kMinimal;
opts.variable.pointer_expand_depth = 1;
opts.variable.max_depth = 4;
Console::get()->Output(FormatFrame(frame, opts, id));
}
Err DoDown(ConsoleContext* context, const Command& cmd) {
if (Err err = AssertStoppedThreadCommand(context, cmd, true, "down"); err.has_error())
return err;
auto id = context->GetActiveFrameIdForThread(cmd.thread());
if (id < 0)
return Err("Cannot find current frame.");
if (id == 0)
return Err("At bottom of stack.");
if (cmd.thread()->GetStack().size() == 0)
return Err("No stack frames.");
id -= 1;
context->SetActiveFrameIdForThread(cmd.thread(), id);
OutputFrameInfoForChange(cmd.thread()->GetStack()[id], id);
return Err();
}
// up ----------------------------------------------------------------------------------------------
const char kUpShortHelp[] = "up: Move up the stack";
const char kUpHelp[] =
R"(up
Switch the active frame to the one above (backward in time from) the current.
Examples
up
Move one frame up the stack
t 1 up
Move up the stack on thread 1
)";
Err DoUp(ConsoleContext* context, const Command& cmd) {
if (Err err = AssertStoppedThreadCommand(context, cmd, true, "up"); err.has_error())
return err;
// This computes the frame index from the callback in case the user does "up" faster than an
// async stack request can complete. Doing the new index computation from the callback ensures
// that all commands are executed.
auto on_has_frames = [weak_thread = cmd.thread()->GetWeakPtr()](const Err& err) {
if (!weak_thread) {
Console::get()->Output(Err("Thread destroyed."));
return;
}
const Thread* thread = weak_thread.get();
ConsoleContext* context = &Console::get()->context();
auto id = context->GetActiveFrameIdForThread(thread);
if (id < 0 || thread->GetStack().size() == 0) {
Console::get()->Output(Err("No current frame."));
return;
}
id += 1;
if (static_cast<size_t>(id) >= thread->GetStack().size()) {
Console::get()->Output(Err("At top of stack."));
return;
}
context->SetActiveFrameIdForThread(thread, id);
OutputFrameInfoForChange(thread->GetStack()[id], id);
};
if (cmd.thread()->GetStack().has_all_frames()) {
on_has_frames(Err());
} else {
cmd.thread()->GetStack().SyncFrames(std::move(on_has_frames));
}
return Err();
}
// finish ------------------------------------------------------------------------------------------
const char kFinishShortHelp[] = "finish / fi: Finish execution of a stack frame.";
const char kFinishHelp[] =
R"(finish / fi
Alias: "fi"
Resume thread execution until the selected stack frame returns. This means
that the current function call will execute normally until it finished.
See also "until".
Examples
fi
finish
Exit the currently selected stack frame (see "frame").
pr 1 t 4 fi
process 1 thead 4 finish
Applies "finish" to process 1, thread 4.
f 2 fi
frame 2 finish
Exit frame 2, leaving program execution in what was frame 3. Try also
"frame 3 until" which will do the same thing when the function is not
recursive.
)";
Err DoFinish(ConsoleContext* context, const Command& cmd) {
Err err = AssertStoppedThreadWithFrameCommand(context, cmd, "finish");
if (err.has_error())
return err;
Stack& stack = cmd.thread()->GetStack();
size_t frame_index;
if (auto found_frame_index = stack.IndexForFrame(cmd.frame()))
frame_index = *found_frame_index;
else
return Err("Internal error, frame not found in current thread.");
auto controller = std::make_unique<FinishThreadController>(stack, frame_index);
cmd.thread()->ContinueWith(std::move(controller), [](const Err& err) {
if (err.has_error())
Console::get()->Output(err);
});
return Err();
}
// jump --------------------------------------------------------------------------------------------
const char kJumpShortHelp[] = "jump / jmp: Set the instruction pointer to a different address.";
const char kJumpHelp[] =
R"(jump <location>
Alias: "jmp"
Sets the instruction pointer of the thread to the given address. It does not
continue execution. You can "step" or "continue" from the new location.
You are responsible for what this means semantically since one can't
generally change the instruction flow and expect things to work.
Location arguments
)" LOCATION_ARG_HELP("jump");
Err DoJump(ConsoleContext* context, const Command& cmd) {
if (Err err = AssertStoppedThreadCommand(context, cmd, true, "jump"); err.has_error())
return err;
if (cmd.args().size() != 1)
return Err("The 'jump' command requires one argument for the location.");
Location location;
if (Err err = ResolveUniqueInputLocation(cmd.frame(), cmd.args()[0], true, &location);
err.has_error())
return err;
cmd.thread()->JumpTo(location.address(), [thread = cmd.thread()->GetWeakPtr()](const Err& err) {
Console* console = Console::get();
if (err.has_error()) {
console->Output(err);
} else if (thread) {
// Reset the current stack frame to the top to reflect the location the user has just jumped
// to.
console->context().SetActiveFrameIdForThread(thread.get(), 0);
// Tell the user where they are.
console->context().OutputThreadContext(thread.get(), debug_ipc::ExceptionType::kNone, {});
}
});
return Err();
}
// locals ------------------------------------------------------------------------------------------
const char kLocalsShortHelp[] = "locals: Print local variables and function args.";
const char kLocalsHelp[] =
R"(locals
Prints all local variables and the current function's arguments. By default
it will print the variables for the currently selected stack frame.
You can override the stack frame with the "frame" noun to get the locals
for any specific stack frame of thread.
Arguments
)" FORMAT_VALUE_SWITCHES
R"(
Examples
locals
Prints locals and args for the current stack frame.
f 4 locals
frame 4 locals
thread 2 frame 3 locals
Prints locals for a specific stack frame.
f 4 locals -t
Prints locals with types.
)";
Err DoLocals(ConsoleContext* context, const Command& cmd) {
if (Err err = AssertStoppedThreadWithFrameCommand(context, cmd, "locals"); err.has_error())
return err;
const Location& location = cmd.frame()->GetLocation();
if (!location.symbol())
return Err("There is no symbol information for the frame.");
const Function* function = location.symbol().Get()->AsFunction();
if (!function)
return Err("Symbols are corrupt.");
// Walk upward from the innermost lexical block for the current IP to collect local variables.
// Using the map allows collecting only the innermost version of a given name, and sorts them as
// we go.
//
// Need owning variable references to copy data out.
std::map<std::string, fxl::RefPtr<Variable>> vars;
VisitLocalBlocks(function->GetMostSpecificChild(location.symbol_context(), location.address()),
[&vars](const CodeBlock* block) {
for (const auto& lazy_var : block->variables()) {
const Variable* var = lazy_var.Get()->AsVariable();
if (!var)
continue; // Symbols are corrupt.
if (var->artificial())
continue; // Skip compiler-generated symbols.
const std::string& name = var->GetAssignedName();
if (vars.find(name) == vars.end())
vars[name] = RefPtrTo(var); // New one.
}
return VisitResult::kContinue;
});
// Add function parameters. Don't overwrite existing names in case of duplicates to duplicate the
// shadowing rules of the language.
for (const auto& param : function->parameters()) {
const Variable* var = param.Get()->AsVariable();
if (!var)
continue; // Symbols are corrupt.
// Here we do not exclude artificial parameters. "this" will be marked as artificial and we want
// to include it. We could special-case the object pointer and exclude the rest, but there's not
// much other use for compiler-generated parameters for now.
const std::string& name = var->GetAssignedName();
if (vars.find(name) == vars.end())
vars[name] = RefPtrTo(var); // New one.
}
if (vars.empty()) {
Console::get()->Output("No local variables in scope.");
return Err();
}
ConsoleFormatOptions options;
if (Err err = GetConsoleFormatOptions(cmd, &options); err.has_error())
return err;
auto output = fxl::MakeRefCounted<AsyncOutputBuffer>();
for (const auto& pair : vars) {
output->Append(
FormatVariableForConsole(pair.second.get(), options, cmd.frame()->GetEvalContext()));
output->Append("\n");
}
output->Complete();
Console::get()->Output(std::move(output));
return Err();
}
// next --------------------------------------------------------------------------------------------
const char kNextShortHelp[] = "next / n: Single-step over one source line.";
const char kNextHelp[] =
R"(next / n
When a thread is stopped, "next" will execute one source line, stepping over
subroutine call instructions, and stop the thread again. If the thread is
running it will issue an error.
By default, "next" will operate on the current thread. If a thread context
is given, the specified thread will be single-stepped. You can't single-step
a process.
See also "step" to step into subroutine calls or "nexti" to step machine
instructions.
Examples
n
next
Step the current thread.
t 2 n
thread 2 next
Steps thread 2 in the current process.
pr 3 n
process 3 next
Steps the current thread in process 3 (regardless of which process is
the current process).
pr 3 t 2 n
process 3 thread 2 next
Steps thread 2 in process 3.
)";
Err DoNext(ConsoleContext* context, const Command& cmd) {
Err err = AssertStoppedThreadCommand(context, cmd, true, "next");
if (err.has_error())
return err;
auto controller = std::make_unique<StepOverThreadController>(StepMode::kSourceLine);
cmd.thread()->ContinueWith(std::move(controller), [](const Err& err) {
if (err.has_error())
Console::get()->Output(err);
});
return Err();
}
// nexti -----------------------------------------------------------------------
const char kNextiShortHelp[] = "nexti / ni: Single-step over one machine instruction.";
const char kNextiHelp[] =
R"(nexti / ni
When a thread is stopped, "nexti" will execute one machine instruction,
stepping over subroutine call instructions, and stop the thread again.
If the thread is running it will issue an error.
Only machine call instructions ("call" on x86 and "bl" on ARM) will be
stepped over with this command. This is not the only way to do a subroutine
call, as code can manually set up a call frame and jump. These jumps will not
count as a call and this command will step into the resulting frame.
By default, "nexti" will operate on the current thread. If a thread context
is given, the specified thread will be single-stepped. You can't single-step
a process.
See also "stepi" to step into subroutine calls.
Examples
ni
nexti
Step the current thread.
t 2 ni
thread 2 nexti
Steps thread 2 in the current process.
pr 3 ni
process 3 nexti
Steps the current thread in process 3 (regardless of which process is
the current process).
pr 3 t 2 ni
process 3 thread 2 nexti
Steps thread 2 in process 3.
)";
Err DoNexti(ConsoleContext* context, const Command& cmd) {
Err err = AssertStoppedThreadCommand(context, cmd, true, "nexti");
if (err.has_error())
return err;
auto controller = std::make_unique<StepOverThreadController>(StepMode::kInstruction);
cmd.thread()->ContinueWith(std::move(controller), [](const Err& err) {
if (err.has_error())
Console::get()->Output(err);
});
return Err();
}
// pause -------------------------------------------------------------------------------------------
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.
TODO(brettw) it might be nice to have a --other flag that would pause
all threads other than the specified one.
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) {
// 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;
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(), debug_ipc::ExceptionType::kNone,
{});
} 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) {
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();
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(), debug_ipc::ExceptionType::kNone,
{});
}
});
return Err();
}
// Source information on this thread will be printed out on completion. The current thread may be
// null.
Err PauseSystem(System* system, Thread* current_thread) {
Err err;
if (!VerifySystemHasRunningProcess(system, &err))
return err;
fxl::WeakPtr<Thread> weak_thread;
if (current_thread)
weak_thread = current_thread->GetWeakPtr();
system->Pause([weak_system = system->GetWeakPtr(), weak_thread]() {
// Provide messaging about the system pause.
if (!weak_system)
return;
OutputBuffer out;
Console* console = Console::get();
// 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 || !weak_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 (weak_thread) {
console->context().OutputThreadContext(weak_thread.get(), debug_ipc::ExceptionType::kNone,
{});
}
});
return Err();
}
Err DoPause(ConsoleContext* context, const Command& cmd) {
Err err = cmd.ValidateNouns({Noun::kProcess, Noun::kThread});
if (err.has_error())
return err;
if (cmd.HasNoun(Noun::kThread))
return PauseThread(context, cmd.thread());
if (cmd.HasNoun(Noun::kProcess))
return PauseTarget(context, cmd.target(), cmd.thread());
return PauseSystem(&context->session()->system(), cmd.thread());
}
// print -------------------------------------------------------------------------------------------
const char kPrintShortHelp[] = "print / p: Print a variable or expression.";
const char kPrintHelp[] =
R"(print <expression>
Alias: p
Evaluates a simple expression or variable name and prints the result.
The expression is evaluated by default in the currently selected thread and
stack frame. You can override this with "frame <x> print ...".
👉 See "help expressions" for how to write expressions.
Arguments
)" FORMAT_VALUE_SWITCHES
R"(
Examples
p foo
print foo
Print a variable
p *foo->bar
print &foo.bar[2]
Deal with structs and arrays.
f 2 p -t foo
frame 2 print -t foo
thread 1 frame 2 print -t foo
Print a variable with types in the context of a specific stack frame.
)";
Err DoPrint(ConsoleContext* context, const Command& cmd) {
// This will work in any context, but the data that's available will vary depending on whether
// there's a stopped thread, a process, or nothing.
fxl::RefPtr<EvalContext> eval_context = GetEvalContextForCommand(cmd);
ConsoleFormatOptions options;
Err err = GetConsoleFormatOptions(cmd, &options);
if (err.has_error())
return err;
auto data_provider = eval_context->GetDataProvider();
return EvalCommandExpression(
cmd, "print", eval_context, false, false, [options, eval_context](ErrOrValue value) {
if (value.has_error())
Console::get()->Output(value.err());
else
Console::get()->Output(FormatValueForConsole(value.value(), options, eval_context));
});
}
// step --------------------------------------------------------------------------------------------
const char kStepShortHelp[] = "step / s: Step one source line, going into subroutines.";
const char kStepHelp[] =
R"(step [ <function-fragment> ]
Alias: "s"
When a thread is stopped, "step" will execute one source line and stop the
thread again. This will follow execution into subroutines. If the thread is
running it will issue an error.
By default, "step" will single-step the current thread. If a thread context
is given, the specified thread will be stepped. You can't step a process.
Other threads in the process will be unchanged so will remain running or
stopped.
If the thread ends up in a new function, that function's prologue will be
automatically skipped before the operation complets. An option to control
whether this happens can be added in the future if desired (bug 45309).
See also "stepi".
Stepping into specific functions
If provided, the parameter will specify a specific function call to step
into.
The string will be matched against the symbol names of subroutines called
directly from the current line. Execution will stop if the function name
contains this fragment, and automatically complete that function call
otherwise.
Arguments
--unsymbolized | -u
Force stepping into functions with no symbols. Normally "step" will
skip over library calls or thunks with no symbols. This option allows
one to step into these unsymbolized calls.
Examples
s
step
Step the current thread.
t 2 s
thread 2 step
Steps thread 2 in the current process.
s Pri
Steps into a function with the substring "Pri" anywhere in its name. If
you have a complex line such as:
Print(GetFoo(), std::string("bar");
The "s Pri" command will step over the GetFoo() and std::string() calls,
and leave execution at the beginning of the "Print" subroutine.
)";
Err DoStep(ConsoleContext* context, const Command& cmd) {
Err err = AssertStoppedThreadCommand(context, cmd, true, "step");
if (err.has_error())
return err;
// All controllers do this on completion.
auto completion = [](const Err& err) {
if (err.has_error())
Console::get()->Output(err);
};
if (cmd.args().empty()) {
// Step for a single line.
auto controller = std::make_unique<StepIntoThreadController>(StepMode::kSourceLine);
controller->set_stop_on_no_symbols(cmd.HasSwitch(kStepIntoUnsymbolized));
cmd.thread()->ContinueWith(std::move(controller), std::move(completion));
} else if (cmd.args().size() == 1) {
// Step into a specific named subroutine. This uses the "step over" controller with a special
// condition.
if (cmd.HasSwitch(kStepIntoUnsymbolized)) {
return Err(
"The --unsymbolized switch is not compatible with a named "
"subroutine to step\ninto.");
}
auto controller = std::make_unique<StepOverThreadController>(StepMode::kSourceLine);
controller->set_subframe_should_stop_callback(
[substr = cmd.args()[0]](const Frame* frame) -> bool {
const Symbol* symbol = frame->GetLocation().symbol().Get();
if (!symbol)
return false; // Unsymbolized location, continue.
return symbol->GetFullName().find(substr) != std::string::npos;
});
cmd.thread()->ContinueWith(std::move(controller), std::move(completion));
} else {
return Err("Too many arguments for 'step'.");
}
return Err();
}
// stepi -------------------------------------------------------------------------------------------
const char kStepiShortHelp[] = "stepi / si: Single-step a thread one machine instruction.";
const char kStepiHelp[] =
R"(stepi / si
When a thread is stopped, "stepi" will execute one machine instruction and
stop the thread again. If the thread is running it will issue an error.
By default, "stepi" will single-step the current thread. If a thread context
is given, the specified thread will be single-stepped. You can't single-step
a process.
See also "nexti" to step over subroutine calls.
Examples
si
stepi
Step the current thread.
t 2 si
thread 2 stepi
Steps thread 2 in the current process.
pr 3 si
process 3 stepi
Steps the current thread in process 3 (regardless of which process is
the current process).
pr 3 t 2 si
process 3 thread 2 stepi
Steps thread 2 in process 3.
)";
Err DoStepi(ConsoleContext* context, const Command& cmd) {
Err err = AssertStoppedThreadCommand(context, cmd, true, "stepi");
if (err.has_error())
return err;
cmd.thread()->StepInstruction();
return Err();
}
// regs --------------------------------------------------------------------------------------------
using debug_ipc::RegisterCategory;
const char kRegsShortHelp[] = "regs / rg: Show the current registers for a thread.";
const char kRegsHelp[] =
R"(regs [(--category|-c)=<category>] [(--extended|-e)] [<regexp>]
Alias: "rg"
Shows the current registers for a stack frame. The thread must be stopped.
By default the general purpose registers will be shown, but more can be
configures through switches.
When the frame is not the topmost stack frame, the regsiters shown will be
only those saved on the stack. The values will reflect the value of the
registers at the time that stack frame was active. To get the current CPU
registers, run "regs" on frame 0.
Category selection arguments
-a
--all
Prints all register categories.
-g
--general (default)
Prints the general CPU registers.
-f
--float
Prints the dedicated floating-point registers but most users will want
--vector instead. 64-bit ARM uses vector registers for floating
point and has no separate floating-point registers. Almost all x64 code
also uses vector registers for floating-point computations.
-v
--vector
Prints the vector registers. These will be displayed in a table according
to the current "vector-format" setting (use "get vector-format" for
the current value and options, and "set vector-format <new-value>" to set).
Note that the vector register table will be displayed with the low values
on the right side, which is the opposite order that the expression
evaluator (which treats them as arrays) displays them.
-d
--debug
Prints the debug registers.
-e
--extended
Enables more verbose flag decoding. This will enable more information
that is not normally useful for everyday debugging. This includes
information such as the system level flags within the RFLAGS register for
x86.
Reading and writing individual registers
The "regs" command only shows full categories of registers. If you want to see
individual ones or modify them, use the expression system:
[zxdb] print $rax
41
[zxdb] print -x $rbx # Use -x for hex formatting.
0x7cc6120190
[zxdb] print $xmm3
{0.0, 3.14159} # Note [0] index is first in contrast to the table view!
[zxdb] print $xmm3[0]
3.14159
The print command can also be used to set register values:
[zxdb] print $rax = 42
42
The "$" may be omitted for registers if there is no collision with program
variables.
Examples
regs
thread 4 regs -v
process 2 thread 1 regs --all
frame 2 regs
)";
// Switches
constexpr int kRegsAllSwitch = 1;
constexpr int kRegsGeneralSwitch = 2;
constexpr int kRegsFloatingPointSwitch = 3;
constexpr int kRegsVectorSwitch = 4;
constexpr int kRegsDebugSwitch = 5;
constexpr int kRegsExtendedSwitch = 6;
void OnRegsComplete(const Err& cmd_err, const std::vector<debug_ipc::Register>& registers,
const FormatRegisterOptions& options, bool top_stack_frame) {
Console* console = Console::get();
if (cmd_err.has_error()) {
console->Output(cmd_err);
return;
}
if (registers.empty()) {
if (top_stack_frame) {
console->Output("No matching registers.");
} else {
console->Output("No matching registers saved with this non-topmost stack frame.");
}
return;
}
// Always output warning first if needed. If the filtering fails it could be because the register
// wasn't saved.
if (!top_stack_frame) {
OutputBuffer warning_out;
warning_out.Append(Syntax::kWarning, GetExclamation());
warning_out.Append(" Stack frame is not topmost. Only saved registers will be available.\n");
console->Output(warning_out);
}
OutputBuffer out;
out.Append(Syntax::kComment,
" (Use \"print $registername\" to show a single one, or\n"
" \"print $registername = newvalue\" to set.)\n\n");
out.Append(FormatRegisters(options, registers));
console->Output(out);
}
// When we request more than one category of registers, this collects all of them and keeps track
// of how many callbacks are remaining.
struct RegisterCollector {
Err err; // Most recent error from all callbacks, if any.
std::vector<debug_ipc::Register> registers;
int remaining_callbacks = 0;
// Parameters to OnRegsComplete().
FormatRegisterOptions options;
bool top_stack_frame;
};
Err DoRegs(ConsoleContext* context, const Command& cmd) {
using debug_ipc::RegisterCategory;
Err err = AssertStoppedThreadWithFrameCommand(context, cmd, "regs");
if (err.has_error())
return err;
FormatRegisterOptions options;
options.arch = cmd.thread()->session()->arch();
std::string vec_fmt = cmd.target()->settings().GetString(ClientSettings::Target::kVectorFormat);
if (auto found = StringToVectorRegisterFormat(vec_fmt))
options.vector_format = *found;
if (!cmd.args().empty())
return Err("\"regs\" takes no arguments. To show an individual register, use \"print\".");
bool top_stack_frame = (cmd.frame() == cmd.thread()->GetStack()[0]);
// General purpose are the default. Other categories can only be shown for the top stack frame
// since they require reading from the current CPU state.
std::set<RegisterCategory> category_set;
if (cmd.HasSwitch(kRegsAllSwitch)) {
category_set.insert(RegisterCategory::kGeneral);
category_set.insert(RegisterCategory::kFloatingPoint);
category_set.insert(RegisterCategory::kVector);
category_set.insert(RegisterCategory::kDebug);
}
if (cmd.HasSwitch(kRegsGeneralSwitch))
category_set.insert(RegisterCategory::kGeneral);
if (cmd.HasSwitch(kRegsFloatingPointSwitch))
category_set.insert(RegisterCategory::kFloatingPoint);
if (cmd.HasSwitch(kRegsVectorSwitch))
category_set.insert(RegisterCategory::kVector);
if (cmd.HasSwitch(kRegsDebugSwitch))
category_set.insert(RegisterCategory::kDebug);
// Default to "general" if no categories specified.
if (category_set.empty())
category_set.insert(RegisterCategory::kGeneral);
options.extended = cmd.HasSwitch(kRegsExtendedSwitch);
if (category_set.size() == 1 && *category_set.begin() == RegisterCategory::kGeneral) {
// Any available general registers should be available synchronously.
auto* regs = cmd.frame()->GetRegisterCategorySync(debug_ipc::RegisterCategory::kGeneral);
FXL_DCHECK(regs);
OnRegsComplete(Err(), *regs, options, top_stack_frame);
} else {
auto collector = std::make_shared<RegisterCollector>();
collector->remaining_callbacks = static_cast<int>(category_set.size());
collector->options = std::move(options);
collector->top_stack_frame = top_stack_frame;
for (auto category : category_set) {
cmd.frame()->GetRegisterCategoryAsync(
category, true,
[collector](const Err& err, const std::vector<debug_ipc::Register>& new_regs) {
// Save the new registers.
collector->registers.insert(collector->registers.end(), new_regs.begin(),
new_regs.end());
// Save the error. Just keep the most recent error if there are multiple.
if (err.has_error())
collector->err = err;
FXL_DCHECK(collector->remaining_callbacks > 0);
collector->remaining_callbacks--;
if (collector->remaining_callbacks == 0) {
OnRegsComplete(collector->err, collector->registers, collector->options,
collector->top_stack_frame);
}
});
}
}
return Err();
}
// until -------------------------------------------------------------------------------------------
const char kUntilShortHelp[] = "until / u: Runs a thread until a location is reached.";
const char kUntilHelp[] =
R"(until <location>
Alias: "u"
Continues execution of a thread or a process until a given location is
reached. You could think of this command as setting an implicit one-shot
breakpoint at the given location and continuing execution.
Normally this operation will apply only to the current thread. To apply to
all threads in a process, use "process until" (see the examples below).
See also "finish".
Location arguments
Current frame's address (no input)
until
)" LOCATION_ARG_HELP("until")
R"(
Examples
u
until
Runs until the current frame's location is hit again. This can be useful
if the current code is called in a loop to advance to the next iteration
of the current code.
f 1 u
frame 1 until
Runs until the given frame's location is hit. Since frame 1 is
always the current function's calling frame, this command will normally
stop when the current function returns. The exception is if the code
in the calling function is called recursively from the current location,
in which case the next invocation will stop ("until" does not match
stack frames on break). See "finish" for a stack-aware version.
u 24
until 24
Runs the current thread until line 24 of the current frame's file.
until foo.cc:24
Runs the current thread until the given file/line is reached.
thread 2 until 24
process 1 thread 2 until 24
Runs the specified thread until line 24 is reached. When no filename is
given, the specified thread's currently selected frame will be used.
u MyClass::MyFunc
until MyClass::MyFunc
Runs the current thread until the given function is called.
pr u MyClass::MyFunc
process until MyClass::MyFunc
Continues all threads of the current process, stopping the next time any
of them call the function.
)";
Err DoUntil(ConsoleContext* context, const Command& cmd) {
Err err;
// Decode the location.
//
// The validation on this is a bit tricky. Most uses apply to the current thread and take some
// implicit information from the current frame (which requires the thread be stopped). But when
// doing a process-wide one, don't require a currently stopped thread unless it's required to
// compute the location.
std::vector<InputLocation> locations;
if (cmd.args().empty()) {
// No args means use the current location.
if (!cmd.frame()) {
return Err(ErrType::kInput, "There isn't a current frame to take the location from.");
}
locations.emplace_back(cmd.frame()->GetAddress());
} else if (cmd.args().size() == 1) {
// One arg = normal location (this function can handle null frames).
Err err = ParseLocalInputLocation(cmd.frame(), cmd.args()[0], &locations);
if (err.has_error())
return err;
} else {
return Err(ErrType::kInput,
"Expecting zero or one arg for the location.\n"
"Formats: <function>, <file>:<line#>, <line#>, or *<address>");
}
auto callback = [](const Err& err) {
if (err.has_error())
Console::get()->Output(err);
};
// Dispatch the request.
if (cmd.HasNoun(Noun::kProcess) && !cmd.HasNoun(Noun::kThread) && !cmd.HasNoun(Noun::kFrame)) {
// Process-wide ("process until ...").
err = AssertRunningTarget(context, "until", cmd.target());
if (err.has_error())
return err;
cmd.target()->GetProcess()->ContinueUntil(locations, callback);
} else {
// Thread-specific.
err = AssertStoppedThreadWithFrameCommand(context, cmd, "until");
if (err.has_error())
return err;
auto controller = std::make_unique<UntilThreadController>(std::move(locations));
cmd.thread()->ContinueWith(std::move(controller), [](const Err& err) {
if (err.has_error())
Console::get()->Output(err);
});
}
return Err();
}
} // namespace
void AppendThreadVerbs(std::map<Verb, VerbRecord>* verbs) {
// Shared options for value printing.
SwitchRecord force_types(kForceAllTypes, false, "types", 't');
SwitchRecord raw(kRawOutput, false, "raw", 'r');
const std::vector<SwitchRecord> format_switches{
force_types,
raw,
SwitchRecord(kVerboseFormat, false, "verbose", 'v'),
SwitchRecord(kForceNumberChar, false, "", 'c'),
SwitchRecord(kForceNumberSigned, false, "", 'd'),
SwitchRecord(kForceNumberUnsigned, false, "", 'u'),
SwitchRecord(kForceNumberHex, false, "", 'x'),
SwitchRecord(kMaxArraySize, true, "max-array")};
// backtrace
VerbRecord backtrace(&DoBacktrace, {"backtrace", "bt"}, kBacktraceShortHelp, kBacktraceHelp,
CommandGroup::kQuery);
backtrace.switches = {force_types, raw, SwitchRecord(kVerboseBacktrace, false, "verbose", 'v')};
(*verbs)[Verb::kBacktrace] = std::move(backtrace);
(*verbs)[Verb::kContinue] =
VerbRecord(&DoContinue, {"continue", "cont", "c"}, kContinueShortHelp, kContinueHelp,
CommandGroup::kStep, SourceAffinity::kSource);
(*verbs)[Verb::kFinish] =
VerbRecord(&DoFinish, {"finish", "fi"}, kFinishShortHelp, kFinishHelp, CommandGroup::kStep);
(*verbs)[Verb::kJump] = VerbRecord(&DoJump, &CompleteInputLocation, {"jump", "jmp"},
kJumpShortHelp, kJumpHelp, CommandGroup::kStep);
// locals
VerbRecord locals(&DoLocals, {"locals"}, kLocalsShortHelp, kLocalsHelp, CommandGroup::kQuery);
locals.switches = format_switches;
(*verbs)[Verb::kLocals] = std::move(locals);
(*verbs)[Verb::kNext] = VerbRecord(&DoNext, {"next", "n"}, kNextShortHelp, kNextHelp,
CommandGroup::kStep, SourceAffinity::kSource);
(*verbs)[Verb::kNexti] = VerbRecord(&DoNexti, {"nexti", "ni"}, kNextiShortHelp, kNextiHelp,
CommandGroup::kAssembly, SourceAffinity::kAssembly);
(*verbs)[Verb::kPause] =
VerbRecord(&DoPause, {"pause", "pa"}, kPauseShortHelp, kPauseHelp, CommandGroup::kProcess);
// print
VerbRecord print(&DoPrint, {"print", "p"}, kPrintShortHelp, kPrintHelp, CommandGroup::kQuery);
print.switches = format_switches;
print.param_type = VerbRecord::kOneParam;
(*verbs)[Verb::kPrint] = std::move(print);
// regs
VerbRecord regs(&DoRegs, {"regs", "rg"}, kRegsShortHelp, kRegsHelp, CommandGroup::kAssembly);
regs.switches.emplace_back(kRegsAllSwitch, false, "all", 'a');
regs.switches.emplace_back(kRegsGeneralSwitch, false, "general", 'g');
regs.switches.emplace_back(kRegsFloatingPointSwitch, false, "float", 'f');
regs.switches.emplace_back(kRegsVectorSwitch, false, "vector", 'v');
regs.switches.emplace_back(kRegsDebugSwitch, false, "debug", 'd');
regs.switches.emplace_back(kRegsExtendedSwitch, false, "extended", 'e');
(*verbs)[Verb::kRegs] = std::move(regs);
// step
SwitchRecord step_force(kStepIntoUnsymbolized, false, "unsymbolized", 'u');
VerbRecord step(&DoStep, {"step", "s"}, kStepShortHelp, kStepHelp, CommandGroup::kStep,
SourceAffinity::kSource);
step.switches.push_back(step_force);
(*verbs)[Verb::kStep] = std::move(step);
(*verbs)[Verb::kStepi] = VerbRecord(&DoStepi, {"stepi", "si"}, kStepiShortHelp, kStepiHelp,
CommandGroup::kAssembly, SourceAffinity::kAssembly);
(*verbs)[Verb::kSteps] = GetStepsVerbRecord();
(*verbs)[Verb::kUntil] = VerbRecord(&DoUntil, &CompleteInputLocation, {"until", "u"},
kUntilShortHelp, kUntilHelp, CommandGroup::kStep);
// Stack navigation
(*verbs)[Verb::kDown] =
VerbRecord(&DoDown, {"down"}, kDownShortHelp, kDownHelp, CommandGroup::kGeneral);
(*verbs)[Verb::kUp] = VerbRecord(&DoUp, {"up"}, kUpShortHelp, kUpHelp, CommandGroup::kGeneral);
}
} // namespace zxdb