Skip to content

[lldb-dap] Improve stackTrace and exceptionInfo DAP request handlers #105905

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions lldb/packages/Python/lldbsuite/test/lldbplatformutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,22 @@ def findMainThreadCheckerDylib():
return ""


def findBacktraceRecordingDylib():
if not platformIsDarwin():
return ""

if getPlatform() in lldbplatform.translate(lldbplatform.darwin_embedded):
return "/Developer/usr/lib/libBacktraceRecording.dylib"

with os.popen("xcode-select -p") as output:
xcode_developer_path = output.read().strip()
mtc_dylib_path = "%s/usr/lib/libBacktraceRecording.dylib" % xcode_developer_path
if os.path.isfile(mtc_dylib_path):
return mtc_dylib_path

return ""


class _PlatformContext(object):
"""Value object class which contains platform-specific options."""

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,17 @@ def request_evaluate(self, expression, frameIndex=0, threadId=None, context=None
}
return self.send_recv(command_dict)

def request_exceptionInfo(self, threadId=None):
if threadId is None:
threadId = self.get_thread_id()
args_dict = {"threadId": threadId}
command_dict = {
"command": "exceptionInfo",
"type": "request",
"arguments": args_dict,
}
return self.send_recv(command_dict)

def request_initialize(self, sourceInitFile):
command_dict = {
"command": "initialize",
Expand Down Expand Up @@ -754,6 +765,7 @@ def request_launch(
runInTerminal=False,
postRunCommands=None,
enableAutoVariableSummaries=False,
enableDisplayExtendedBacktrace=False,
enableSyntheticChildDebugging=False,
commandEscapePrefix=None,
customFrameFormat=None,
Expand Down Expand Up @@ -806,6 +818,7 @@ def request_launch(

args_dict["enableAutoVariableSummaries"] = enableAutoVariableSummaries
args_dict["enableSyntheticChildDebugging"] = enableSyntheticChildDebugging
args_dict["enableDisplayExtendedBacktrace"] = enableDisplayExtendedBacktrace
args_dict["commandEscapePrefix"] = commandEscapePrefix
command_dict = {"command": "launch", "type": "request", "arguments": args_dict}
response = self.send_recv(command_dict)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,14 @@ def verify_breakpoint_hit(self, breakpoint_ids):
return
self.assertTrue(False, "breakpoint not hit")

def verify_stop_exception_info(self, expected_description):
def verify_stop_exception_info(self, expected_description, timeout=timeoutval):
"""Wait for the process we are debugging to stop, and verify the stop
reason is 'exception' and that the description matches
'expected_description'
"""
stopped_events = self.dap_server.wait_for_stopped()
stopped_events = self.dap_server.wait_for_stopped(timeout=timeout)
for stopped_event in stopped_events:
print("stopped_event", stopped_event)
if "body" in stopped_event:
body = stopped_event["body"]
if "reason" not in body:
Expand Down Expand Up @@ -180,6 +181,10 @@ def get_stackFrames(self, threadId=None, startFrame=None, levels=None, dump=Fals
)
return stackFrames

def get_exceptionInfo(self, threadId=None):
response = self.dap_server.request_exceptionInfo(threadId=threadId)
return self.get_dict_value(response, ["body"])

def get_source_and_line(self, threadId=None, frameIndex=0):
stackFrames = self.get_stackFrames(
threadId=threadId, startFrame=frameIndex, levels=1
Expand Down Expand Up @@ -381,6 +386,7 @@ def launch(
expectFailure=False,
postRunCommands=None,
enableAutoVariableSummaries=False,
enableDisplayExtendedBacktrace=False,
enableSyntheticChildDebugging=False,
commandEscapePrefix=None,
customFrameFormat=None,
Expand Down Expand Up @@ -422,6 +428,7 @@ def cleanup():
runInTerminal=runInTerminal,
postRunCommands=postRunCommands,
enableAutoVariableSummaries=enableAutoVariableSummaries,
enableDisplayExtendedBacktrace=enableDisplayExtendedBacktrace,
enableSyntheticChildDebugging=enableSyntheticChildDebugging,
commandEscapePrefix=commandEscapePrefix,
customFrameFormat=customFrameFormat,
Expand Down Expand Up @@ -461,6 +468,7 @@ def build_and_launch(
postRunCommands=None,
lldbDAPEnv=None,
enableAutoVariableSummaries=False,
enableDisplayExtendedBacktrace=False,
enableSyntheticChildDebugging=False,
commandEscapePrefix=None,
customFrameFormat=None,
Expand Down Expand Up @@ -497,6 +505,7 @@ def build_and_launch(
postRunCommands=postRunCommands,
enableAutoVariableSummaries=enableAutoVariableSummaries,
enableSyntheticChildDebugging=enableSyntheticChildDebugging,
enableDisplayExtendedBacktrace=enableDisplayExtendedBacktrace,
commandEscapePrefix=commandEscapePrefix,
customFrameFormat=customFrameFormat,
customThreadFormat=customThreadFormat,
Expand Down
2 changes: 1 addition & 1 deletion lldb/test/API/tools/lldb-dap/exception/Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
CXX_SOURCES := main.cpp
C_SOURCES := main.c

include Makefile.rules
8 changes: 5 additions & 3 deletions lldb/test/API/tools/lldb-dap/exception/TestDAP_exception.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Test exception behavior in DAP
Test exception behavior in DAP with signal.
"""


Expand All @@ -16,8 +16,10 @@ def test_stopped_description(self):
event.
"""
program = self.getBuildArtifact("a.out")
print("test_stopped_description called", flush=True)
self.build_and_launch(program)

self.dap_server.request_continue()
self.assertTrue(self.verify_stop_exception_info("signal SIGABRT"))
exceptionInfo = self.get_exceptionInfo()
self.assertEqual(exceptionInfo["breakMode"], "always")
self.assertEqual(exceptionInfo["description"], "signal SIGABRT")
self.assertEqual(exceptionInfo["exceptionId"], "signal")
3 changes: 3 additions & 0 deletions lldb/test/API/tools/lldb-dap/exception/cpp/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
CXX_SOURCES := main.cpp

include Makefile.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""
Test exception behavior in DAP with c++ throw.
"""


from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
import lldbdap_testcase


class TestDAP_exception_cpp(lldbdap_testcase.DAPTestCaseBase):
@skipIfWindows
def test_stopped_description(self):
"""
Test that exception description is shown correctly in stopped
event.
"""
program = self.getBuildArtifact("a.out")
self.build_and_launch(program)
self.dap_server.request_continue()
self.assertTrue(self.verify_stop_exception_info("signal SIGABRT"))
exceptionInfo = self.get_exceptionInfo()
self.assertEqual(exceptionInfo["breakMode"], "always")
self.assertEqual(exceptionInfo["description"], "signal SIGABRT")
self.assertEqual(exceptionInfo["exceptionId"], "signal")
self.assertIsNotNone(exceptionInfo["details"])
6 changes: 6 additions & 0 deletions lldb/test/API/tools/lldb-dap/exception/cpp/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include <stdexcept>

int main(int argc, char const *argv[]) {
throw std::invalid_argument("throwing exception for testing");
return 0;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include <signal.h>

int main() {
int main(int argc, char const *argv[]) {
raise(SIGABRT);
return 0;
}
9 changes: 9 additions & 0 deletions lldb/test/API/tools/lldb-dap/exception/objc/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
OBJC_SOURCES := main.m

CFLAGS_EXTRAS := -w

USE_SYSTEM_STDLIB := 1

LD_EXTRAS := -framework Foundation

include Makefile.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""
Test exception behavior in DAP with obj-c throw.
"""


from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
import lldbdap_testcase


class TestDAP_exception_objc(lldbdap_testcase.DAPTestCaseBase):
@skipUnlessDarwin
def test_stopped_description(self):
"""
Test that exception description is shown correctly in stopped event.
"""
program = self.getBuildArtifact("a.out")
self.build_and_launch(program)
self.dap_server.request_continue()
self.assertTrue(self.verify_stop_exception_info("signal SIGABRT"))
exception_info = self.get_exceptionInfo()
self.assertEqual(exception_info["breakMode"], "always")
self.assertEqual(exception_info["description"], "signal SIGABRT")
self.assertEqual(exception_info["exceptionId"], "signal")
exception_details = exception_info["details"]
self.assertRegex(exception_details["message"], "SomeReason")
self.assertRegex(exception_details["stackTrace"], "main.m")
8 changes: 8 additions & 0 deletions lldb/test/API/tools/lldb-dap/exception/objc/main.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#import <Foundation/Foundation.h>

int main(int argc, char const *argv[]) {
@throw [[NSException alloc] initWithName:@"ThrownException"
reason:@"SomeReason"
userInfo:nil];
return 0;
}
5 changes: 5 additions & 0 deletions lldb/test/API/tools/lldb-dap/extendedStackTrace/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
OBJC_SOURCES := main.m

USE_SYSTEM_STDLIB := 1

include Makefile.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
"""
Test lldb-dap stackTrace request with an extended backtrace thread.
"""


import os

import lldbdap_testcase
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test.lldbplatformutil import *


class TestDAP_extendedStackTrace(lldbdap_testcase.DAPTestCaseBase):
@skipUnlessDarwin
def test_stackTrace(self):
"""
Tests the 'stackTrace' packet on a thread with an extended backtrace.
"""
backtrace_recording_lib = findBacktraceRecordingDylib()
if not backtrace_recording_lib:
self.skipTest(
"Skipped because libBacktraceRecording.dylib was present on the system."
)

if not os.path.isfile("/usr/lib/system/introspection/libdispatch.dylib"):
self.skipTest(
"Skipped because introspection libdispatch dylib is not present."
)

program = self.getBuildArtifact("a.out")

self.build_and_launch(
program,
env=[
"DYLD_LIBRARY_PATH=/usr/lib/system/introspection",
"DYLD_INSERT_LIBRARIES=" + backtrace_recording_lib,
],
enableDisplayExtendedBacktrace=True,
)
source = "main.m"
breakpoint = line_number(source, "breakpoint 1")
lines = [breakpoint]

breakpoint_ids = self.set_source_breakpoints(source, lines)
self.assertEqual(
len(breakpoint_ids), len(lines), "expect correct number of breakpoints"
)

events = self.continue_to_next_stop()

stackFrames, totalFrames = self.get_stackFrames_and_totalFramesCount(
threadId=events[0]["body"]["threadId"]
)
self.assertGreaterEqual(len(stackFrames), 3, "expect >= 3 frames")
self.assertEqual(len(stackFrames), totalFrames)
self.assertEqual(stackFrames[0]["name"], "one")
self.assertEqual(stackFrames[1]["name"], "two")
self.assertEqual(stackFrames[2]["name"], "three")

stackLabels = [
(i, frame)
for i, frame in enumerate(stackFrames)
if frame.get("presentationHint", "") == "label"
]
self.assertEqual(len(stackLabels), 2, "expected two label stack frames")
self.assertRegex(
stackLabels[0][1]["name"],
"Enqueued from com.apple.root.default-qos \(Thread \d\)",
)
self.assertRegex(
stackLabels[1][1]["name"],
"Enqueued from com.apple.main-thread \(Thread \d\)",
)

for i, frame in stackLabels:
# Ensure requesting startFrame+levels across thread backtraces works as expected.
stackFrames, totalFrames = self.get_stackFrames_and_totalFramesCount(
threadId=events[0]["body"]["threadId"], startFrame=i - 1, levels=3
)
self.assertEqual(len(stackFrames), 3, "expected 3 frames with levels=3")
self.assertGreaterEqual(
totalFrames, i + 3, "total frames should include a pagination offset"
)
self.assertEqual(stackFrames[1], frame)

# Ensure requesting startFrame+levels at the beginning of a thread backtraces works as expected.
stackFrames, totalFrames = self.get_stackFrames_and_totalFramesCount(
threadId=events[0]["body"]["threadId"], startFrame=i, levels=3
)
self.assertEqual(len(stackFrames), 3, "expected 3 frames with levels=3")
self.assertGreaterEqual(
totalFrames, i + 3, "total frames should include a pagination offset"
)
self.assertEqual(stackFrames[0], frame)

# Ensure requests with startFrame+levels that end precisely on the last frame includes the totalFrames pagination offset.
stackFrames, totalFrames = self.get_stackFrames_and_totalFramesCount(
threadId=events[0]["body"]["threadId"], startFrame=i - 1, levels=1
)
self.assertEqual(len(stackFrames), 1, "expected 1 frames with levels=1")
self.assertGreaterEqual(
totalFrames, i, "total frames should include a pagination offset"
)
28 changes: 28 additions & 0 deletions lldb/test/API/tools/lldb-dap/extendedStackTrace/main.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#import <dispatch/dispatch.h>
#include <stdio.h>

void one() {
printf("one...\n"); // breakpoint 1
}

void two() {
printf("two...\n");
one();
}

void three() {
printf("three...\n");
two();
}

int main(int argc, char *argv[]) {
printf("main...\n");
// Nest from main queue > global queue > main queue.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
dispatch_async(dispatch_get_main_queue(), ^{
three();
});
});
dispatch_main();
}
Loading
Loading