Skip to content

Commit ebcf484

Browse files
committed
[lldb-dap] Improve stackTrace and exceptionInfo DAP request handlers.
Refactoring `stackTrace` to perform frame look ups in a more on-demand fashion to improve overall performance. Additionally adding additional information to the `exceptionInfo` request to report exception stacks there instead of merging the exception stack into the stack trace. The `exceptionInfo` request is only called if a stop event occurs with `reason='exception'`, which should mitigate the performance of `SBThread::GetCurrentException` calls. Adding unit tests for exception handling and stack trace supporting.
1 parent cdd11d6 commit ebcf484

File tree

22 files changed

+449
-88
lines changed

22 files changed

+449
-88
lines changed

lldb/packages/Python/lldbsuite/test/lldbplatformutil.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,22 @@ def findMainThreadCheckerDylib():
181181
return ""
182182

183183

184+
def findBacktraceRecordingDylib():
185+
if not platformIsDarwin():
186+
return ""
187+
188+
if getPlatform() in lldbplatform.translate(lldbplatform.darwin_embedded):
189+
return "/Developer/usr/lib/libBacktraceRecording.dylib"
190+
191+
with os.popen("xcode-select -p") as output:
192+
xcode_developer_path = output.read().strip()
193+
mtc_dylib_path = "%s/usr/lib/libBacktraceRecording.dylib" % xcode_developer_path
194+
if os.path.isfile(mtc_dylib_path):
195+
return mtc_dylib_path
196+
197+
return ""
198+
199+
184200
class _PlatformContext(object):
185201
"""Value object class which contains platform-specific options."""
186202

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,17 @@ def request_evaluate(self, expression, frameIndex=0, threadId=None, context=None
707707
}
708708
return self.send_recv(command_dict)
709709

710+
def request_exceptionInfo(self, threadId=None):
711+
if threadId is None:
712+
threadId = self.get_thread_id()
713+
args_dict = {"threadId": threadId}
714+
command_dict = {
715+
"command": "exceptionInfo",
716+
"type": "request",
717+
"arguments": args_dict,
718+
}
719+
return self.send_recv(command_dict)
720+
710721
def request_initialize(self, sourceInitFile):
711722
command_dict = {
712723
"command": "initialize",

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,13 +100,14 @@ def verify_breakpoint_hit(self, breakpoint_ids):
100100
return
101101
self.assertTrue(False, "breakpoint not hit")
102102

103-
def verify_stop_exception_info(self, expected_description):
103+
def verify_stop_exception_info(self, expected_description, timeout=timeoutval):
104104
"""Wait for the process we are debugging to stop, and verify the stop
105105
reason is 'exception' and that the description matches
106106
'expected_description'
107107
"""
108-
stopped_events = self.dap_server.wait_for_stopped()
108+
stopped_events = self.dap_server.wait_for_stopped(timeout=timeout)
109109
for stopped_event in stopped_events:
110+
print("stopped_event", stopped_event)
110111
if "body" in stopped_event:
111112
body = stopped_event["body"]
112113
if "reason" not in body:
@@ -177,6 +178,10 @@ def get_stackFrames(self, threadId=None, startFrame=None, levels=None, dump=Fals
177178
)
178179
return stackFrames
179180

181+
def get_exceptionInfo(self, threadId=None):
182+
response = self.dap_server.request_exceptionInfo(threadId=threadId)
183+
return self.get_dict_value(response, ["body"])
184+
180185
def get_source_and_line(self, threadId=None, frameIndex=0):
181186
stackFrames = self.get_stackFrames(
182187
threadId=threadId, startFrame=frameIndex, levels=1
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
CXX_SOURCES := main.cpp
1+
C_SOURCES := main.c
22

33
include Makefile.rules

lldb/test/API/tools/lldb-dap/exception/TestDAP_exception.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
2-
Test exception behavior in DAP
2+
Test exception behavior in DAP with signal.
33
"""
44

55

@@ -16,8 +16,10 @@ def test_stopped_description(self):
1616
event.
1717
"""
1818
program = self.getBuildArtifact("a.out")
19-
print("test_stopped_description called", flush=True)
2019
self.build_and_launch(program)
21-
2220
self.dap_server.request_continue()
2321
self.assertTrue(self.verify_stop_exception_info("signal SIGABRT"))
22+
exceptionInfo = self.get_exceptionInfo()
23+
self.assertEqual(exceptionInfo["breakMode"], "always")
24+
self.assertEqual(exceptionInfo["description"], "signal SIGABRT")
25+
self.assertEqual(exceptionInfo["exceptionId"], "signal")
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
CXX_SOURCES := main.cpp
2+
3+
include Makefile.rules
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"""
2+
Test exception behavior in DAP with c++ throw.
3+
"""
4+
5+
6+
from lldbsuite.test.decorators import *
7+
from lldbsuite.test.lldbtest import *
8+
import lldbdap_testcase
9+
10+
11+
class TestDAP_exception_cpp(lldbdap_testcase.DAPTestCaseBase):
12+
@skipIfWindows
13+
def test_stopped_description(self):
14+
"""
15+
Test that exception description is shown correctly in stopped
16+
event.
17+
"""
18+
program = self.getBuildArtifact("a.out")
19+
self.build_and_launch(program)
20+
self.dap_server.request_continue()
21+
self.assertTrue(self.verify_stop_exception_info("signal SIGABRT"))
22+
exceptionInfo = self.get_exceptionInfo()
23+
self.assertEqual(exceptionInfo["breakMode"], "always")
24+
self.assertEqual(exceptionInfo["description"], "signal SIGABRT")
25+
self.assertEqual(exceptionInfo["exceptionId"], "signal")
26+
self.assertIsNotNone(exceptionInfo["details"])
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#include <stdexcept>
2+
3+
int main(int argc, char const *argv[]) {
4+
throw std::invalid_argument("throwing exception for testing");
5+
return 0;
6+
}
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#include <signal.h>
22

3-
int main() {
3+
int main(int argc, char const *argv[]) {
44
raise(SIGABRT);
55
return 0;
66
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
OBJC_SOURCES := main.m
2+
3+
CFLAGS_EXTRAS := -w
4+
5+
USE_SYSTEM_STDLIB := 1
6+
7+
LD_EXTRAS := -framework Foundation
8+
9+
include Makefile.rules
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"""
2+
Test exception behavior in DAP with obj-c throw.
3+
"""
4+
5+
6+
from lldbsuite.test.decorators import *
7+
from lldbsuite.test.lldbtest import *
8+
import lldbdap_testcase
9+
10+
11+
class TestDAP_exception_objc(lldbdap_testcase.DAPTestCaseBase):
12+
@skipUnlessDarwin
13+
def test_stopped_description(self):
14+
"""
15+
Test that exception description is shown correctly in stopped event.
16+
"""
17+
program = self.getBuildArtifact("a.out")
18+
self.build_and_launch(program)
19+
self.dap_server.request_continue()
20+
self.assertTrue(self.verify_stop_exception_info("signal SIGABRT"))
21+
exception_info = self.get_exceptionInfo()
22+
self.assertEqual(exception_info["breakMode"], "always")
23+
self.assertEqual(exception_info["description"], "signal SIGABRT")
24+
self.assertEqual(exception_info["exceptionId"], "signal")
25+
exception_details = exception_info["details"]
26+
self.assertRegex(exception_details["message"], "SomeReason")
27+
self.assertRegex(exception_details["stackTrace"], "main.m")
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#import <Foundation/Foundation.h>
2+
3+
int main(int argc, char const *argv[]) {
4+
@throw [[NSException alloc] initWithName:@"ThrownException"
5+
reason:@"SomeReason"
6+
userInfo:nil];
7+
return 0;
8+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
OBJC_SOURCES := main.m
2+
3+
USE_SYSTEM_STDLIB := 1
4+
5+
include Makefile.rules
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""
2+
Test lldb-dap stackTrace request with an extended backtrace thread.
3+
"""
4+
5+
6+
import os
7+
8+
import lldbdap_testcase
9+
from lldbsuite.test.decorators import *
10+
from lldbsuite.test.lldbtest import *
11+
from lldbsuite.test.lldbplatformutil import *
12+
13+
14+
class TestDAP_extendedStackTrace(lldbdap_testcase.DAPTestCaseBase):
15+
@skipUnlessDarwin
16+
def test_stackTrace(self):
17+
"""
18+
Tests the 'stackTrace' packet on a thread with an extended backtrace.
19+
"""
20+
backtrace_recording_lib = findBacktraceRecordingDylib()
21+
if not backtrace_recording_lib:
22+
self.skipTest(
23+
"Skipped because libBacktraceRecording.dylib was present on the system."
24+
)
25+
26+
if not os.path.isfile("/usr/lib/system/introspection/libdispatch.dylib"):
27+
self.skipTest(
28+
"Skipped because introspection libdispatch dylib is not present."
29+
)
30+
31+
program = self.getBuildArtifact("a.out")
32+
33+
self.build_and_launch(
34+
program,
35+
env=[
36+
"DYLD_LIBRARY_PATH=/usr/lib/system/introspection",
37+
"DYLD_INSERT_LIBRARIES=" + backtrace_recording_lib,
38+
],
39+
)
40+
source = "main.m"
41+
breakpoint = line_number(source, "breakpoint 1")
42+
lines = [breakpoint]
43+
44+
breakpoint_ids = self.set_source_breakpoints(source, lines)
45+
self.assertEqual(
46+
len(breakpoint_ids), len(lines), "expect correct number of breakpoints"
47+
)
48+
49+
events = self.continue_to_next_stop()
50+
print("huh", events)
51+
stackFrames = self.get_stackFrames(threadId=events[0]["body"]["threadId"])
52+
self.assertGreaterEqual(len(stackFrames), 3, "expect >= 3 frames")
53+
self.assertEqual(stackFrames[0]["name"], "one")
54+
self.assertEqual(stackFrames[1]["name"], "two")
55+
self.assertEqual(stackFrames[2]["name"], "three")
56+
57+
stackLabels = [
58+
frame
59+
for frame in stackFrames
60+
if frame.get("presentationHint", "") == "label"
61+
]
62+
self.assertEqual(len(stackLabels), 2, "expected two label stack frames")
63+
self.assertRegex(
64+
stackLabels[0]["name"],
65+
"Enqueued from com.apple.root.default-qos \(Thread \d\)",
66+
)
67+
self.assertRegex(
68+
stackLabels[1]["name"], "Enqueued from com.apple.main-thread \(Thread \d\)"
69+
)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#import <dispatch/dispatch.h>
2+
#include <stdio.h>
3+
4+
void one() {
5+
printf("one...\n"); // breakpoint 1
6+
}
7+
8+
void two() {
9+
printf("two...\n");
10+
one();
11+
}
12+
13+
void three() {
14+
printf("three...\n");
15+
two();
16+
}
17+
18+
int main(int argc, char *argv[]) {
19+
printf("main...\n");
20+
// Nest from main queue > global queue > main queue.
21+
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
22+
^{
23+
dispatch_async(dispatch_get_main_queue(), ^{
24+
three();
25+
});
26+
});
27+
dispatch_main();
28+
}

lldb/test/API/tools/lldb-dap/stackTrace/TestDAP_stackTrace.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
"""
2-
Test lldb-dap setBreakpoints request
2+
Test lldb-dap stackTrace request
33
"""
44

55

66
import os
77

8-
import dap_server
98
import lldbdap_testcase
10-
from lldbsuite.test import lldbutil
119
from lldbsuite.test.decorators import *
1210
from lldbsuite.test.lldbtest import *
1311

lldb/test/API/tools/lldb-dap/stackTraceMissingFunctionName/TestDAP_stackTraceMissingFunctionName.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,8 @@
22
Test lldb-dap stack trace response
33
"""
44

5-
6-
import dap_server
75
from lldbsuite.test.decorators import *
8-
import os
9-
106
import lldbdap_testcase
11-
from lldbsuite.test import lldbtest, lldbutil
127

138

149
class TestDAP_stackTraceMissingFunctionName(lldbdap_testcase.DAPTestCaseBase):

lldb/tools/lldb-dap/DAP.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ DAP::DAP()
3636
focus_tid(LLDB_INVALID_THREAD_ID), stop_at_entry(false), is_attach(false),
3737
enable_auto_variable_summaries(false),
3838
enable_synthetic_child_debugging(false),
39-
enable_display_extended_backtrace(false),
4039
restarting_process_id(LLDB_INVALID_PROCESS_ID),
4140
configuration_done_sent(false), waiting_for_run_in_terminal(false),
4241
progress_event_reporter(

lldb/tools/lldb-dap/DAP.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,6 @@ struct DAP {
181181
bool is_attach;
182182
bool enable_auto_variable_summaries;
183183
bool enable_synthetic_child_debugging;
184-
bool enable_display_extended_backtrace;
185184
// The process event thread normally responds to process exited events by
186185
// shutting down the entire adapter. When we're restarting, we keep the id of
187186
// the old process here so we can detect this case and keep running.
@@ -193,6 +192,7 @@ struct DAP {
193192
// Keep track of the last stop thread index IDs as threads won't go away
194193
// unless we send a "thread" event to indicate the thread exited.
195194
llvm::DenseSet<lldb::tid_t> thread_ids;
195+
std::map<lldb::tid_t, uint32_t> thread_stack_size_cache;
196196
uint32_t reverse_request_seq;
197197
std::mutex call_mutex;
198198
std::map<int /* request_seq */, ResponseCallback /* reply handler */>

lldb/tools/lldb-dap/JSONUtils.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1260,6 +1260,46 @@ llvm::json::Value CreateVariable(lldb::SBValue v, int64_t variablesReference,
12601260
return llvm::json::Value(std::move(object));
12611261
}
12621262

1263+
// "ExceptionDetails": {
1264+
// "type": "object",
1265+
// "description": "Detailed information about an exception that has
1266+
// occurred.", "properties": {
1267+
// "message": {
1268+
// "type": "string",
1269+
// "description": "Message contained in the exception."
1270+
// },
1271+
// "typeName": {
1272+
// "type": "string",
1273+
// "description": "Short type name of the exception object."
1274+
// },
1275+
// "fullTypeName": {
1276+
// "type": "string",
1277+
// "description": "Fully-qualified type name of the exception object."
1278+
// },
1279+
// "evaluateName": {
1280+
// "type": "string",
1281+
// "description": "An expression that can be evaluated in the current
1282+
// scope to obtain the exception object."
1283+
// },
1284+
// "stackTrace": {
1285+
// "type": "string",
1286+
// "description": "Stack trace at the time the exception was thrown."
1287+
// },
1288+
// "innerException": {
1289+
// "type": "array",
1290+
// "items": {
1291+
// "$ref": "#/definitions/ExceptionDetails"
1292+
// },
1293+
// "description": "Details of the exception contained by this exception,
1294+
// if any."
1295+
// }
1296+
// }
1297+
// },
1298+
llvm::json::Value CreateExceptionDetails() {
1299+
llvm::json::Object object;
1300+
return llvm::json::Value(std::move(object));
1301+
}
1302+
12631303
llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit unit) {
12641304
llvm::json::Object object;
12651305
char unit_path_arr[PATH_MAX];

0 commit comments

Comments
 (0)