blob: c827bb3c759744c04e1db61ac276ea04329a0b56 [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 "garnet/bin/zxdb/console/verbs.h"
#include "garnet/bin/zxdb/client/breakpoint.h"
#include "garnet/bin/zxdb/client/breakpoint_location.h"
#include "garnet/bin/zxdb/client/breakpoint_settings.h"
#include "garnet/bin/zxdb/client/frame.h"
#include "garnet/bin/zxdb/client/session.h"
#include "garnet/bin/zxdb/console/command.h"
#include "garnet/bin/zxdb/console/command_utils.h"
#include "garnet/bin/zxdb/console/console.h"
#include "garnet/bin/zxdb/console/console_context.h"
#include "garnet/bin/zxdb/console/format_context.h"
#include "garnet/bin/zxdb/console/input_location_parser.h"
#include "garnet/bin/zxdb/console/output_buffer.h"
#include "garnet/bin/zxdb/symbols/location.h"
#include "lib/fxl/strings/string_printf.h"
namespace zxdb {
namespace {
constexpr int kStopSwitch = 1;
constexpr int kEnableSwitch = 2;
constexpr int kTypeSwitch = 3;
// Callback for when updating a breakpoint is done.
void CreateOrEditBreakpointComplete(fxl::WeakPtr<Breakpoint> breakpoint,
const Err& err) {
if (!breakpoint)
return; // Do nothing if the breakpoint is gone.
Console* console = Console::get();
if (err.has_error()) {
OutputBuffer out("Error setting breakpoint: ");
out.Append(err);
console->Output(std::move(out));
return;
}
auto locs = breakpoint->GetLocations();
if (locs.empty()) {
// When the breakpoint resolved to nothing, warn the user, they may have
// made a typo.
OutputBuffer out;
out.Append(DescribeBreakpoint(&console->context(), breakpoint.get()));
out.Append(Syntax::kWarning, "\nPending");
out.Append(": No matches for location, it will be pending library loads.");
console->Output(std::move(out));
return;
}
// Successfully wrote the breakpoint.
OutputBuffer out;
out.Append(DescribeBreakpoint(&console->context(), breakpoint.get()));
out.Append("\n");
// There is a question of what to show the breakpoint enabled state. The
// breakpoint has a main enabled bit and each location (it can apply to more
// than one address -- think templates and inlined functions) within that
// breakpoint has its own. But each location normally resolves to the same
// source code location so we can't practically show the individual
// location's enabled state separately.
//
// For simplicity, just base it on the main enabled bit. Most people won't
// use location-specific enabling anyway.
//
// Ignore errors from printing the source, it doesn't matter that much.
FormatBreakpointContext(
locs[0]->GetLocation(),
breakpoint->session()->system().GetSymbols()->build_dir(),
breakpoint->GetSettings().enabled, &out);
console->Output(std::move(out));
}
// Backend for setting attributes on a breakpoint from both creation and
// editing. The given breakpoint is specified if this is an edit, or is null
// if this is a creation.
Err CreateOrEditBreakpoint(ConsoleContext* context, const Command& cmd,
Breakpoint* breakpoint) {
// Get existing settings (or defaults for new one).
BreakpointSettings settings;
if (breakpoint)
settings = breakpoint->GetSettings();
// Enable flag.
if (cmd.HasSwitch(kEnableSwitch)) {
std::string enable_str = cmd.GetSwitchValue(kEnableSwitch);
if (enable_str == "true") {
settings.enabled = true;
} else if (enable_str == "false") {
settings.enabled = false;
} else {
return Err(
"--enabled switch requires either \"true\" or \"false\" values.");
}
}
// Stop mode.
if (cmd.HasSwitch(kStopSwitch)) {
std::string stop_str = cmd.GetSwitchValue(kStopSwitch);
if (stop_str == "all") {
settings.stop_mode = BreakpointSettings::StopMode::kAll;
} else if (stop_str == "process") {
settings.stop_mode = BreakpointSettings::StopMode::kProcess;
} else if (stop_str == "thread") {
settings.stop_mode = BreakpointSettings::StopMode::kThread;
} else if (stop_str == "none") {
settings.stop_mode = BreakpointSettings::StopMode::kNone;
} else {
return Err(
"--stop switch requires \"all\", \"process\", \"thread\", "
"or \"none\".");
}
}
// Type.
auto break_type = debug_ipc::BreakpointType::kSoftware;
if (cmd.HasSwitch(kTypeSwitch)) {
std::string type_str = cmd.GetSwitchValue(kTypeSwitch);
if (type_str == "s" || type_str == "software") {
break_type = debug_ipc::BreakpointType::kSoftware;
} else if (type_str == "h" || type_str == "hardware") {
break_type = debug_ipc::BreakpointType::kHardware;
} else {
return Err(
fxl::StringPrintf("Unknown breakpoint type: %s", type_str.data()));
}
}
settings.type = break_type;
// Location.
if (cmd.args().empty()) {
if (!breakpoint) {
// Creating a breakpoint with no location implicitly uses the current
// frame's current location.
if (!cmd.frame()) {
return Err(ErrType::kInput,
"There isn't a current frame to take the breakpoint "
"location from.");
}
// Use the file/line of the frame if available. This is what a user will
// generally want to see in the breakpoint list, and will persist across
// restarts. Fall back to an address otherwise. Sometimes the file/line
// might not be what they want, though.
const Location& frame_loc = cmd.frame()->GetLocation();
if (frame_loc.has_symbols())
settings.location = InputLocation(frame_loc.file_line());
else
settings.location = InputLocation(cmd.frame()->GetAddress());
}
} else if (cmd.args().size() == 1u) {
Err err =
ParseInputLocation(cmd.frame(), cmd.args()[0], &settings.location);
if (err.has_error())
return err;
} else {
return Err(ErrType::kInput,
"Expecting only one arg for the location.\n"
"Formats: <function>, <file>:<line#>, <line#>, or *<address>");
}
// Scope.
if (cmd.HasNoun(Noun::kThread)) {
settings.scope = BreakpointSettings::Scope::kThread;
settings.scope_thread = cmd.thread();
settings.scope_target = cmd.target();
} else if (cmd.HasNoun(Noun::kProcess) ||
settings.location.type == InputLocation::Type::kAddress) {
settings.scope = BreakpointSettings::Scope::kTarget;
settings.scope_thread = nullptr;
settings.scope_target = cmd.target();
}
// TODO(brettw) We don't have a "system" noun so there's no way to express
// converting a process- or thread-specific breakpoint to a global one.
// A system noun should be added and, if specified, this code should
// convert to a global breakpoint.
// Commit the changes.
if (!breakpoint) {
// New breakpoint.
breakpoint = context->session()->system().CreateNewBreakpoint();
context->SetActiveBreakpoint(breakpoint);
}
breakpoint->SetSettings(
settings, [breakpoint = breakpoint->GetWeakPtr()](const Err& err) {
CreateOrEditBreakpointComplete(std::move(breakpoint), err);
});
return Err();
}
// break -----------------------------------------------------------------------
const char kBreakShortHelp[] = "break / b: Create a breakpoint.";
const char kBreakHelp[] =
R"(break <location>
Alias: "b"
Creates or modifies a breakpoint. Not to be confused with the "breakpoint" /
"bp" noun which lists breakpoints and modifies the breakpoint context. See
"help bp" for more.
The new breakpoint will become the active breakpoint so future breakpoint
commands will apply to it by default.
Location arguments
Current frame's address (no input)
break
)" LOCATION_ARG_HELP("break")
R"(
Options
--enable=[ true | false ]
-e [ true | false ]
Controls whether the breakpoint is enabled or disabled. A disabled
breakpoint is never hit and hit counts are not incremented, but its
settings are preserved. Defaults to enabled (true).
--stop=[ all | process | thread | none ]
-s [ all | process | thread | none ]
Controls what execution is stopped when the breakpoint is hit. By
default all threads of all debugged process will be stopped ("all") when
a breakpoint is hit. But it's possible to only stop the threads of the
current process ("process") or the thread that hit the breakpoint
("thread").
If "none" is specified, any threads hitting the breakpoint will
immediately resume, but the hit count will continue to accumulate.
--type=[ (software|s) | (hardware|h) ] (default: software)
-t [ (software|s) | (hardware|h) ]
Defines what kind of breakpoint to use. Hardware registers require support
from the architecture and are limited in quantity. Keep this in mind when
using breakpoints that will expand to several locations.
Scoping to processes and threads
Explicit context can be provided to scope a breakpoint to a single process
or a single thread. To do this, provide that process or thread as context
before the break command:
t 1 b *0x614a19837
thread 1 break *0x614a19837
Breaks on only this thread in the current process.
pr 2 b *0x614a19837
process 2 break *0x614a19837
Breaks on all threads in the given process.
When the thread of a thread-scoped breakpoint is destroyed, the breakpoint
will be converted to a disabled process-scoped breakpoint. When the process
context of a process-scoped breakpoint is destroyed, the breakpoint will be
converted to a disabled global breakpoint.
See also
"help breakpoint": To list or select breakpoints.
"help clear": To delete breakpoints.
Examples
break
Set a breakpoint at the current frame's address.
frame 1 break
Set a breakpoint at the specified frame's address. Since frame 1 is
always the current function's calling frame, this command will set a
breakpoint at the current function's return.
break MyClass::MyFunc
Breakpoint in all processes that have a function with this name.
break *0x123c9df
Process-specific breakpoint at the given address.
process 3 break MyClass::MyFunc
Process-specific breakpoint at the given function.
thread 1 break foo.cpp:34
Thread-specific breakpoint at the give file/line.
break 23
Break at line 23 of the file referenced by the current frame.
frame 3 break 23
Break at line 23 of the file referenced by frame 3.
break 32 --type h
Break at line 23 of the file referenced by the current frame and use a
hardware breakpoint.
)";
Err DoBreak(ConsoleContext* context, const Command& cmd) {
Err err = cmd.ValidateNouns(
{Noun::kProcess, Noun::kThread, Noun::kFrame, Noun::kBreakpoint});
if (err.has_error())
return err;
return CreateOrEditBreakpoint(context, cmd, nullptr);
}
// clear -----------------------------------------------------------------------
const char kClearShortHelp[] = "clear / cl: Clear a breakpoint.";
const char kClearHelp[] =
R"(clear
Alias: "cl"
By itself, "clear" will delete the current active breakpoint.
Clear a named breakpoint by specifying the breakpoint context for the
command. Unlike GDB, the context comes first, so instead of "clear 2" to
clear breakpoint #2, use "breakpoint 2 clear" (or "bp 2 cl" for short).
See also
"help break": To create breakpoints.
"help breakpoint": To manage the current breakpoint context.
Examples
breakpoint 2 clear
bp 2 cl
clear
cl
)";
Err DoClear(ConsoleContext* context, const Command& cmd) {
Err err = cmd.ValidateNouns({Noun::kBreakpoint});
if (err.has_error())
return err;
// Expect no args. If an arg was specified, most likely they're trying to
// use GDB syntax of "clear 2".
if (cmd.args().size() > 0) {
return Err(
"\"clear\" takes no arguments. To specify an explicit "
"breakpoint to clear,\nuse \"breakpoint <index> clear\" or "
"\"bp <index> cl\" for short.");
}
if (!cmd.breakpoint()) {
return Err(
"There is no active breakpoint and no breakpoint was given.\n"
"Use \"breakpoint <index> clear\" to specify one.\n");
}
std::string desc = DescribeBreakpoint(context, cmd.breakpoint());
context->session()->system().DeleteBreakpoint(cmd.breakpoint());
Console::get()->Output("Deleted " + desc);
return Err();
}
// edit ------------------------------------------------------------------------
const char kEditShortHelp[] = "edit / ed: Edit a breakpoint.";
const char kEditHelp[] =
R"(edit
Alias: "ed"
Edits an existing breakpoint. Edit requires an explicit context. The only
context currently supported is "breakpoint". Specify an explicit breakpoint
with the "breakpoint"/"bp" noun and its index:
bp 4 ed ...
breakpoint 4 edit ...
Or use the active breakpoint by omitting the index:
bp ed ...
breakpoint edit ...
The parameters accepted are any parameters accepted by the "break" command.
Specified parameters will overwrite the existing settings. If a location is
specified, the breakpoint will be moved, if a location is not specified, its
location will be unchanged.
The active breakpoint will not be changed.
See also
"help break": To create breakpoints.
"help breakpoint": To list and select the active breakpoint.
Examples
bp 2 ed --enable=false
breakpoint 2 edit --enable=false
Disable breakpoint 2.
bp ed --stop=thread
breakpoint edit --stop=thread
Make the active breakpoint stop only the thread that triggered it.
pr 1 t 6 bp 7 b 0x614a19837
process 1 thread 6 breakpoint 7 edit 0x614a19837
Modifies breakpoint 7 to only break in process 1, thread 6 at the
given address.
)";
Err DoEdit(ConsoleContext* context, const Command& cmd) {
if (!cmd.HasNoun(Noun::kBreakpoint)) {
// Edit requires an explicit "breakpoint" context so that in the future we
// can apply edit to other nouns. I'm thinking any noun that can be created
// can have its switches modified via an "edit" command that accepts the
// same settings.
return Err(ErrType::kInput,
"\"edit\" requires an explicit breakpoint context.\n"
"Either \"breakpoint edit\" for the active breakpoint, or "
"\"breakpoint <index> edit\" for an\nexplicit one.");
}
Err err =
cmd.ValidateNouns({Noun::kProcess, Noun::kThread, Noun::kBreakpoint});
if (err.has_error())
return err;
return CreateOrEditBreakpoint(context, cmd, cmd.breakpoint());
}
} // namespace
void AppendBreakpointVerbs(std::map<Verb, VerbRecord>* verbs) {
SwitchRecord enable_switch(kEnableSwitch, true, "enable", 'e');
SwitchRecord stop_switch(kStopSwitch, true, "stop", 's');
SwitchRecord type_switch(kTypeSwitch, true, "type", 't');
VerbRecord break_record(&DoBreak, {"break", "b"}, kBreakShortHelp, kBreakHelp,
CommandGroup::kBreakpoint);
break_record.switches.push_back(enable_switch);
break_record.switches.push_back(stop_switch);
break_record.switches.push_back(type_switch);
(*verbs)[Verb::kBreak] = break_record;
// Note: if "edit" becomes more general than just for breakpoints, we'll
// want to change the command category.
VerbRecord edit_record(&DoEdit, {"edit", "ed"}, kEditShortHelp, kEditHelp,
CommandGroup::kBreakpoint);
edit_record.switches.push_back(enable_switch);
edit_record.switches.push_back(stop_switch);
(*verbs)[Verb::kEdit] = edit_record;
(*verbs)[Verb::kClear] =
VerbRecord(&DoClear, {"clear", "cl"}, kClearShortHelp, kClearHelp,
CommandGroup::kBreakpoint);
}
} // namespace zxdb