Skip to content

Commit 378f248

Browse files
authored
[lldb] Add SB API to make a breakpoint a hardware breakpoint (#146602)
This adds SBBreakpoint::SetIsHardware, allowing clients to mark an existing breakpoint as a hardware breakpoint purely through the API. This is safe to do after creation, as the hardware/software distinction doesn't affect how breakpoint locations are selected. In some cases (e.g. when writing a trap instruction would alter program behavior), it's important to use hardware breakpoints. Ideally, we’d extend the various `Create` methods to support this, but given their number, this patch limits the scope to the post-creation API. As a workaround, users can also rely on target.require-hardware-breakpoint or use the `breakpoint set` command. rdar://153528045
1 parent 1e76f01 commit 378f248

File tree

9 files changed

+164
-32
lines changed

9 files changed

+164
-32
lines changed

lldb/include/lldb/API/SBBreakpoint.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,11 @@ class LLDB_API SBBreakpoint {
148148

149149
bool IsHardware() const;
150150

151+
/// Make this breakpoint a hardware breakpoint. This will replace all existing
152+
/// breakpoint locations with hardware breakpoints. Returns an error if this
153+
/// fails, e.g. when there aren't enough hardware resources available.
154+
lldb::SBError SetIsHardware(bool is_hardware);
155+
151156
// Can only be called from a ScriptedBreakpointResolver...
152157
SBError
153158
AddLocation(SBAddress &address);

lldb/include/lldb/Breakpoint/Breakpoint.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,8 @@ class Breakpoint : public std::enable_shared_from_this<Breakpoint>,
519519

520520
bool IsHardware() const { return m_hardware; }
521521

522+
llvm::Error SetIsHardware(bool is_hardware);
523+
522524
lldb::BreakpointResolverSP GetResolver() { return m_resolver_sp; }
523525

524526
lldb::SearchFilterSP GetSearchFilter() { return m_filter_sp; }

lldb/include/lldb/Breakpoint/BreakpointLocation.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ class BreakpointLocation
6969
// The next section deals with various breakpoint options.
7070

7171
/// If \a enabled is \b true, enable the breakpoint, if \b false disable it.
72-
void SetEnabled(bool enabled);
72+
bool SetEnabled(bool enabled);
7373

7474
/// Check the Enable/Disable state.
7575
///

lldb/source/API/SBBreakpoint.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,18 @@ bool SBBreakpoint::IsHardware() const {
781781
return false;
782782
}
783783

784+
lldb::SBError SBBreakpoint::SetIsHardware(bool is_hardware) {
785+
LLDB_INSTRUMENT_VA(this, is_hardware);
786+
787+
BreakpointSP bkpt_sp = GetSP();
788+
if (bkpt_sp) {
789+
std::lock_guard<std::recursive_mutex> guard(
790+
bkpt_sp->GetTarget().GetAPIMutex());
791+
return SBError(Status::FromError(bkpt_sp->SetIsHardware(is_hardware)));
792+
}
793+
return SBError();
794+
}
795+
784796
BreakpointSP SBBreakpoint::GetSP() const { return m_opaque_wp.lock(); }
785797

786798
// This is simple collection of breakpoint id's and their target.

lldb/source/Breakpoint/Breakpoint.cpp

Lines changed: 72 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ Breakpoint::Breakpoint(Target &new_target, const Breakpoint &source_bp)
6161
Breakpoint::~Breakpoint() = default;
6262

6363
BreakpointSP Breakpoint::CopyFromBreakpoint(TargetSP new_target,
64-
const Breakpoint& bp_to_copy_from) {
64+
const Breakpoint &bp_to_copy_from) {
6565
if (!new_target)
6666
return BreakpointSP();
6767

@@ -163,7 +163,7 @@ lldb::BreakpointSP Breakpoint::CreateFromStructuredData(
163163
std::make_shared<SearchFilterForUnconstrainedSearches>(target_sp);
164164
else {
165165
filter_sp = SearchFilter::CreateFromStructuredData(target_sp, *filter_dict,
166-
create_error);
166+
create_error);
167167
if (create_error.Fail()) {
168168
error = Status::FromErrorStringWithFormat(
169169
"Error creating breakpoint filter from data: %s.",
@@ -174,7 +174,7 @@ lldb::BreakpointSP Breakpoint::CreateFromStructuredData(
174174

175175
std::unique_ptr<BreakpointOptions> options_up;
176176
StructuredData::Dictionary *options_dict;
177-
Target& target = *target_sp;
177+
Target &target = *target_sp;
178178
success = breakpoint_dict->GetValueForKeyAsDictionary(
179179
BreakpointOptions::GetSerializationKey(), options_dict);
180180
if (success) {
@@ -192,8 +192,8 @@ lldb::BreakpointSP Breakpoint::CreateFromStructuredData(
192192
success = breakpoint_dict->GetValueForKeyAsBoolean(
193193
Breakpoint::GetKey(OptionNames::Hardware), hardware);
194194

195-
result_sp = target.CreateBreakpoint(filter_sp, resolver_sp, false,
196-
hardware, true);
195+
result_sp =
196+
target.CreateBreakpoint(filter_sp, resolver_sp, false, hardware, true);
197197

198198
if (result_sp && options_up) {
199199
result_sp->m_options = *options_up;
@@ -251,6 +251,45 @@ const lldb::TargetSP Breakpoint::GetTargetSP() {
251251

252252
bool Breakpoint::IsInternal() const { return LLDB_BREAK_ID_IS_INTERNAL(m_bid); }
253253

254+
llvm::Error Breakpoint::SetIsHardware(bool is_hardware) {
255+
if (is_hardware == m_hardware)
256+
return llvm::Error::success();
257+
258+
// Disable all non-hardware breakpoint locations.
259+
std::vector<BreakpointLocationSP> locations;
260+
for (BreakpointLocationSP location_sp : m_locations.BreakpointLocations()) {
261+
if (!location_sp || !location_sp->IsEnabled())
262+
continue;
263+
264+
lldb::BreakpointSiteSP breakpoint_site_sp =
265+
location_sp->GetBreakpointSite();
266+
if (!breakpoint_site_sp ||
267+
breakpoint_site_sp->GetType() == BreakpointSite::eHardware)
268+
continue;
269+
270+
locations.push_back(location_sp);
271+
location_sp->SetEnabled(false);
272+
}
273+
274+
// Toggle the hardware mode.
275+
m_hardware = is_hardware;
276+
277+
// Re-enable all breakpoint locations.
278+
size_t num_failures = 0;
279+
for (BreakpointLocationSP location_sp : locations) {
280+
if (!location_sp->SetEnabled(true))
281+
num_failures++;
282+
}
283+
284+
if (num_failures != 0)
285+
return llvm::createStringError(
286+
"%ull out of %ull breakpoint locations left disabled because they "
287+
"couldn't be converted to hardware",
288+
num_failures, locations.size());
289+
290+
return llvm::Error::success();
291+
}
292+
254293
BreakpointLocationSP Breakpoint::AddLocation(const Address &addr,
255294
bool *new_location) {
256295
return m_locations.AddLocation(addr, m_resolve_indirect_symbols,
@@ -952,8 +991,7 @@ void Breakpoint::GetResolverDescription(Stream *s) {
952991
m_resolver_sp->GetDescription(s);
953992
}
954993

955-
bool Breakpoint::GetMatchingFileLine(ConstString filename,
956-
uint32_t line_number,
994+
bool Breakpoint::GetMatchingFileLine(ConstString filename, uint32_t line_number,
957995
BreakpointLocationCollection &loc_coll) {
958996
// TODO: To be correct, this method needs to fill the breakpoint location
959997
// collection
@@ -1010,19 +1048,32 @@ void Breakpoint::SendBreakpointChangedEvent(
10101048

10111049
const char *Breakpoint::BreakpointEventTypeAsCString(BreakpointEventType type) {
10121050
switch (type) {
1013-
case eBreakpointEventTypeInvalidType: return "invalid";
1014-
case eBreakpointEventTypeAdded: return "breakpoint added";
1015-
case eBreakpointEventTypeRemoved: return "breakpoint removed";
1016-
case eBreakpointEventTypeLocationsAdded: return "locations added";
1017-
case eBreakpointEventTypeLocationsRemoved: return "locations removed";
1018-
case eBreakpointEventTypeLocationsResolved: return "locations resolved";
1019-
case eBreakpointEventTypeEnabled: return "breakpoint enabled";
1020-
case eBreakpointEventTypeDisabled: return "breakpoint disabled";
1021-
case eBreakpointEventTypeCommandChanged: return "command changed";
1022-
case eBreakpointEventTypeConditionChanged: return "condition changed";
1023-
case eBreakpointEventTypeIgnoreChanged: return "ignore count changed";
1024-
case eBreakpointEventTypeThreadChanged: return "thread changed";
1025-
case eBreakpointEventTypeAutoContinueChanged: return "autocontinue changed";
1051+
case eBreakpointEventTypeInvalidType:
1052+
return "invalid";
1053+
case eBreakpointEventTypeAdded:
1054+
return "breakpoint added";
1055+
case eBreakpointEventTypeRemoved:
1056+
return "breakpoint removed";
1057+
case eBreakpointEventTypeLocationsAdded:
1058+
return "locations added";
1059+
case eBreakpointEventTypeLocationsRemoved:
1060+
return "locations removed";
1061+
case eBreakpointEventTypeLocationsResolved:
1062+
return "locations resolved";
1063+
case eBreakpointEventTypeEnabled:
1064+
return "breakpoint enabled";
1065+
case eBreakpointEventTypeDisabled:
1066+
return "breakpoint disabled";
1067+
case eBreakpointEventTypeCommandChanged:
1068+
return "command changed";
1069+
case eBreakpointEventTypeConditionChanged:
1070+
return "condition changed";
1071+
case eBreakpointEventTypeIgnoreChanged:
1072+
return "ignore count changed";
1073+
case eBreakpointEventTypeThreadChanged:
1074+
return "thread changed";
1075+
case eBreakpointEventTypeAutoContinueChanged:
1076+
return "autocontinue changed";
10261077
};
10271078
llvm_unreachable("Fully covered switch above!");
10281079
}
@@ -1060,7 +1111,7 @@ void Breakpoint::BreakpointEventData::Dump(Stream *s) const {
10601111
BreakpointEventType event_type = GetBreakpointEventType();
10611112
break_id_t bkpt_id = GetBreakpoint()->GetID();
10621113
s->Format("bkpt: {0} type: {1}", bkpt_id,
1063-
BreakpointEventTypeAsCString(event_type));
1114+
BreakpointEventTypeAsCString(event_type));
10641115
}
10651116

10661117
const Breakpoint::BreakpointEventData *

lldb/source/Breakpoint/BreakpointLocation.cpp

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,13 @@ bool BreakpointLocation::IsEnabled() const {
7272
return true;
7373
}
7474

75-
void BreakpointLocation::SetEnabled(bool enabled) {
75+
bool BreakpointLocation::SetEnabled(bool enabled) {
7676
GetLocationOptions().SetEnabled(enabled);
77-
if (enabled) {
78-
ResolveBreakpointSite();
79-
} else {
80-
ClearBreakpointSite();
81-
}
77+
const bool success =
78+
enabled ? ResolveBreakpointSite() : ClearBreakpointSite();
8279
SendBreakpointLocationChangedEvent(enabled ? eBreakpointEventTypeEnabled
8380
: eBreakpointEventTypeDisabled);
81+
return success;
8482
}
8583

8684
bool BreakpointLocation::IsAutoContinue() const {
@@ -436,10 +434,10 @@ bool BreakpointLocation::ResolveBreakpointSite() {
436434
process->CreateBreakpointSite(shared_from_this(), m_owner.IsHardware());
437435

438436
if (new_id == LLDB_INVALID_BREAK_ID) {
439-
Log *log = GetLog(LLDBLog::Breakpoints);
440-
if (log)
441-
log->Warning("Failed to add breakpoint site at 0x%" PRIx64,
442-
m_address.GetOpcodeLoadAddress(&m_owner.GetTarget()));
437+
LLDB_LOGF(GetLog(LLDBLog::Breakpoints),
438+
"Failed to add breakpoint site at 0x%" PRIx64 "(resolved=%s)",
439+
m_address.GetOpcodeLoadAddress(&m_owner.GetTarget()),
440+
IsResolved() ? "yes" : "no");
443441
}
444442

445443
return IsResolved();
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
C_SOURCES := main.c
2+
3+
ifeq ($(CC_TYPE), icc)
4+
CFLAGS_EXTRAS := -debug inline-debug-info
5+
endif
6+
7+
include Makefile.rules
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import lldb
2+
from lldbsuite.test.decorators import *
3+
from lldbsuite.test.lldbtest import *
4+
from lldbsuite.test import lldbutil
5+
6+
from functionalities.breakpoint.hardware_breakpoints.base import *
7+
8+
9+
class SimpleHWBreakpointTest(HardwareBreakpointTestBase):
10+
def does_not_support_hw_breakpoints(self):
11+
# FIXME: Use HardwareBreakpointTestBase.supports_hw_breakpoints
12+
if super().supports_hw_breakpoints() is None:
13+
return "Hardware breakpoints are unsupported"
14+
return None
15+
16+
@skipTestIfFn(does_not_support_hw_breakpoints)
17+
def test(self):
18+
"""Test SBBreakpoint::SetIsHardware"""
19+
self.build()
20+
21+
# Set a breakpoint on main.
22+
target, process, _, main_bp = lldbutil.run_to_source_breakpoint(
23+
self, "main", lldb.SBFileSpec("main.c")
24+
)
25+
26+
break_on_me_bp = target.BreakpointCreateByLocation("main.c", 1)
27+
28+
self.assertFalse(main_bp.IsHardware())
29+
self.assertFalse(break_on_me_bp.IsHardware())
30+
self.assertGreater(break_on_me_bp.GetNumResolvedLocations(), 0)
31+
32+
error = break_on_me_bp.SetIsHardware(True)
33+
34+
# Regardless of whether we succeeded in updating all the locations, the
35+
# breakpoint will be marked as a hardware breakpoint.
36+
self.assertTrue(break_on_me_bp.IsHardware())
37+
38+
if super().supports_hw_breakpoints():
39+
self.assertSuccess(error)
40+
41+
# Continue to our Hardware breakpoint and verify that's the reason
42+
# we're stopped.
43+
process.Continue()
44+
self.expect(
45+
"thread list",
46+
STOPPED_DUE_TO_BREAKPOINT,
47+
substrs=["stopped", "stop reason = breakpoint"],
48+
)
49+
else:
50+
self.assertFailure(error)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
int break_on_me() {
2+
int i = 10;
3+
i++;
4+
return i;
5+
}
6+
7+
int main() { return break_on_me(); }

0 commit comments

Comments
 (0)