Skip to content

Commit 749af7f

Browse files
authored
[lldb] Define breakpoint location "." to mean the location(s) at which the current thread is stopped (llvm#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
1 parent d394d15 commit 749af7f

11 files changed

Lines changed: 132 additions & 28 deletions

File tree

lldb/include/lldb/Breakpoint/BreakpointIDList.h

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,9 @@ class BreakpointIDList {
5050
static std::pair<llvm::StringRef, llvm::StringRef>
5151
SplitIDRangeExpression(llvm::StringRef in_string);
5252

53-
static llvm::Error
54-
FindAndReplaceIDRanges(Args &old_args, Target *target, bool allow_locations,
55-
BreakpointName::Permissions ::PermissionKinds purpose,
56-
Args &new_args);
53+
static llvm::Error FindAndReplaceIDRanges(
54+
Args &old_args, ExecutionContext &exe_ctx, bool allow_locations,
55+
BreakpointName::Permissions ::PermissionKinds purpose, Args &new_args);
5756

5857
private:
5958
BreakpointIDArray m_breakpoint_ids;

lldb/source/Breakpoint/BreakpointIDList.cpp

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,13 @@
1111

1212
#include "lldb/Breakpoint/Breakpoint.h"
1313
#include "lldb/Breakpoint/BreakpointLocation.h"
14+
#include "lldb/Target/ExecutionContext.h"
15+
#include "lldb/Target/StopInfo.h"
1416
#include "lldb/Target/Target.h"
17+
#include "lldb/Target/Thread.h"
1518
#include "lldb/Utility/Args.h"
1619
#include "lldb/Utility/StreamString.h"
20+
#include "lldb/lldb-forward.h"
1721

1822
#include "llvm/ADT/STLExtras.h"
1923
#include "llvm/ADT/StringRef.h"
@@ -55,6 +59,16 @@ bool BreakpointIDList::Contains(BreakpointID bp_id) const {
5559
return llvm::is_contained(m_breakpoint_ids, bp_id);
5660
}
5761

62+
static std::string LocationIDForStop(StopInfoSP stop_info_sp, uint32_t idx) {
63+
assert(stop_info_sp->GetStopReason() == lldb::eStopReasonBreakpoint &&
64+
"expected breakpoint stop");
65+
break_id_t bp_id = stop_info_sp->GetStopReasonDataAtIndex(idx);
66+
break_id_t loc_id = stop_info_sp->GetStopReasonDataAtIndex(idx + 1);
67+
StreamString stream;
68+
BreakpointID::GetCanonicalReference(&stream, bp_id, loc_id);
69+
return stream.GetString().str();
70+
}
71+
5872
// This function takes OLD_ARGS, which is usually the result of breaking the
5973
// command string arguments into
6074
// an array of space-separated strings, and searches through the arguments for
@@ -69,8 +83,9 @@ bool BreakpointIDList::Contains(BreakpointID bp_id) const {
6983
// by the members of the range.
7084

7185
llvm::Error BreakpointIDList::FindAndReplaceIDRanges(
72-
Args &old_args, Target *target, bool allow_locations,
86+
Args &old_args, ExecutionContext &exe_ctx, bool allow_locations,
7387
BreakpointName::Permissions ::PermissionKinds purpose, Args &new_args) {
88+
Target *target = exe_ctx.GetTargetPtr();
7489
llvm::StringRef range_from;
7590
llvm::StringRef range_to;
7691
llvm::StringRef current_arg;
@@ -80,6 +95,30 @@ llvm::Error BreakpointIDList::FindAndReplaceIDRanges(
8095
bool is_range = false;
8196

8297
current_arg = old_args[i].ref();
98+
99+
if (allow_locations && current_arg == ".") {
100+
Thread *thread = exe_ctx.GetThreadPtr();
101+
if (!thread) {
102+
new_args.Clear();
103+
return llvm::createStringError("no current thread");
104+
}
105+
StopInfoSP stop_info_sp = thread->GetStopInfo();
106+
if (!stop_info_sp ||
107+
stop_info_sp->GetStopReason() != eStopReasonBreakpoint) {
108+
new_args.Clear();
109+
return llvm::createStringError(
110+
"current thread is not stopped at a breakpoint");
111+
}
112+
113+
uint32_t data_count = stop_info_sp->GetStopReasonDataCount();
114+
for (uint32_t j = 0; j < data_count; j += 2) {
115+
std::string location_id = LocationIDForStop(stop_info_sp, j);
116+
new_args.AppendArgument(location_id);
117+
}
118+
119+
continue;
120+
}
121+
83122
if (!allow_locations && current_arg.contains('.')) {
84123
new_args.Clear();
85124
return llvm::createStringError(

lldb/source/Commands/CommandObjectBreakpoint.cpp

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2073,7 +2073,7 @@ class CommandObjectBreakpointModify : public CommandObjectParsed {
20732073
BreakpointIDList valid_bp_ids;
20742074

20752075
CommandObjectMultiwordBreakpoint::VerifyBreakpointOrLocationIDs(
2076-
command, target, result, &valid_bp_ids,
2076+
command, m_exe_ctx, result, &valid_bp_ids,
20772077
BreakpointName::Permissions::PermissionKinds::disablePerm);
20782078

20792079
if (result.Succeeded()) {
@@ -2153,7 +2153,7 @@ class CommandObjectBreakpointEnable : public CommandObjectParsed {
21532153
// Particular breakpoint selected; enable that breakpoint.
21542154
BreakpointIDList valid_bp_ids;
21552155
CommandObjectMultiwordBreakpoint::VerifyBreakpointOrLocationIDs(
2156-
command, target, result, &valid_bp_ids,
2156+
command, m_exe_ctx, result, &valid_bp_ids,
21572157
BreakpointName::Permissions::PermissionKinds::disablePerm);
21582158

21592159
if (result.Succeeded()) {
@@ -2261,7 +2261,7 @@ the second re-enables the first location.");
22612261
BreakpointIDList valid_bp_ids;
22622262

22632263
CommandObjectMultiwordBreakpoint::VerifyBreakpointOrLocationIDs(
2264-
command, target, result, &valid_bp_ids,
2264+
command, m_exe_ctx, result, &valid_bp_ids,
22652265
BreakpointName::Permissions::PermissionKinds::disablePerm);
22662266

22672267
if (result.Succeeded()) {
@@ -2406,7 +2406,7 @@ class CommandObjectBreakpointList : public CommandObjectParsed {
24062406
// Particular breakpoints selected; show info about that breakpoint.
24072407
BreakpointIDList valid_bp_ids;
24082408
CommandObjectMultiwordBreakpoint::VerifyBreakpointOrLocationIDs(
2409-
command, target, result, &valid_bp_ids,
2409+
command, m_exe_ctx, result, &valid_bp_ids,
24102410
BreakpointName::Permissions::PermissionKinds::listPerm);
24112411

24122412
if (result.Succeeded()) {
@@ -2685,7 +2685,7 @@ class CommandObjectBreakpointDelete : public CommandObjectParsed {
26852685

26862686
if (!command.empty()) {
26872687
CommandObjectMultiwordBreakpoint::VerifyBreakpointOrLocationIDs(
2688-
command, target, result, &excluded_bp_ids,
2688+
command, m_exe_ctx, result, &excluded_bp_ids,
26892689
BreakpointName::Permissions::PermissionKinds::deletePerm);
26902690
if (!result.Succeeded())
26912691
return;
@@ -2704,7 +2704,7 @@ class CommandObjectBreakpointDelete : public CommandObjectParsed {
27042704
}
27052705
} else {
27062706
CommandObjectMultiwordBreakpoint::VerifyBreakpointOrLocationIDs(
2707-
command, target, result, &valid_bp_ids,
2707+
command, m_exe_ctx, result, &valid_bp_ids,
27082708
BreakpointName::Permissions::PermissionKinds::deletePerm);
27092709
if (!result.Succeeded())
27102710
return;
@@ -3015,7 +3015,7 @@ class CommandObjectBreakpointNameAdd : public CommandObjectParsed {
30153015
// Particular breakpoint selected; disable that breakpoint.
30163016
BreakpointIDList valid_bp_ids;
30173017
CommandObjectMultiwordBreakpoint::VerifyBreakpointIDs(
3018-
command, target, result, &valid_bp_ids,
3018+
command, m_exe_ctx, result, &valid_bp_ids,
30193019
BreakpointName::Permissions::PermissionKinds::listPerm);
30203020

30213021
if (result.Succeeded()) {
@@ -3089,7 +3089,7 @@ class CommandObjectBreakpointNameDelete : public CommandObjectParsed {
30893089
// Particular breakpoint selected; disable that breakpoint.
30903090
BreakpointIDList valid_bp_ids;
30913091
CommandObjectMultiwordBreakpoint::VerifyBreakpointIDs(
3092-
command, target, result, &valid_bp_ids,
3092+
command, m_exe_ctx, result, &valid_bp_ids,
30933093
BreakpointName::Permissions::PermissionKinds::deletePerm);
30943094

30953095
if (result.Succeeded()) {
@@ -3562,7 +3562,7 @@ class CommandObjectBreakpointWrite : public CommandObjectParsed {
35623562
BreakpointIDList valid_bp_ids;
35633563
if (!command.empty()) {
35643564
CommandObjectMultiwordBreakpoint::VerifyBreakpointIDs(
3565-
command, target, result, &valid_bp_ids,
3565+
command, m_exe_ctx, result, &valid_bp_ids,
35663566
BreakpointName::Permissions::PermissionKinds::listPerm);
35673567

35683568
if (!result.Succeeded()) {
@@ -3648,7 +3648,7 @@ CommandObjectMultiwordBreakpoint::CommandObjectMultiwordBreakpoint(
36483648
CommandObjectMultiwordBreakpoint::~CommandObjectMultiwordBreakpoint() = default;
36493649

36503650
void CommandObjectMultiwordBreakpoint::VerifyIDs(
3651-
Args &args, Target &target, bool allow_locations,
3651+
Args &args, ExecutionContext &exe_ctx, bool allow_locations,
36523652
CommandReturnObject &result, BreakpointIDList *valid_ids,
36533653
BreakpointName::Permissions ::PermissionKinds purpose) {
36543654
// args can be strings representing 1). integers (for breakpoint ids)
@@ -3662,6 +3662,7 @@ void CommandObjectMultiwordBreakpoint::VerifyIDs(
36623662
// If args is empty, we will use the last created breakpoint (if there is
36633663
// one.)
36643664

3665+
Target &target = exe_ctx.GetTargetRef();
36653666
Args temp_args;
36663667

36673668
if (args.empty()) {
@@ -3683,7 +3684,7 @@ void CommandObjectMultiwordBreakpoint::VerifyIDs(
36833684
// into TEMP_ARGS.
36843685

36853686
if (llvm::Error err = BreakpointIDList::FindAndReplaceIDRanges(
3686-
args, &target, allow_locations, purpose, temp_args)) {
3687+
args, exe_ctx, allow_locations, purpose, temp_args)) {
36873688
result.SetError(std::move(err));
36883689
return;
36893690
}

lldb/source/Commands/CommandObjectBreakpoint.h

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,22 @@ class CommandObjectMultiwordBreakpoint : public CommandObjectMultiword {
2323
~CommandObjectMultiwordBreakpoint() override;
2424

2525
static void VerifyBreakpointOrLocationIDs(
26-
Args &args, Target &target, CommandReturnObject &result,
26+
Args &args, ExecutionContext &exe_ctx, CommandReturnObject &result,
2727
BreakpointIDList *valid_ids,
2828
BreakpointName::Permissions ::PermissionKinds purpose) {
29-
VerifyIDs(args, target, true, result, valid_ids, purpose);
29+
VerifyIDs(args, exe_ctx, true, result, valid_ids, purpose);
3030
}
3131

3232
static void
33-
VerifyBreakpointIDs(Args &args, Target &target, CommandReturnObject &result,
34-
BreakpointIDList *valid_ids,
33+
VerifyBreakpointIDs(Args &args, ExecutionContext &exe_ctx,
34+
CommandReturnObject &result, BreakpointIDList *valid_ids,
3535
BreakpointName::Permissions::PermissionKinds purpose) {
36-
VerifyIDs(args, target, false, result, valid_ids, purpose);
36+
VerifyIDs(args, exe_ctx, false, result, valid_ids, purpose);
3737
}
3838

3939
private:
40-
static void VerifyIDs(Args &args, Target &target, bool allow_locations,
41-
CommandReturnObject &result,
40+
static void VerifyIDs(Args &args, ExecutionContext &exe_ctx,
41+
bool allow_locations, CommandReturnObject &result,
4242
BreakpointIDList *valid_ids,
4343
BreakpointName::Permissions::PermissionKinds purpose);
4444
};

lldb/source/Commands/CommandObjectBreakpointCommand.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ are no syntax errors may indicate that a function was declared but never called.
344344

345345
BreakpointIDList valid_bp_ids;
346346
CommandObjectMultiwordBreakpoint::VerifyBreakpointOrLocationIDs(
347-
command, target, result, &valid_bp_ids,
347+
command, m_exe_ctx, result, &valid_bp_ids,
348348
BreakpointName::Permissions::PermissionKinds::listPerm);
349349

350350
m_bp_options_vec.clear();
@@ -500,7 +500,7 @@ class CommandObjectBreakpointCommandDelete : public CommandObjectParsed {
500500

501501
BreakpointIDList valid_bp_ids;
502502
CommandObjectMultiwordBreakpoint::VerifyBreakpointOrLocationIDs(
503-
command, target, result, &valid_bp_ids,
503+
command, m_exe_ctx, result, &valid_bp_ids,
504504
BreakpointName::Permissions::PermissionKinds::listPerm);
505505

506506
if (result.Succeeded()) {
@@ -567,7 +567,7 @@ class CommandObjectBreakpointCommandList : public CommandObjectParsed {
567567

568568
BreakpointIDList valid_bp_ids;
569569
CommandObjectMultiwordBreakpoint::VerifyBreakpointOrLocationIDs(
570-
command, target, result, &valid_bp_ids,
570+
command, m_exe_ctx, result, &valid_bp_ids,
571571
BreakpointName::Permissions::PermissionKinds::listPerm);
572572

573573
if (result.Succeeded()) {

lldb/source/Commands/CommandObjectProcess.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -533,7 +533,7 @@ class CommandObjectProcessContinue : public CommandObjectParsed {
533533
// default breakpoint.
534534
if (m_options.m_run_to_bkpt_args.GetArgumentCount() > 0)
535535
CommandObjectMultiwordBreakpoint::VerifyBreakpointOrLocationIDs(
536-
m_options.m_run_to_bkpt_args, target, result, &run_to_bkpt_ids,
536+
m_options.m_run_to_bkpt_args, m_exe_ctx, result, &run_to_bkpt_ids,
537537
BreakpointName::Permissions::disablePerm);
538538
if (!result.Succeeded()) {
539539
return;

lldb/source/Commands/CommandOptionArgumentTable.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,10 @@ llvm::StringRef BreakpointIDHelpTextCallback() {
4949
"major "
5050
"number, or the major number followed by a dot and the location "
5151
"number (e.g. "
52-
"3 or 3.2 could both be valid breakpoint IDs.)";
52+
"3 or 3.2 could both be valid breakpoint IDs.)\n"
53+
"\n"
54+
"You can use . to refer to the breakpoint location(s) at which the "
55+
"current thread is stopped.";
5356
}
5457

5558
llvm::StringRef BreakpointIDRangeHelpTextCallback() {
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
C_SOURCES := main.c
2+
3+
include Makefile.rules
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import lldb
2+
from lldbsuite.test.lldbtest import TestBase
3+
from lldbsuite.test import lldbutil
4+
5+
6+
class TestCase(TestBase):
7+
def test_disable_enable(self):
8+
self.build()
9+
_, _, _, bp = lldbutil.run_to_source_breakpoint(
10+
self, "break here", lldb.SBFileSpec("main.c")
11+
)
12+
13+
self.assertTrue(bp.FindLocationByID(1).IsEnabled())
14+
self.expect("breakpoint disable .", startstr="1 breakpoints disabled.")
15+
self.assertFalse(bp.FindLocationByID(1).IsEnabled())
16+
self.expect("breakpoint enable .", startstr="1 breakpoints enabled.")
17+
self.assertTrue(bp.FindLocationByID(1).IsEnabled())
18+
19+
def test_delete(self):
20+
self.build()
21+
_, _, _, bp = lldbutil.run_to_source_breakpoint(
22+
self, "break here", lldb.SBFileSpec("main.c")
23+
)
24+
25+
self.expect(
26+
"breakpoint delete .",
27+
startstr="0 breakpoints deleted; 1 breakpoint locations disabled",
28+
)
29+
self.assertFalse(bp.FindLocationByID(1).IsEnabled())
30+
31+
def test_error_not_breakpoint_stop(self):
32+
self.build()
33+
_, _, thread, bp = lldbutil.run_to_source_breakpoint(
34+
self, "break here", lldb.SBFileSpec("main.c")
35+
)
36+
37+
self.assertTrue(bp.FindLocationByID(1).IsEnabled())
38+
thread.StepOver()
39+
self.assertNotEqual(thread.stop_reason, lldb.eStopReasonBreakpoint)
40+
self.expect(
41+
"breakpoint disable .",
42+
error=True,
43+
startstr="error: current thread is not stopped at a breakpoint",
44+
)
45+
self.assertTrue(bp.FindLocationByID(1).IsEnabled())
46+
47+
def test_error_no_process(self):
48+
self.build()
49+
target = self.createTestTarget()
50+
target.BreakpointCreateByLocation("main.c", 2)
51+
self.expect("breakpoint disable .", error=True, substrs=["no current thread"])
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
int main() {
2+
int x = 1; // break here
3+
int y = 2;
4+
return x + y;
5+
}

0 commit comments

Comments
 (0)