Skip to content

Commit 6421bd9

Browse files
authored
[lldb-dap] Creating protocol types for setExceptionBreakpoints. (#144153)
This adds new types for setExceptionBreakpoints and adds support for `supportsExceptionFilterOptions`, which allows exception breakpoints to set a condition. While testing this, I noticed that obj-c exception catch breakpoints may not be working correctly in lldb-dap.
1 parent d882670 commit 6421bd9

21 files changed

+453
-259
lines changed

lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1050,8 +1050,12 @@ def request_setBreakpoints(self, source: Source, line_array, data=None):
10501050
self._update_verified_breakpoints(response["body"]["breakpoints"])
10511051
return response
10521052

1053-
def request_setExceptionBreakpoints(self, filters):
1053+
def request_setExceptionBreakpoints(
1054+
self, *, filters: list[str] = [], filter_options: list[dict] = []
1055+
):
10541056
args_dict = {"filters": filters}
1057+
if filter_options:
1058+
args_dict["filterOptions"] = filter_options
10551059
command_dict = {
10561060
"command": "setExceptionBreakpoints",
10571061
"type": "request",

lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_setExceptionBreakpoints.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
11
"""
2-
Test lldb-dap setBreakpoints request
2+
Test lldb-dap setExceptionBreakpoints request
33
"""
44

5-
6-
import dap_server
75
from lldbsuite.test.decorators import *
86
from lldbsuite.test.lldbtest import *
9-
from lldbsuite.test import lldbutil
107
import lldbdap_testcase
118

129

13-
@skip("Temporarily disable the breakpoint tests")
1410
class TestDAP_setExceptionBreakpoints(lldbdap_testcase.DAPTestCaseBase):
1511
@skipIfWindows
1612
def test_functionality(self):
@@ -33,8 +29,9 @@ def test_functionality(self):
3329
program = self.getBuildArtifact("a.out")
3430
self.build_and_launch(program)
3531

36-
filters = ["cpp_throw", "cpp_catch"]
37-
response = self.dap_server.request_setExceptionBreakpoints(filters)
32+
response = self.dap_server.request_setExceptionBreakpoints(
33+
filters=["cpp_throw", "cpp_catch"],
34+
)
3835
if response:
3936
self.assertTrue(response["success"])
4037

lldb/test/API/tools/lldb-dap/exception/objc/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
OBJC_SOURCES := main.m
22

3-
CFLAGS_EXTRAS := -w
3+
CFLAGS_EXTRAS := -w -fobjc-exceptions
44

55
USE_SYSTEM_STDLIB := 1
66

lldb/test/API/tools/lldb-dap/exception/objc/TestDAP_exception_objc.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
Test exception behavior in DAP with obj-c throw.
33
"""
44

5-
65
from lldbsuite.test.decorators import *
76
from lldbsuite.test.lldbtest import *
87
import lldbdap_testcase
@@ -25,3 +24,41 @@ def test_stopped_description(self):
2524
exception_details = exception_info["details"]
2625
self.assertRegex(exception_details["message"], "SomeReason")
2726
self.assertRegex(exception_details["stackTrace"], "main.m")
27+
28+
@skipUnlessDarwin
29+
def test_break_on_throw_and_catch(self):
30+
"""
31+
Test that breakpoints on exceptions work as expected.
32+
"""
33+
program = self.getBuildArtifact("a.out")
34+
self.build_and_launch(program)
35+
36+
response = self.dap_server.request_setExceptionBreakpoints(
37+
filter_options=[
38+
{
39+
"filterId": "objc_throw",
40+
"condition": '[[((NSException *)$arg1) name] isEqual:@"ThrownException"]',
41+
},
42+
]
43+
)
44+
if response:
45+
self.assertTrue(response["success"])
46+
47+
self.continue_to_exception_breakpoint("Objective-C Throw")
48+
49+
# FIXME: Catching objc exceptions do not appear to be working.
50+
# Xcode appears to set a breakpoint on '__cxa_begin_catch' for objc
51+
# catch, which is different than
52+
# SBTarget::BreakpointCreateForException(eLanguageObjectiveC, /*catch_bp=*/true, /*throw_bp=*/false);
53+
# self.continue_to_exception_breakpoint("Objective-C Catch")
54+
55+
self.do_continue()
56+
57+
self.assertTrue(self.verify_stop_exception_info("signal SIGABRT"))
58+
exception_info = self.get_exceptionInfo()
59+
self.assertEqual(exception_info["breakMode"], "always")
60+
self.assertEqual(exception_info["description"], "signal SIGABRT")
61+
self.assertEqual(exception_info["exceptionId"], "signal")
62+
exception_details = exception_info["details"]
63+
self.assertRegex(exception_details["message"], "SomeReason")
64+
self.assertRegex(exception_details["stackTrace"], "main.m")
Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
#import <Foundation/Foundation.h>
22

33
int main(int argc, char const *argv[]) {
4-
@throw [[NSException alloc] initWithName:@"ThrownException"
5-
reason:@"SomeReason"
6-
userInfo:nil];
4+
@try {
5+
NSException *e = [[NSException alloc] initWithName:@"ThrownException"
6+
reason:@"SomeReason"
7+
userInfo:nil];
8+
@throw e;
9+
} @catch (NSException *e) {
10+
NSLog(@"Caught %@", e);
11+
@throw; // let the process crash...
12+
}
713
return 0;
814
}

lldb/tools/lldb-dap/DAP.cpp

Lines changed: 73 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "DAP.h"
1010
#include "DAPLog.h"
1111
#include "EventHelper.h"
12+
#include "ExceptionBreakpoint.h"
1213
#include "Handler/RequestHandler.h"
1314
#include "Handler/ResponseHandler.h"
1415
#include "JSONUtils.h"
@@ -17,6 +18,7 @@
1718
#include "Protocol/ProtocolBase.h"
1819
#include "Protocol/ProtocolRequests.h"
1920
#include "Protocol/ProtocolTypes.h"
21+
#include "ProtocolUtils.h"
2022
#include "Transport.h"
2123
#include "lldb/API/SBBreakpoint.h"
2224
#include "lldb/API/SBCommandInterpreter.h"
@@ -129,104 +131,89 @@ DAP::DAP(Log *log, const ReplMode default_repl_mode,
129131
DAP::~DAP() = default;
130132

131133
void DAP::PopulateExceptionBreakpoints() {
132-
llvm::call_once(init_exception_breakpoints_flag, [this]() {
133-
exception_breakpoints = std::vector<ExceptionBreakpoint>{};
134-
135-
if (lldb::SBDebugger::SupportsLanguage(lldb::eLanguageTypeC_plus_plus)) {
136-
exception_breakpoints->emplace_back(*this, "cpp_catch", "C++ Catch",
137-
lldb::eLanguageTypeC_plus_plus);
138-
exception_breakpoints->emplace_back(*this, "cpp_throw", "C++ Throw",
139-
lldb::eLanguageTypeC_plus_plus);
140-
}
141-
if (lldb::SBDebugger::SupportsLanguage(lldb::eLanguageTypeObjC)) {
142-
exception_breakpoints->emplace_back(
143-
*this, "objc_catch", "Objective-C Catch", lldb::eLanguageTypeObjC);
144-
exception_breakpoints->emplace_back(
145-
*this, "objc_throw", "Objective-C Throw", lldb::eLanguageTypeObjC);
146-
}
147-
if (lldb::SBDebugger::SupportsLanguage(lldb::eLanguageTypeSwift)) {
148-
exception_breakpoints->emplace_back(*this, "swift_catch", "Swift Catch",
149-
lldb::eLanguageTypeSwift);
150-
exception_breakpoints->emplace_back(*this, "swift_throw", "Swift Throw",
151-
lldb::eLanguageTypeSwift);
134+
if (lldb::SBDebugger::SupportsLanguage(lldb::eLanguageTypeC_plus_plus)) {
135+
exception_breakpoints.emplace_back(*this, "cpp_catch", "C++ Catch",
136+
lldb::eLanguageTypeC_plus_plus,
137+
eExceptionKindCatch);
138+
exception_breakpoints.emplace_back(*this, "cpp_throw", "C++ Throw",
139+
lldb::eLanguageTypeC_plus_plus,
140+
eExceptionKindThrow);
141+
}
142+
143+
if (lldb::SBDebugger::SupportsLanguage(lldb::eLanguageTypeObjC)) {
144+
exception_breakpoints.emplace_back(*this, "objc_catch", "Objective-C Catch",
145+
lldb::eLanguageTypeObjC,
146+
eExceptionKindCatch);
147+
exception_breakpoints.emplace_back(*this, "objc_throw", "Objective-C Throw",
148+
lldb::eLanguageTypeObjC,
149+
eExceptionKindThrow);
150+
}
151+
152+
if (lldb::SBDebugger::SupportsLanguage(lldb::eLanguageTypeSwift)) {
153+
exception_breakpoints.emplace_back(*this, "swift_catch", "Swift Catch",
154+
lldb::eLanguageTypeSwift,
155+
eExceptionKindCatch);
156+
exception_breakpoints.emplace_back(*this, "swift_throw", "Swift Throw",
157+
lldb::eLanguageTypeSwift,
158+
eExceptionKindThrow);
159+
}
160+
161+
// Besides handling the hardcoded list of languages from above, we try to find
162+
// any other languages that support exception breakpoints using the SB API.
163+
for (int raw_lang = lldb::eLanguageTypeUnknown;
164+
raw_lang < lldb::eNumLanguageTypes; ++raw_lang) {
165+
lldb::LanguageType lang = static_cast<lldb::LanguageType>(raw_lang);
166+
167+
// We first discard any languages already handled above.
168+
if (lldb::SBLanguageRuntime::LanguageIsCFamily(lang) ||
169+
lang == lldb::eLanguageTypeSwift)
170+
continue;
171+
172+
if (!lldb::SBDebugger::SupportsLanguage(lang))
173+
continue;
174+
175+
const char *name = lldb::SBLanguageRuntime::GetNameForLanguageType(lang);
176+
if (!name)
177+
continue;
178+
std::string raw_lang_name = name;
179+
std::string capitalized_lang_name = capitalize(name);
180+
181+
if (lldb::SBLanguageRuntime::SupportsExceptionBreakpointsOnThrow(lang)) {
182+
const char *raw_throw_keyword =
183+
lldb::SBLanguageRuntime::GetThrowKeywordForLanguage(lang);
184+
std::string throw_keyword =
185+
raw_throw_keyword ? raw_throw_keyword : "throw";
186+
187+
exception_breakpoints.emplace_back(
188+
*this, raw_lang_name + "_" + throw_keyword,
189+
capitalized_lang_name + " " + capitalize(throw_keyword), lang,
190+
eExceptionKindThrow);
152191
}
153-
// Besides handling the hardcoded list of languages from above, we try to
154-
// find any other languages that support exception breakpoints using the
155-
// SB API.
156-
for (int raw_lang = lldb::eLanguageTypeUnknown;
157-
raw_lang < lldb::eNumLanguageTypes; ++raw_lang) {
158-
lldb::LanguageType lang = static_cast<lldb::LanguageType>(raw_lang);
159-
160-
// We first discard any languages already handled above.
161-
if (lldb::SBLanguageRuntime::LanguageIsCFamily(lang) ||
162-
lang == lldb::eLanguageTypeSwift)
163-
continue;
164-
165-
if (!lldb::SBDebugger::SupportsLanguage(lang))
166-
continue;
167-
168-
const char *name = lldb::SBLanguageRuntime::GetNameForLanguageType(lang);
169-
if (!name)
170-
continue;
171-
std::string raw_lang_name = name;
172-
std::string capitalized_lang_name = capitalize(name);
173-
174-
if (lldb::SBLanguageRuntime::SupportsExceptionBreakpointsOnThrow(lang)) {
175-
const char *raw_throw_keyword =
176-
lldb::SBLanguageRuntime::GetThrowKeywordForLanguage(lang);
177-
std::string throw_keyword =
178-
raw_throw_keyword ? raw_throw_keyword : "throw";
179-
180-
exception_breakpoints->emplace_back(
181-
*this, raw_lang_name + "_" + throw_keyword,
182-
capitalized_lang_name + " " + capitalize(throw_keyword), lang);
183-
}
184192

185-
if (lldb::SBLanguageRuntime::SupportsExceptionBreakpointsOnCatch(lang)) {
186-
const char *raw_catch_keyword =
187-
lldb::SBLanguageRuntime::GetCatchKeywordForLanguage(lang);
188-
std::string catch_keyword =
189-
raw_catch_keyword ? raw_catch_keyword : "catch";
193+
if (lldb::SBLanguageRuntime::SupportsExceptionBreakpointsOnCatch(lang)) {
194+
const char *raw_catch_keyword =
195+
lldb::SBLanguageRuntime::GetCatchKeywordForLanguage(lang);
196+
std::string catch_keyword =
197+
raw_catch_keyword ? raw_catch_keyword : "catch";
190198

191-
exception_breakpoints->emplace_back(
192-
*this, raw_lang_name + "_" + catch_keyword,
193-
capitalized_lang_name + " " + capitalize(catch_keyword), lang);
194-
}
199+
exception_breakpoints.emplace_back(
200+
*this, raw_lang_name + "_" + catch_keyword,
201+
capitalized_lang_name + " " + capitalize(catch_keyword), lang,
202+
eExceptionKindCatch);
195203
}
196-
assert(!exception_breakpoints->empty() && "should not be empty");
197-
});
204+
}
198205
}
199206

200207
ExceptionBreakpoint *DAP::GetExceptionBreakpoint(llvm::StringRef filter) {
201-
// PopulateExceptionBreakpoints() is called after g_dap.debugger is created
202-
// in a request-initialize.
203-
//
204-
// But this GetExceptionBreakpoint() method may be called before attaching, in
205-
// which case, we may not have populated the filter yet.
206-
//
207-
// We also cannot call PopulateExceptionBreakpoints() in DAP::DAP() because
208-
// we need SBDebugger::Initialize() to have been called before this.
209-
//
210-
// So just calling PopulateExceptionBreakoints(),which does lazy-populating
211-
// seems easiest. Two other options include:
212-
// + call g_dap.PopulateExceptionBreakpoints() in lldb-dap.cpp::main()
213-
// right after the call to SBDebugger::Initialize()
214-
// + Just call PopulateExceptionBreakpoints() to get a fresh list everytime
215-
// we query (a bit overkill since it's not likely to change?)
216-
PopulateExceptionBreakpoints();
217-
218-
for (auto &bp : *exception_breakpoints) {
208+
for (auto &bp : exception_breakpoints) {
219209
if (bp.GetFilter() == filter)
220210
return &bp;
221211
}
222212
return nullptr;
223213
}
224214

225215
ExceptionBreakpoint *DAP::GetExceptionBreakpoint(const lldb::break_id_t bp_id) {
226-
// See comment in the other GetExceptionBreakpoint().
227-
PopulateExceptionBreakpoints();
228-
229-
for (auto &bp : *exception_breakpoints) {
216+
for (auto &bp : exception_breakpoints) {
230217
if (bp.GetID() == bp_id)
231218
return &bp;
232219
}
@@ -1118,8 +1105,9 @@ protocol::Capabilities DAP::GetCapabilities() {
11181105
}
11191106

11201107
// Available filters or options for the setExceptionBreakpoints request.
1108+
PopulateExceptionBreakpoints();
11211109
std::vector<protocol::ExceptionBreakpointsFilter> filters;
1122-
for (const auto &exc_bp : *exception_breakpoints)
1110+
for (const auto &exc_bp : exception_breakpoints)
11231111
filters.emplace_back(CreateExceptionBreakpointFilter(exc_bp));
11241112
capabilities.exceptionBreakpointFilters = std::move(filters);
11251113

lldb/tools/lldb-dap/DAP.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ struct DAP {
9999
lldb::SBBroadcaster broadcaster;
100100
FunctionBreakpointMap function_breakpoints;
101101
InstructionBreakpointMap instruction_breakpoints;
102-
std::optional<std::vector<ExceptionBreakpoint>> exception_breakpoints;
102+
std::vector<ExceptionBreakpoint> exception_breakpoints;
103103
llvm::once_flag init_exception_breakpoints_flag;
104104

105105
/// Map step in target id to list of function targets that user can choose.
@@ -320,7 +320,7 @@ struct DAP {
320320
});
321321
}
322322

323-
/// The set of capablities supported by this adapter.
323+
/// The set of capabilities supported by this adapter.
324324
protocol::Capabilities GetCapabilities();
325325

326326
/// Debuggee will continue from stopped state.

lldb/tools/lldb-dap/ExceptionBreakpoint.cpp

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,33 @@
99
#include "ExceptionBreakpoint.h"
1010
#include "BreakpointBase.h"
1111
#include "DAP.h"
12+
#include "Protocol/ProtocolTypes.h"
1213
#include "lldb/API/SBMutex.h"
1314
#include "lldb/API/SBTarget.h"
1415
#include <mutex>
1516

17+
using namespace llvm;
18+
using namespace lldb_dap::protocol;
19+
1620
namespace lldb_dap {
1721

18-
void ExceptionBreakpoint::SetBreakpoint() {
22+
protocol::Breakpoint ExceptionBreakpoint::SetBreakpoint(StringRef condition) {
1923
lldb::SBMutex lock = m_dap.GetAPIMutex();
2024
std::lock_guard<lldb::SBMutex> guard(lock);
2125

22-
if (m_bp.IsValid())
23-
return;
24-
bool catch_value = m_filter.find("_catch") != std::string::npos;
25-
bool throw_value = m_filter.find("_throw") != std::string::npos;
26-
m_bp = m_dap.target.BreakpointCreateForException(m_language, catch_value,
27-
throw_value);
28-
m_bp.AddName(BreakpointBase::kDAPBreakpointLabel);
26+
if (!m_bp.IsValid()) {
27+
m_bp = m_dap.target.BreakpointCreateForException(
28+
m_language, m_kind == eExceptionKindCatch,
29+
m_kind == eExceptionKindThrow);
30+
m_bp.AddName(BreakpointBase::kDAPBreakpointLabel);
31+
}
32+
33+
m_bp.SetCondition(condition.data());
34+
35+
protocol::Breakpoint breakpoint;
36+
breakpoint.id = m_bp.GetID();
37+
breakpoint.verified = m_bp.IsValid();
38+
return breakpoint;
2939
}
3040

3141
void ExceptionBreakpoint::ClearBreakpoint() {

0 commit comments

Comments
 (0)