[lldb] Define breakpoint location "." to mean the location(s) at which the current thread is stopped (#194272)
Adds `.` as a new `breakpt-id` syntax. Users can specify `.` to mean the breakpoint location(s) that caused the current thread to stop. I selected `.` to mean the current breakpoint locations for two reasons. In a shells, period means <ins>current</ins> directory. In prose, a period is a <ins>stop</ins>. My workflow often starts with multiple breakpoint locations, such as with regex breakpoints, or basename breakpoints for overloaded/overridden names. As locations are hit, I realize which locations are no longer needed. This new syntax makes it quick and easy to disable the currently stopped location(s). Another use case for this is to quickly repeat commands for the current location: ``` break com add -o 'p someVar' . ``` Usage example: ``` (lldb) b main.c:2 Process 47071 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 frame #0: ... main`main at main.c:2:3 1 int main() { -> 2 return 0; 3 } Target 0: (main) stopped. (lldb) breakpoint disable . 1 breakpoints disabled. (lldb) breakpoint list Current breakpoints: 1: file = 'main.c', line = 2, exact_match = 0, locations = 1 1.1: where = main`main + 12 at main.c:2:3, address = ..., hit count = 1 Options: disabled ``` rdar://73047170 Assisted-by: claude
This commit is contained in:
@@ -50,10 +50,9 @@ public:
|
||||
static std::pair<llvm::StringRef, llvm::StringRef>
|
||||
SplitIDRangeExpression(llvm::StringRef in_string);
|
||||
|
||||
static llvm::Error
|
||||
FindAndReplaceIDRanges(Args &old_args, Target *target, bool allow_locations,
|
||||
BreakpointName::Permissions ::PermissionKinds purpose,
|
||||
Args &new_args);
|
||||
static llvm::Error FindAndReplaceIDRanges(
|
||||
Args &old_args, ExecutionContext &exe_ctx, bool allow_locations,
|
||||
BreakpointName::Permissions ::PermissionKinds purpose, Args &new_args);
|
||||
|
||||
private:
|
||||
BreakpointIDArray m_breakpoint_ids;
|
||||
|
||||
@@ -11,9 +11,13 @@
|
||||
|
||||
#include "lldb/Breakpoint/Breakpoint.h"
|
||||
#include "lldb/Breakpoint/BreakpointLocation.h"
|
||||
#include "lldb/Target/ExecutionContext.h"
|
||||
#include "lldb/Target/StopInfo.h"
|
||||
#include "lldb/Target/Target.h"
|
||||
#include "lldb/Target/Thread.h"
|
||||
#include "lldb/Utility/Args.h"
|
||||
#include "lldb/Utility/StreamString.h"
|
||||
#include "lldb/lldb-forward.h"
|
||||
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
@@ -55,6 +59,16 @@ bool BreakpointIDList::Contains(BreakpointID bp_id) const {
|
||||
return llvm::is_contained(m_breakpoint_ids, bp_id);
|
||||
}
|
||||
|
||||
static std::string LocationIDForStop(StopInfoSP stop_info_sp, uint32_t idx) {
|
||||
assert(stop_info_sp->GetStopReason() == lldb::eStopReasonBreakpoint &&
|
||||
"expected breakpoint stop");
|
||||
break_id_t bp_id = stop_info_sp->GetStopReasonDataAtIndex(idx);
|
||||
break_id_t loc_id = stop_info_sp->GetStopReasonDataAtIndex(idx + 1);
|
||||
StreamString stream;
|
||||
BreakpointID::GetCanonicalReference(&stream, bp_id, loc_id);
|
||||
return stream.GetString().str();
|
||||
}
|
||||
|
||||
// This function takes OLD_ARGS, which is usually the result of breaking the
|
||||
// command string arguments into
|
||||
// an array of space-separated strings, and searches through the arguments for
|
||||
@@ -69,8 +83,9 @@ bool BreakpointIDList::Contains(BreakpointID bp_id) const {
|
||||
// by the members of the range.
|
||||
|
||||
llvm::Error BreakpointIDList::FindAndReplaceIDRanges(
|
||||
Args &old_args, Target *target, bool allow_locations,
|
||||
Args &old_args, ExecutionContext &exe_ctx, bool allow_locations,
|
||||
BreakpointName::Permissions ::PermissionKinds purpose, Args &new_args) {
|
||||
Target *target = exe_ctx.GetTargetPtr();
|
||||
llvm::StringRef range_from;
|
||||
llvm::StringRef range_to;
|
||||
llvm::StringRef current_arg;
|
||||
@@ -80,6 +95,30 @@ llvm::Error BreakpointIDList::FindAndReplaceIDRanges(
|
||||
bool is_range = false;
|
||||
|
||||
current_arg = old_args[i].ref();
|
||||
|
||||
if (allow_locations && current_arg == ".") {
|
||||
Thread *thread = exe_ctx.GetThreadPtr();
|
||||
if (!thread) {
|
||||
new_args.Clear();
|
||||
return llvm::createStringError("no current thread");
|
||||
}
|
||||
StopInfoSP stop_info_sp = thread->GetStopInfo();
|
||||
if (!stop_info_sp ||
|
||||
stop_info_sp->GetStopReason() != eStopReasonBreakpoint) {
|
||||
new_args.Clear();
|
||||
return llvm::createStringError(
|
||||
"current thread is not stopped at a breakpoint");
|
||||
}
|
||||
|
||||
uint32_t data_count = stop_info_sp->GetStopReasonDataCount();
|
||||
for (uint32_t j = 0; j < data_count; j += 2) {
|
||||
std::string location_id = LocationIDForStop(stop_info_sp, j);
|
||||
new_args.AppendArgument(location_id);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!allow_locations && current_arg.contains('.')) {
|
||||
new_args.Clear();
|
||||
return llvm::createStringError(
|
||||
|
||||
@@ -2073,7 +2073,7 @@ protected:
|
||||
BreakpointIDList valid_bp_ids;
|
||||
|
||||
CommandObjectMultiwordBreakpoint::VerifyBreakpointOrLocationIDs(
|
||||
command, target, result, &valid_bp_ids,
|
||||
command, m_exe_ctx, result, &valid_bp_ids,
|
||||
BreakpointName::Permissions::PermissionKinds::disablePerm);
|
||||
|
||||
if (result.Succeeded()) {
|
||||
@@ -2153,7 +2153,7 @@ protected:
|
||||
// Particular breakpoint selected; enable that breakpoint.
|
||||
BreakpointIDList valid_bp_ids;
|
||||
CommandObjectMultiwordBreakpoint::VerifyBreakpointOrLocationIDs(
|
||||
command, target, result, &valid_bp_ids,
|
||||
command, m_exe_ctx, result, &valid_bp_ids,
|
||||
BreakpointName::Permissions::PermissionKinds::disablePerm);
|
||||
|
||||
if (result.Succeeded()) {
|
||||
@@ -2261,7 +2261,7 @@ protected:
|
||||
BreakpointIDList valid_bp_ids;
|
||||
|
||||
CommandObjectMultiwordBreakpoint::VerifyBreakpointOrLocationIDs(
|
||||
command, target, result, &valid_bp_ids,
|
||||
command, m_exe_ctx, result, &valid_bp_ids,
|
||||
BreakpointName::Permissions::PermissionKinds::disablePerm);
|
||||
|
||||
if (result.Succeeded()) {
|
||||
@@ -2406,7 +2406,7 @@ protected:
|
||||
// Particular breakpoints selected; show info about that breakpoint.
|
||||
BreakpointIDList valid_bp_ids;
|
||||
CommandObjectMultiwordBreakpoint::VerifyBreakpointOrLocationIDs(
|
||||
command, target, result, &valid_bp_ids,
|
||||
command, m_exe_ctx, result, &valid_bp_ids,
|
||||
BreakpointName::Permissions::PermissionKinds::listPerm);
|
||||
|
||||
if (result.Succeeded()) {
|
||||
@@ -2685,7 +2685,7 @@ protected:
|
||||
|
||||
if (!command.empty()) {
|
||||
CommandObjectMultiwordBreakpoint::VerifyBreakpointOrLocationIDs(
|
||||
command, target, result, &excluded_bp_ids,
|
||||
command, m_exe_ctx, result, &excluded_bp_ids,
|
||||
BreakpointName::Permissions::PermissionKinds::deletePerm);
|
||||
if (!result.Succeeded())
|
||||
return;
|
||||
@@ -2704,7 +2704,7 @@ protected:
|
||||
}
|
||||
} else {
|
||||
CommandObjectMultiwordBreakpoint::VerifyBreakpointOrLocationIDs(
|
||||
command, target, result, &valid_bp_ids,
|
||||
command, m_exe_ctx, result, &valid_bp_ids,
|
||||
BreakpointName::Permissions::PermissionKinds::deletePerm);
|
||||
if (!result.Succeeded())
|
||||
return;
|
||||
@@ -3015,7 +3015,7 @@ protected:
|
||||
// Particular breakpoint selected; disable that breakpoint.
|
||||
BreakpointIDList valid_bp_ids;
|
||||
CommandObjectMultiwordBreakpoint::VerifyBreakpointIDs(
|
||||
command, target, result, &valid_bp_ids,
|
||||
command, m_exe_ctx, result, &valid_bp_ids,
|
||||
BreakpointName::Permissions::PermissionKinds::listPerm);
|
||||
|
||||
if (result.Succeeded()) {
|
||||
@@ -3089,7 +3089,7 @@ protected:
|
||||
// Particular breakpoint selected; disable that breakpoint.
|
||||
BreakpointIDList valid_bp_ids;
|
||||
CommandObjectMultiwordBreakpoint::VerifyBreakpointIDs(
|
||||
command, target, result, &valid_bp_ids,
|
||||
command, m_exe_ctx, result, &valid_bp_ids,
|
||||
BreakpointName::Permissions::PermissionKinds::deletePerm);
|
||||
|
||||
if (result.Succeeded()) {
|
||||
@@ -3562,7 +3562,7 @@ protected:
|
||||
BreakpointIDList valid_bp_ids;
|
||||
if (!command.empty()) {
|
||||
CommandObjectMultiwordBreakpoint::VerifyBreakpointIDs(
|
||||
command, target, result, &valid_bp_ids,
|
||||
command, m_exe_ctx, result, &valid_bp_ids,
|
||||
BreakpointName::Permissions::PermissionKinds::listPerm);
|
||||
|
||||
if (!result.Succeeded()) {
|
||||
@@ -3648,7 +3648,7 @@ CommandObjectMultiwordBreakpoint::CommandObjectMultiwordBreakpoint(
|
||||
CommandObjectMultiwordBreakpoint::~CommandObjectMultiwordBreakpoint() = default;
|
||||
|
||||
void CommandObjectMultiwordBreakpoint::VerifyIDs(
|
||||
Args &args, Target &target, bool allow_locations,
|
||||
Args &args, ExecutionContext &exe_ctx, bool allow_locations,
|
||||
CommandReturnObject &result, BreakpointIDList *valid_ids,
|
||||
BreakpointName::Permissions ::PermissionKinds purpose) {
|
||||
// args can be strings representing 1). integers (for breakpoint ids)
|
||||
@@ -3662,6 +3662,7 @@ void CommandObjectMultiwordBreakpoint::VerifyIDs(
|
||||
// If args is empty, we will use the last created breakpoint (if there is
|
||||
// one.)
|
||||
|
||||
Target &target = exe_ctx.GetTargetRef();
|
||||
Args temp_args;
|
||||
|
||||
if (args.empty()) {
|
||||
@@ -3683,7 +3684,7 @@ void CommandObjectMultiwordBreakpoint::VerifyIDs(
|
||||
// into TEMP_ARGS.
|
||||
|
||||
if (llvm::Error err = BreakpointIDList::FindAndReplaceIDRanges(
|
||||
args, &target, allow_locations, purpose, temp_args)) {
|
||||
args, exe_ctx, allow_locations, purpose, temp_args)) {
|
||||
result.SetError(std::move(err));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -23,22 +23,22 @@ public:
|
||||
~CommandObjectMultiwordBreakpoint() override;
|
||||
|
||||
static void VerifyBreakpointOrLocationIDs(
|
||||
Args &args, Target &target, CommandReturnObject &result,
|
||||
Args &args, ExecutionContext &exe_ctx, CommandReturnObject &result,
|
||||
BreakpointIDList *valid_ids,
|
||||
BreakpointName::Permissions ::PermissionKinds purpose) {
|
||||
VerifyIDs(args, target, true, result, valid_ids, purpose);
|
||||
VerifyIDs(args, exe_ctx, true, result, valid_ids, purpose);
|
||||
}
|
||||
|
||||
static void
|
||||
VerifyBreakpointIDs(Args &args, Target &target, CommandReturnObject &result,
|
||||
BreakpointIDList *valid_ids,
|
||||
VerifyBreakpointIDs(Args &args, ExecutionContext &exe_ctx,
|
||||
CommandReturnObject &result, BreakpointIDList *valid_ids,
|
||||
BreakpointName::Permissions::PermissionKinds purpose) {
|
||||
VerifyIDs(args, target, false, result, valid_ids, purpose);
|
||||
VerifyIDs(args, exe_ctx, false, result, valid_ids, purpose);
|
||||
}
|
||||
|
||||
private:
|
||||
static void VerifyIDs(Args &args, Target &target, bool allow_locations,
|
||||
CommandReturnObject &result,
|
||||
static void VerifyIDs(Args &args, ExecutionContext &exe_ctx,
|
||||
bool allow_locations, CommandReturnObject &result,
|
||||
BreakpointIDList *valid_ids,
|
||||
BreakpointName::Permissions::PermissionKinds purpose);
|
||||
};
|
||||
|
||||
@@ -344,7 +344,7 @@ protected:
|
||||
|
||||
BreakpointIDList valid_bp_ids;
|
||||
CommandObjectMultiwordBreakpoint::VerifyBreakpointOrLocationIDs(
|
||||
command, target, result, &valid_bp_ids,
|
||||
command, m_exe_ctx, result, &valid_bp_ids,
|
||||
BreakpointName::Permissions::PermissionKinds::listPerm);
|
||||
|
||||
m_bp_options_vec.clear();
|
||||
@@ -500,7 +500,7 @@ protected:
|
||||
|
||||
BreakpointIDList valid_bp_ids;
|
||||
CommandObjectMultiwordBreakpoint::VerifyBreakpointOrLocationIDs(
|
||||
command, target, result, &valid_bp_ids,
|
||||
command, m_exe_ctx, result, &valid_bp_ids,
|
||||
BreakpointName::Permissions::PermissionKinds::listPerm);
|
||||
|
||||
if (result.Succeeded()) {
|
||||
@@ -567,7 +567,7 @@ protected:
|
||||
|
||||
BreakpointIDList valid_bp_ids;
|
||||
CommandObjectMultiwordBreakpoint::VerifyBreakpointOrLocationIDs(
|
||||
command, target, result, &valid_bp_ids,
|
||||
command, m_exe_ctx, result, &valid_bp_ids,
|
||||
BreakpointName::Permissions::PermissionKinds::listPerm);
|
||||
|
||||
if (result.Succeeded()) {
|
||||
|
||||
@@ -533,7 +533,7 @@ protected:
|
||||
// default breakpoint.
|
||||
if (m_options.m_run_to_bkpt_args.GetArgumentCount() > 0)
|
||||
CommandObjectMultiwordBreakpoint::VerifyBreakpointOrLocationIDs(
|
||||
m_options.m_run_to_bkpt_args, target, result, &run_to_bkpt_ids,
|
||||
m_options.m_run_to_bkpt_args, m_exe_ctx, result, &run_to_bkpt_ids,
|
||||
BreakpointName::Permissions::disablePerm);
|
||||
if (!result.Succeeded()) {
|
||||
return;
|
||||
|
||||
@@ -49,7 +49,10 @@ llvm::StringRef BreakpointIDHelpTextCallback() {
|
||||
"major "
|
||||
"number, or the major number followed by a dot and the location "
|
||||
"number (e.g. "
|
||||
"3 or 3.2 could both be valid breakpoint IDs.)";
|
||||
"3 or 3.2 could both be valid breakpoint IDs.)\n"
|
||||
"\n"
|
||||
"You can use . to refer to the breakpoint location(s) at which the "
|
||||
"current thread is stopped.";
|
||||
}
|
||||
|
||||
llvm::StringRef BreakpointIDRangeHelpTextCallback() {
|
||||
|
||||
3
lldb/test/API/commands/breakpoint/location-dot/Makefile
Normal file
3
lldb/test/API/commands/breakpoint/location-dot/Makefile
Normal file
@@ -0,0 +1,3 @@
|
||||
C_SOURCES := main.c
|
||||
|
||||
include Makefile.rules
|
||||
@@ -0,0 +1,51 @@
|
||||
import lldb
|
||||
from lldbsuite.test.lldbtest import TestBase
|
||||
from lldbsuite.test import lldbutil
|
||||
|
||||
|
||||
class TestCase(TestBase):
|
||||
def test_disable_enable(self):
|
||||
self.build()
|
||||
_, _, _, bp = lldbutil.run_to_source_breakpoint(
|
||||
self, "break here", lldb.SBFileSpec("main.c")
|
||||
)
|
||||
|
||||
self.assertTrue(bp.FindLocationByID(1).IsEnabled())
|
||||
self.expect("breakpoint disable .", startstr="1 breakpoints disabled.")
|
||||
self.assertFalse(bp.FindLocationByID(1).IsEnabled())
|
||||
self.expect("breakpoint enable .", startstr="1 breakpoints enabled.")
|
||||
self.assertTrue(bp.FindLocationByID(1).IsEnabled())
|
||||
|
||||
def test_delete(self):
|
||||
self.build()
|
||||
_, _, _, bp = lldbutil.run_to_source_breakpoint(
|
||||
self, "break here", lldb.SBFileSpec("main.c")
|
||||
)
|
||||
|
||||
self.expect(
|
||||
"breakpoint delete .",
|
||||
startstr="0 breakpoints deleted; 1 breakpoint locations disabled",
|
||||
)
|
||||
self.assertFalse(bp.FindLocationByID(1).IsEnabled())
|
||||
|
||||
def test_error_not_breakpoint_stop(self):
|
||||
self.build()
|
||||
_, _, thread, bp = lldbutil.run_to_source_breakpoint(
|
||||
self, "break here", lldb.SBFileSpec("main.c")
|
||||
)
|
||||
|
||||
self.assertTrue(bp.FindLocationByID(1).IsEnabled())
|
||||
thread.StepOver()
|
||||
self.assertNotEqual(thread.stop_reason, lldb.eStopReasonBreakpoint)
|
||||
self.expect(
|
||||
"breakpoint disable .",
|
||||
error=True,
|
||||
startstr="error: current thread is not stopped at a breakpoint",
|
||||
)
|
||||
self.assertTrue(bp.FindLocationByID(1).IsEnabled())
|
||||
|
||||
def test_error_no_process(self):
|
||||
self.build()
|
||||
target = self.createTestTarget()
|
||||
target.BreakpointCreateByLocation("main.c", 2)
|
||||
self.expect("breakpoint disable .", error=True, substrs=["no current thread"])
|
||||
5
lldb/test/API/commands/breakpoint/location-dot/main.c
Normal file
5
lldb/test/API/commands/breakpoint/location-dot/main.c
Normal file
@@ -0,0 +1,5 @@
|
||||
int main() {
|
||||
int x = 1; // break here
|
||||
int y = 2;
|
||||
return x + y;
|
||||
}
|
||||
@@ -259,6 +259,9 @@ Changes to LLDB
|
||||
code signed dSYM bundles are now loaded automatically, while untrusted bundles continue to produce a warning.
|
||||
* Pressing enter after `frame variable` repeats the command with an incremented `--depth` option, allowing quick
|
||||
expansion of nested data.
|
||||
* Breakpoint commands now accept `.` to refer to the location(s) at which the current thread is stopped. For
|
||||
example, `breakpoint disable .` disables the just-hit breakpoint location. Another usage is to automate a
|
||||
command to run at the current location: `breakpoint command add -o 'p my_var' .`.
|
||||
|
||||
### Deprecated APIs
|
||||
|
||||
|
||||
Reference in New Issue
Block a user