Skip to content

Commit d8463cd

Browse files
ashgtiJDevlieghere
authored andcommitted
[lldb-dap] Improve stackTrace and exceptionInfo DAP request handlers (llvm#105905)
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. (cherry picked from commit 5b4100c)
1 parent bbbbeb1 commit d8463cd

File tree

23 files changed

+571
-100
lines changed

23 files changed

+571
-100
lines changed

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

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

182182

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

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

Lines changed: 13 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",
@@ -754,6 +765,7 @@ def request_launch(
754765
runInTerminal=False,
755766
postRunCommands=None,
756767
enableAutoVariableSummaries=False,
768+
enableDisplayExtendedBacktrace=False,
757769
enableSyntheticChildDebugging=False,
758770
commandEscapePrefix=None,
759771
customFrameFormat=None,
@@ -806,6 +818,7 @@ def request_launch(
806818

807819
args_dict["enableAutoVariableSummaries"] = enableAutoVariableSummaries
808820
args_dict["enableSyntheticChildDebugging"] = enableSyntheticChildDebugging
821+
args_dict["enableDisplayExtendedBacktrace"] = enableDisplayExtendedBacktrace
809822
args_dict["commandEscapePrefix"] = commandEscapePrefix
810823
command_dict = {"command": "launch", "type": "request", "arguments": args_dict}
811824
response = self.send_recv(command_dict)

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,13 +103,14 @@ def verify_breakpoint_hit(self, breakpoint_ids):
103103
return
104104
self.assertTrue(False, "breakpoint not hit")
105105

106-
def verify_stop_exception_info(self, expected_description):
106+
def verify_stop_exception_info(self, expected_description, timeout=timeoutval):
107107
"""Wait for the process we are debugging to stop, and verify the stop
108108
reason is 'exception' and that the description matches
109109
'expected_description'
110110
"""
111-
stopped_events = self.dap_server.wait_for_stopped()
111+
stopped_events = self.dap_server.wait_for_stopped(timeout=timeout)
112112
for stopped_event in stopped_events:
113+
print("stopped_event", stopped_event)
113114
if "body" in stopped_event:
114115
body = stopped_event["body"]
115116
if "reason" not in body:
@@ -180,6 +181,10 @@ def get_stackFrames(self, threadId=None, startFrame=None, levels=None, dump=Fals
180181
)
181182
return stackFrames
182183

184+
def get_exceptionInfo(self, threadId=None):
185+
response = self.dap_server.request_exceptionInfo(threadId=threadId)
186+
return self.get_dict_value(response, ["body"])
187+
183188
def get_source_and_line(self, threadId=None, frameIndex=0):
184189
stackFrames = self.get_stackFrames(
185190
threadId=threadId, startFrame=frameIndex, levels=1
@@ -381,6 +386,7 @@ def launch(
381386
expectFailure=False,
382387
postRunCommands=None,
383388
enableAutoVariableSummaries=False,
389+
enableDisplayExtendedBacktrace=False,
384390
enableSyntheticChildDebugging=False,
385391
commandEscapePrefix=None,
386392
customFrameFormat=None,
@@ -422,6 +428,7 @@ def cleanup():
422428
runInTerminal=runInTerminal,
423429
postRunCommands=postRunCommands,
424430
enableAutoVariableSummaries=enableAutoVariableSummaries,
431+
enableDisplayExtendedBacktrace=enableDisplayExtendedBacktrace,
425432
enableSyntheticChildDebugging=enableSyntheticChildDebugging,
426433
commandEscapePrefix=commandEscapePrefix,
427434
customFrameFormat=customFrameFormat,
@@ -461,6 +468,7 @@ def build_and_launch(
461468
postRunCommands=None,
462469
lldbDAPEnv=None,
463470
enableAutoVariableSummaries=False,
471+
enableDisplayExtendedBacktrace=False,
464472
enableSyntheticChildDebugging=False,
465473
commandEscapePrefix=None,
466474
customFrameFormat=None,
@@ -497,6 +505,7 @@ def build_and_launch(
497505
postRunCommands=postRunCommands,
498506
enableAutoVariableSummaries=enableAutoVariableSummaries,
499507
enableSyntheticChildDebugging=enableSyntheticChildDebugging,
508+
enableDisplayExtendedBacktrace=enableDisplayExtendedBacktrace,
500509
commandEscapePrefix=commandEscapePrefix,
501510
customFrameFormat=customFrameFormat,
502511
customThreadFormat=customThreadFormat,
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: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
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+
enableDisplayExtendedBacktrace=True,
40+
)
41+
source = "main.m"
42+
breakpoint = line_number(source, "breakpoint 1")
43+
lines = [breakpoint]
44+
45+
breakpoint_ids = self.set_source_breakpoints(source, lines)
46+
self.assertEqual(
47+
len(breakpoint_ids), len(lines), "expect correct number of breakpoints"
48+
)
49+
50+
events = self.continue_to_next_stop()
51+
52+
stackFrames, totalFrames = self.get_stackFrames_and_totalFramesCount(
53+
threadId=events[0]["body"]["threadId"]
54+
)
55+
self.assertGreaterEqual(len(stackFrames), 3, "expect >= 3 frames")
56+
self.assertEqual(len(stackFrames), totalFrames)
57+
self.assertEqual(stackFrames[0]["name"], "one")
58+
self.assertEqual(stackFrames[1]["name"], "two")
59+
self.assertEqual(stackFrames[2]["name"], "three")
60+
61+
stackLabels = [
62+
(i, frame)
63+
for i, frame in enumerate(stackFrames)
64+
if frame.get("presentationHint", "") == "label"
65+
]
66+
self.assertEqual(len(stackLabels), 2, "expected two label stack frames")
67+
self.assertRegex(
68+
stackLabels[0][1]["name"],
69+
"Enqueued from com.apple.root.default-qos \(Thread \d\)",
70+
)
71+
self.assertRegex(
72+
stackLabels[1][1]["name"],
73+
"Enqueued from com.apple.main-thread \(Thread \d\)",
74+
)
75+
76+
for i, frame in stackLabels:
77+
# Ensure requesting startFrame+levels across thread backtraces works as expected.
78+
stackFrames, totalFrames = self.get_stackFrames_and_totalFramesCount(
79+
threadId=events[0]["body"]["threadId"], startFrame=i - 1, levels=3
80+
)
81+
self.assertEqual(len(stackFrames), 3, "expected 3 frames with levels=3")
82+
self.assertGreaterEqual(
83+
totalFrames, i + 3, "total frames should include a pagination offset"
84+
)
85+
self.assertEqual(stackFrames[1], frame)
86+
87+
# Ensure requesting startFrame+levels at the beginning of a thread backtraces works as expected.
88+
stackFrames, totalFrames = self.get_stackFrames_and_totalFramesCount(
89+
threadId=events[0]["body"]["threadId"], startFrame=i, levels=3
90+
)
91+
self.assertEqual(len(stackFrames), 3, "expected 3 frames with levels=3")
92+
self.assertGreaterEqual(
93+
totalFrames, i + 3, "total frames should include a pagination offset"
94+
)
95+
self.assertEqual(stackFrames[0], frame)
96+
97+
# Ensure requests with startFrame+levels that end precisely on the last frame includes the totalFrames pagination offset.
98+
stackFrames, totalFrames = self.get_stackFrames_and_totalFramesCount(
99+
threadId=events[0]["body"]["threadId"], startFrame=i - 1, levels=1
100+
)
101+
self.assertEqual(len(stackFrames), 1, "expected 1 frames with levels=1")
102+
self.assertGreaterEqual(
103+
totalFrames, i, "total frames should include a pagination offset"
104+
)
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+
}

0 commit comments

Comments
 (0)