blob: a21bb5890652eb6e24a90a73e1ee215e6b5685f8 [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_break.h"
#include "src/developer/debug/zxdb/client/breakpoint.h"
#include "src/developer/debug/zxdb/client/breakpoint_location.h"
#include "src/developer/debug/zxdb/client/breakpoint_settings.h"
#include "src/developer/debug/zxdb/client/frame.h"
#include "src/developer/debug/zxdb/client/session.h"
#include "src/developer/debug/zxdb/client/setting_schema_definition.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/console_context.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/verbs.h"
namespace zxdb {
namespace {
constexpr int kSizeSwitch = 1;
constexpr int kStopSwitch = 2;
constexpr int kDisabledSwitch = 3;
constexpr int kTypeSwitch = 4;
constexpr int kOneShotSwitch = 5;
constexpr int kMultSwitch = 6;
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") LOCATION_EXPRESSION_HELP("break")
R"( You can also specify the magic symbol "$main" to break on the process'
entrypoint:
break $main
Options
--disabled
-d
Creates the breakpoint as initially disabled. Otherwise, it will be
enabled.
--hit-mult=<count>
-m <count>
Only breaks the execution every <count> times the breakpoint is hit.
--one-shot
-o
Creates a one-shot breakpoint. One-shot breakpoints are automatically
deleted after they are hit once.
--size=<byte-size>
-s <byte-size>
Size in bytes for hardware write and read-write breakpoints. This will
default to 4 if unspecified. Not valid for hardware or software execution
breakpoints. The address will need to be aligned to an even multiple of
its size.
--stop=[ all | process | thread | none ]
-p [ 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=<type>
-t <type>
The type of the breakpoint. Defaults to "software". Possible values are:
)" BREAKPOINT_TYPE_HELP(" ")
R"(
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.
ELF PLT breakpoints for system calls
Breakpoints can be set in the code in the ELF Procedure Linkage Table. This
code is the tiny stub that the dynamic linker fixes up to resolve each
function call imported from other ELF objects.
This allows is setting breakpoints on system calls without using hardware
breakpoints. The Zircon vDSO is mapped read-only which prevents the debugger
from inserting hardware breakpoints. But each library's calls to vDSO
functions goes through that library's PLT which is writable by the debugger.
To indicate a PLT breakpoint, use the form $plt(...):
[zxdb] break $plt(zx_debug_write)
This will apply the breakpoint to every library's PLT entry for
"zx_debug_write".
The supplied string must be the exact name in the ELF binary. This means C++
symbols must be mangled.
Breakpoints on overloaded functions
If a named function has multiple overloads, the debugger will set a breakpoint
on all of them. Specifying an individual overload by name is not supported
(bug 41928).
To refer to an individual overload, either refer to the location by file:line
or by address. To get the addresses of each overload, use the command
"sym-info FunctionName".
Editing breakpoint attributes
Individual breakpoint attributes can be accessed with the "get" and "set"
commands. To list all attributes on the current breakpoint:
bp get
To get a specific value along with help for what the setting means, give the
specific attribute:
bp get stop
And to set the attribute:
bp set stop = thread
Other breakpoint commands
"breakpoint" / "bp": List or select breakpoints.
"clear": To delete breakpoints.
"disable": Disable a breakpoint without deleting it.
"enable": Enable a previously-disabled breakpoint.
"watch": Create a hardware write breakpoint.
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
break *$rip + 0x10
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 --type execute 23
Break at line 23 of the file referenced by the current frame and use a
hardware execution breakpoint.
)";
void OutputCreatedMessage(ConsoleContext* context, Breakpoint* breakpoint) {
OutputBuffer out("Created ");
out.Append(FormatBreakpoint(context, breakpoint, true));
Console::get()->Output(out);
}
Err RunVerbBreak(ConsoleContext* context, const Command& cmd, CommandCallback cb) {
Err err = cmd.ValidateNouns({Noun::kProcess, Noun::kThread, Noun::kFrame, Noun::kBreakpoint});
if (err.has_error())
return err;
// Get existing settings (or defaults for new one).
BreakpointSettings settings;
// Disabled flag.
if (cmd.HasSwitch(kDisabledSwitch))
settings.enabled = false;
// One-shot.
if (cmd.HasSwitch(kOneShotSwitch))
settings.one_shot = true;
// Stop mode.
if (cmd.HasSwitch(kStopSwitch)) {
auto stop_mode = BreakpointSettings::StringToStopMode(cmd.GetSwitchValue(kStopSwitch));
if (!stop_mode) {
return Err(
"--%s requires \"%s\", \"%s\", \"%s\", or \"%s\".", ClientSettings::Breakpoint::kStopMode,
ClientSettings::Breakpoint::kStopMode_All, ClientSettings::Breakpoint::kStopMode_Process,
ClientSettings::Breakpoint::kStopMode_Thread, ClientSettings::Breakpoint::kStopMode_None);
}
settings.stop_mode = *stop_mode;
}
// Type.
settings.type = BreakpointSettings::Type::kSoftware;
if (cmd.HasSwitch(kTypeSwitch)) {
if (auto opt_type = BreakpointSettings::StringToType(cmd.GetSwitchValue(kTypeSwitch)))
settings.type = *opt_type;
else
return Err("Unknown breakpoint type.");
}
// Size. Track if this is set or not si we can change the default based on the expression result.
bool has_explicit_size = false;
if (cmd.HasSwitch(kSizeSwitch)) {
has_explicit_size = true;
if (!BreakpointSettings::TypeHasSize(settings.type))
return Err("Breakpoint size is only supported for write and read-write breakpoints.");
// TODO(dangyi): settings.byte_size should be validated by BreakpointSettings::ValidateSize.
if (Err err = StringToUint32(cmd.GetSwitchValue(kSizeSwitch), &settings.byte_size);
err.has_error())
return err;
} else if (BreakpointSettings::TypeHasSize(settings.type)) {
settings.byte_size = 4; // Default size.
}
// Scope.
settings.scope = ExecutionScopeForCommand(cmd);
// Hit mult.
if (cmd.HasSwitch(kMultSwitch)) {
int hit_mult;
if (Err err = StringToInt(cmd.GetSwitchValue(kMultSwitch), &hit_mult); err.has_error())
return err;
// TODO(dangyi): Unify validation logics with settings.
if (hit_mult <= 0)
return Err("hit-mult must be positive.");
settings.hit_mult = hit_mult;
}
if (cmd.args().empty()) {
// 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.locations.emplace_back(frame_loc.file_line());
else
settings.locations.emplace_back(cmd.frame()->GetAddress());
// New breakpoint.
Breakpoint* breakpoint = context->session()->system().CreateNewBreakpoint();
context->SetActiveBreakpoint(breakpoint);
breakpoint->SetSettings(settings);
OutputCreatedMessage(context, breakpoint);
if (cb)
cb(err);
return Err();
}
// Parse the given input location in args[0]. This may require async evaluation.
EvalLocalInputLocation(
GetEvalContextForCommand(cmd), cmd.frame(), cmd.args()[0],
[settings, has_explicit_size, cb = std::move(cb)](ErrOr<std::vector<InputLocation>> locs,
std::optional<uint32_t> expr_size) mutable {
if (locs.has_error()) {
Console::get()->Output(locs.err());
if (cb)
cb(locs.err());
return;
}
// New breakpoint.
ConsoleContext* context = &Console::get()->context();
Breakpoint* breakpoint = context->session()->system().CreateNewBreakpoint();
context->SetActiveBreakpoint(breakpoint);
if (BreakpointSettings::TypeHasSize(settings.type) && !has_explicit_size && expr_size) {
// Input expression has a size we should default to.
settings.byte_size = *expr_size;
}
settings.locations = locs.take_value();
breakpoint->SetSettings(settings);
OutputCreatedMessage(context, breakpoint);
if (cb)
cb(Err());
});
return Err();
}
} // namespace
VerbRecord GetBreakVerbRecord() {
SwitchRecord disabled_switch(kDisabledSwitch, false, "disabled", 'd');
SwitchRecord one_shot_switch(kOneShotSwitch, false, ClientSettings::Breakpoint::kOneShot, 'o');
SwitchRecord size_switch(kSizeSwitch, true, ClientSettings::Breakpoint::kSize, 's');
SwitchRecord stop_switch(kStopSwitch, true, ClientSettings::Breakpoint::kStopMode, 'p');
SwitchRecord type_switch(kTypeSwitch, true, ClientSettings::Breakpoint::kType, 't');
SwitchRecord mult_switch(kMultSwitch, true, ClientSettings::Breakpoint::kHitMult, 'm');
VerbRecord break_record(&RunVerbBreak, &CompleteInputLocation, {"break", "b"}, kBreakShortHelp,
kBreakHelp, CommandGroup::kBreakpoint);
break_record.param_type = VerbRecord::kOneParam; // Don't require quoting for expressions.
break_record.switches.push_back(disabled_switch);
break_record.switches.push_back(one_shot_switch);
break_record.switches.push_back(size_switch);
break_record.switches.push_back(stop_switch);
break_record.switches.push_back(type_switch);
break_record.switches.push_back(mult_switch);
return break_record;
}
} // namespace zxdb