[Lldb-commits] [lldb] 5b4100c - [lldb-dap] Improve `stackTrace` and `exceptionInfo` DAP request handlers (#105905)

via lldb-commits lldb-commits at lists.llvm.org
Tue Sep 10 12:40:24 PDT 2024


Author: John Harrison
Date: 2024-09-10T12:40:20-07:00
New Revision: 5b4100cc354148a1140546e7f5ac2bf380bc5eff

URL: https://github.com/llvm/llvm-project/commit/5b4100cc354148a1140546e7f5ac2bf380bc5eff
DIFF: https://github.com/llvm/llvm-project/commit/5b4100cc354148a1140546e7f5ac2bf380bc5eff.diff

LOG: [lldb-dap] Improve `stackTrace` and `exceptionInfo` DAP request handlers (#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.

Added: 
    lldb/test/API/tools/lldb-dap/exception/cpp/Makefile
    lldb/test/API/tools/lldb-dap/exception/cpp/TestDAP_exception_cpp.py
    lldb/test/API/tools/lldb-dap/exception/cpp/main.cpp
    lldb/test/API/tools/lldb-dap/exception/main.c
    lldb/test/API/tools/lldb-dap/exception/objc/Makefile
    lldb/test/API/tools/lldb-dap/exception/objc/TestDAP_exception_objc.py
    lldb/test/API/tools/lldb-dap/exception/objc/main.m
    lldb/test/API/tools/lldb-dap/extendedStackTrace/Makefile
    lldb/test/API/tools/lldb-dap/extendedStackTrace/TestDAP_extendedStackTrace.py
    lldb/test/API/tools/lldb-dap/extendedStackTrace/main.m

Modified: 
    lldb/packages/Python/lldbsuite/test/lldbplatformutil.py
    lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
    lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py
    lldb/test/API/tools/lldb-dap/exception/Makefile
    lldb/test/API/tools/lldb-dap/exception/TestDAP_exception.py
    lldb/test/API/tools/lldb-dap/stackTrace/TestDAP_stackTrace.py
    lldb/test/API/tools/lldb-dap/stackTrace/main.c
    lldb/test/API/tools/lldb-dap/stackTraceMissingFunctionName/TestDAP_stackTraceMissingFunctionName.py
    lldb/tools/lldb-dap/JSONUtils.cpp
    lldb/tools/lldb-dap/JSONUtils.h
    lldb/tools/lldb-dap/README.md
    lldb/tools/lldb-dap/lldb-dap.cpp
    lldb/tools/lldb-dap/package.json

Removed: 
    lldb/test/API/tools/lldb-dap/exception/main.cpp


################################################################################
diff  --git a/lldb/packages/Python/lldbsuite/test/lldbplatformutil.py b/lldb/packages/Python/lldbsuite/test/lldbplatformutil.py
index 602e15d207e94a..3d8c713562e9bf 100644
--- a/lldb/packages/Python/lldbsuite/test/lldbplatformutil.py
+++ b/lldb/packages/Python/lldbsuite/test/lldbplatformutil.py
@@ -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."""
 

diff  --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
index b095171d8fd1a4..c6417760f17a2b 100644
--- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
@@ -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",
@@ -754,6 +765,7 @@ def request_launch(
         runInTerminal=False,
         postRunCommands=None,
         enableAutoVariableSummaries=False,
+        enableDisplayExtendedBacktrace=False,
         enableSyntheticChildDebugging=False,
         commandEscapePrefix=None,
         customFrameFormat=None,
@@ -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)

diff  --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py
index 709b7aff11d7f2..7b192d62881729 100644
--- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py
@@ -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:
@@ -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
@@ -381,6 +386,7 @@ def launch(
         expectFailure=False,
         postRunCommands=None,
         enableAutoVariableSummaries=False,
+        enableDisplayExtendedBacktrace=False,
         enableSyntheticChildDebugging=False,
         commandEscapePrefix=None,
         customFrameFormat=None,
@@ -422,6 +428,7 @@ def cleanup():
             runInTerminal=runInTerminal,
             postRunCommands=postRunCommands,
             enableAutoVariableSummaries=enableAutoVariableSummaries,
+            enableDisplayExtendedBacktrace=enableDisplayExtendedBacktrace,
             enableSyntheticChildDebugging=enableSyntheticChildDebugging,
             commandEscapePrefix=commandEscapePrefix,
             customFrameFormat=customFrameFormat,
@@ -461,6 +468,7 @@ def build_and_launch(
         postRunCommands=None,
         lldbDAPEnv=None,
         enableAutoVariableSummaries=False,
+        enableDisplayExtendedBacktrace=False,
         enableSyntheticChildDebugging=False,
         commandEscapePrefix=None,
         customFrameFormat=None,
@@ -497,6 +505,7 @@ def build_and_launch(
             postRunCommands=postRunCommands,
             enableAutoVariableSummaries=enableAutoVariableSummaries,
             enableSyntheticChildDebugging=enableSyntheticChildDebugging,
+            enableDisplayExtendedBacktrace=enableDisplayExtendedBacktrace,
             commandEscapePrefix=commandEscapePrefix,
             customFrameFormat=customFrameFormat,
             customThreadFormat=customThreadFormat,

diff  --git a/lldb/test/API/tools/lldb-dap/exception/Makefile b/lldb/test/API/tools/lldb-dap/exception/Makefile
index 99998b20bcb050..10495940055b63 100644
--- a/lldb/test/API/tools/lldb-dap/exception/Makefile
+++ b/lldb/test/API/tools/lldb-dap/exception/Makefile
@@ -1,3 +1,3 @@
-CXX_SOURCES := main.cpp
+C_SOURCES := main.c
 
 include Makefile.rules

diff  --git a/lldb/test/API/tools/lldb-dap/exception/TestDAP_exception.py b/lldb/test/API/tools/lldb-dap/exception/TestDAP_exception.py
index 8c2c0154ba65c0..39d73737b7e8c0 100644
--- a/lldb/test/API/tools/lldb-dap/exception/TestDAP_exception.py
+++ b/lldb/test/API/tools/lldb-dap/exception/TestDAP_exception.py
@@ -1,5 +1,5 @@
 """
-Test exception behavior in DAP
+Test exception behavior in DAP with signal.
 """
 
 
@@ -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")

diff  --git a/lldb/test/API/tools/lldb-dap/exception/cpp/Makefile b/lldb/test/API/tools/lldb-dap/exception/cpp/Makefile
new file mode 100644
index 00000000000000..99998b20bcb050
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/exception/cpp/Makefile
@@ -0,0 +1,3 @@
+CXX_SOURCES := main.cpp
+
+include Makefile.rules

diff  --git a/lldb/test/API/tools/lldb-dap/exception/cpp/TestDAP_exception_cpp.py b/lldb/test/API/tools/lldb-dap/exception/cpp/TestDAP_exception_cpp.py
new file mode 100644
index 00000000000000..6471e2b87251a7
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/exception/cpp/TestDAP_exception_cpp.py
@@ -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"])

diff  --git a/lldb/test/API/tools/lldb-dap/exception/cpp/main.cpp b/lldb/test/API/tools/lldb-dap/exception/cpp/main.cpp
new file mode 100644
index 00000000000000..39d89b95319a8c
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/exception/cpp/main.cpp
@@ -0,0 +1,6 @@
+#include <stdexcept>
+
+int main(int argc, char const *argv[]) {
+  throw std::invalid_argument("throwing exception for testing");
+  return 0;
+}

diff  --git a/lldb/test/API/tools/lldb-dap/exception/main.cpp b/lldb/test/API/tools/lldb-dap/exception/main.c
similarity index 56%
rename from lldb/test/API/tools/lldb-dap/exception/main.cpp
rename to lldb/test/API/tools/lldb-dap/exception/main.c
index b940d07c6f2bb3..a653ac5d82aa3a 100644
--- a/lldb/test/API/tools/lldb-dap/exception/main.cpp
+++ b/lldb/test/API/tools/lldb-dap/exception/main.c
@@ -1,6 +1,6 @@
 #include <signal.h>
 
-int main() {
+int main(int argc, char const *argv[]) {
   raise(SIGABRT);
   return 0;
 }

diff  --git a/lldb/test/API/tools/lldb-dap/exception/objc/Makefile b/lldb/test/API/tools/lldb-dap/exception/objc/Makefile
new file mode 100644
index 00000000000000..9b6528337cb9d8
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/exception/objc/Makefile
@@ -0,0 +1,9 @@
+OBJC_SOURCES := main.m
+
+CFLAGS_EXTRAS := -w
+
+USE_SYSTEM_STDLIB := 1
+
+LD_EXTRAS := -framework Foundation
+
+include Makefile.rules

diff  --git a/lldb/test/API/tools/lldb-dap/exception/objc/TestDAP_exception_objc.py b/lldb/test/API/tools/lldb-dap/exception/objc/TestDAP_exception_objc.py
new file mode 100644
index 00000000000000..777d55f48e8504
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/exception/objc/TestDAP_exception_objc.py
@@ -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")

diff  --git a/lldb/test/API/tools/lldb-dap/exception/objc/main.m b/lldb/test/API/tools/lldb-dap/exception/objc/main.m
new file mode 100644
index 00000000000000..e8db04fb40de15
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/exception/objc/main.m
@@ -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;
+}

diff  --git a/lldb/test/API/tools/lldb-dap/extendedStackTrace/Makefile b/lldb/test/API/tools/lldb-dap/extendedStackTrace/Makefile
new file mode 100644
index 00000000000000..e4ee1a0506c0cf
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/extendedStackTrace/Makefile
@@ -0,0 +1,5 @@
+OBJC_SOURCES := main.m
+
+USE_SYSTEM_STDLIB := 1
+
+include Makefile.rules

diff  --git a/lldb/test/API/tools/lldb-dap/extendedStackTrace/TestDAP_extendedStackTrace.py b/lldb/test/API/tools/lldb-dap/extendedStackTrace/TestDAP_extendedStackTrace.py
new file mode 100644
index 00000000000000..0cc8534daf4e94
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/extendedStackTrace/TestDAP_extendedStackTrace.py
@@ -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"
+            )

diff  --git a/lldb/test/API/tools/lldb-dap/extendedStackTrace/main.m b/lldb/test/API/tools/lldb-dap/extendedStackTrace/main.m
new file mode 100644
index 00000000000000..d513880236c517
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/extendedStackTrace/main.m
@@ -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();
+}

diff  --git a/lldb/test/API/tools/lldb-dap/stackTrace/TestDAP_stackTrace.py b/lldb/test/API/tools/lldb-dap/stackTrace/TestDAP_stackTrace.py
index 0d7776faa4a9de..56ed1ebdf7ab44 100644
--- a/lldb/test/API/tools/lldb-dap/stackTrace/TestDAP_stackTrace.py
+++ b/lldb/test/API/tools/lldb-dap/stackTrace/TestDAP_stackTrace.py
@@ -1,13 +1,11 @@
 """
-Test lldb-dap setBreakpoints request
+Test lldb-dap stackTrace request
 """
 
 
 import os
 
-import dap_server
 import lldbdap_testcase
-from lldbsuite.test import lldbutil
 from lldbsuite.test.decorators import *
 from lldbsuite.test.lldbtest import *
 
@@ -17,11 +15,14 @@ class TestDAP_stackTrace(lldbdap_testcase.DAPTestCaseBase):
     source_key_path = ["source", "path"]
     line_key_path = ["line"]
 
+    # stackTrace additioanl frames for paginated traces
+    page_size = 20
+
     def verify_stackFrames(self, start_idx, stackFrames):
         frame_idx = start_idx
         for stackFrame in stackFrames:
             # Don't care about frame above main
-            if frame_idx > 20:
+            if frame_idx > 40:
                 return
             self.verify_stackFrame(frame_idx, stackFrame)
             frame_idx += 1
@@ -33,7 +34,7 @@ def verify_stackFrame(self, frame_idx, stackFrame):
         if frame_idx == 0:
             expected_line = self.recurse_end
             expected_name = "recurse"
-        elif frame_idx < 20:
+        elif frame_idx < 40:
             expected_line = self.recurse_call
             expected_name = "recurse"
         else:
@@ -83,10 +84,24 @@ def test_stackTrace(self):
         (stackFrames, totalFrames) = self.get_stackFrames_and_totalFramesCount()
         frameCount = len(stackFrames)
         self.assertGreaterEqual(
-            frameCount, 20, "verify we get at least 20 frames for all frames"
+            frameCount, 40, "verify we get at least 40 frames for all frames"
+        )
+        self.assertEqual(
+            totalFrames,
+            frameCount,
+            "verify total frames returns a speculative page size",
         )
+        self.verify_stackFrames(startFrame, stackFrames)
+
+        # Verify totalFrames contains a speculative page size of additional frames with startFrame = 0 and levels = 0
+        (stackFrames, totalFrames) = self.get_stackFrames_and_totalFramesCount(
+            startFrame=0, levels=10
+        )
+        self.assertEqual(len(stackFrames), 10, "verify we get levels=10 frames")
         self.assertEqual(
-            totalFrames, frameCount, "verify we get correct value for totalFrames count"
+            totalFrames,
+            len(stackFrames) + self.page_size,
+            "verify total frames returns a speculative page size",
         )
         self.verify_stackFrames(startFrame, stackFrames)
 

diff  --git a/lldb/test/API/tools/lldb-dap/stackTrace/main.c b/lldb/test/API/tools/lldb-dap/stackTrace/main.c
index 862473a3e6ac8c..25d81be08e7782 100644
--- a/lldb/test/API/tools/lldb-dap/stackTrace/main.c
+++ b/lldb/test/API/tools/lldb-dap/stackTrace/main.c
@@ -8,6 +8,6 @@ int recurse(int x) {
 }
 
 int main(int argc, char const *argv[]) {
-  recurse(20); // recurse invocation
+  recurse(40); // recurse invocation
   return 0;
 }

diff  --git a/lldb/test/API/tools/lldb-dap/stackTraceMissingFunctionName/TestDAP_stackTraceMissingFunctionName.py b/lldb/test/API/tools/lldb-dap/stackTraceMissingFunctionName/TestDAP_stackTraceMissingFunctionName.py
index a04c752764fbb2..f2131d6a821217 100644
--- a/lldb/test/API/tools/lldb-dap/stackTraceMissingFunctionName/TestDAP_stackTraceMissingFunctionName.py
+++ b/lldb/test/API/tools/lldb-dap/stackTraceMissingFunctionName/TestDAP_stackTraceMissingFunctionName.py
@@ -2,13 +2,8 @@
 Test lldb-dap stack trace response
 """
 
-
-import dap_server
 from lldbsuite.test.decorators import *
-import os
-
 import lldbdap_testcase
-from lldbsuite.test import lldbtest, lldbutil
 
 
 class TestDAP_stackTraceMissingFunctionName(lldbdap_testcase.DAPTestCaseBase):

diff  --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp
index 7338e7cf41eb03..342859adef214f 100644
--- a/lldb/tools/lldb-dap/JSONUtils.cpp
+++ b/lldb/tools/lldb-dap/JSONUtils.cpp
@@ -769,6 +769,28 @@ llvm::json::Value CreateStackFrame(lldb::SBFrame &frame) {
   return llvm::json::Value(std::move(object));
 }
 
+llvm::json::Value CreateExtendedStackFrameLabel(lldb::SBThread &thread) {
+  std::string name;
+  lldb::SBStream stream;
+  if (g_dap.thread_format &&
+      thread.GetDescriptionWithFormat(g_dap.thread_format, stream).Success()) {
+    name = stream.GetData();
+  } else {
+    const uint32_t thread_idx = thread.GetExtendedBacktraceOriginatingIndexID();
+    const char *queue_name = thread.GetQueueName();
+    if (queue_name != nullptr) {
+      name = llvm::formatv("Enqueued from {0} (Thread {1})", queue_name,
+                           thread_idx);
+    } else {
+      name = llvm::formatv("Thread {0}", thread_idx);
+    }
+  }
+
+  return llvm::json::Value(llvm::json::Object{{"id", thread.GetThreadID() + 1},
+                                              {"name", name},
+                                              {"presentationHint", "label"}});
+}
+
 // Response to `setInstructionBreakpoints` request.
 // "Breakpoint": {
 //   "type": "object",

diff  --git a/lldb/tools/lldb-dap/JSONUtils.h b/lldb/tools/lldb-dap/JSONUtils.h
index b6356630b72682..f8fec22d7aa0ea 100644
--- a/lldb/tools/lldb-dap/JSONUtils.h
+++ b/lldb/tools/lldb-dap/JSONUtils.h
@@ -322,6 +322,25 @@ llvm::json::Value CreateSource(llvm::StringRef source_path);
 ///     definition outlined by Microsoft.
 llvm::json::Value CreateStackFrame(lldb::SBFrame &frame);
 
+/// Create a "StackFrame" label object for a LLDB thread.
+///
+/// This function will fill in the following keys in the returned
+/// object:
+///   "id" - the thread ID as an integer
+///   "name" - the thread name as a string which combines the LLDB
+///            thread index ID along with the string name of the thread
+///            from the OS if it has a name.
+///   "presentationHint" - "label"
+///
+/// \param[in] thread
+///     The LLDB thread to use when populating out the "Thread"
+///     object.
+///
+/// \return
+///     A "StackFrame" JSON object with that follows the formal JSON
+///     definition outlined by Microsoft.
+llvm::json::Value CreateExtendedStackFrameLabel(lldb::SBThread &thread);
+
 /// Create a "instruction" object for a LLDB disassemble object as described in
 /// the Visual Studio Code debug adaptor definition.
 ///

diff  --git a/lldb/tools/lldb-dap/README.md b/lldb/tools/lldb-dap/README.md
index 11a14d29ab51e2..ddc2017e843dc2 100644
--- a/lldb/tools/lldb-dap/README.md
+++ b/lldb/tools/lldb-dap/README.md
@@ -36,6 +36,9 @@ file that defines how your program will be run. The JSON configuration file can
 |**terminateCommands** |[string]| | LLDB commands executed when the debugging session ends. Commands and command output will be sent to the debugger console when they are executed.
 |**sourceMap**      |[string[2]]| | Specify an array of path re-mappings. Each element in the array must be a two element array containing a source and destination pathname.
 |**debuggerRoot**   | string| |Specify a working directory to use when launching lldb-dap. If the debug information in your executable contains relative paths, this option can be used so that `lldb-dap` can find source files and object files that have relative paths.
+|**enableAutoVariableSummaries**|bool| | Enable auto generated summaries for variables when no summaries exist for a given type. This feature can cause performance delays in large projects when viewing variables.
+|**enableDisplayExtendedBacktrace**|bool| | Enable language specific extended backtraces.
+|**enableSyntheticChildDebugging**|bool| | If a variable is displayed using a synthetic children, also display the actual contents of the variable at the end under a [raw] entry. This is useful when creating sythetic child plug-ins as it lets you see the actual contents of the variable.
 
 ### Attaching Settings
 
@@ -62,6 +65,9 @@ The JSON configuration file can contain the following `lldb-dap` specific launch
 |**exitCommands**   |[string]| | LLDB commands executed when the program exits. Commands and command output will be sent to the debugger console when they are executed.
 |**terminateCommands** |[string]| | LLDB commands executed when the debugging session ends. Commands and command output will be sent to the debugger console when they are executed.
 |**attachCommands** |[string]| | LLDB commands that will be executed after **preRunCommands** which take place of the code that normally does the attach. The commands can create a new target and attach or launch it however desired. This allows custom launch and attach configurations. Core files can use `target create --core /path/to/core` to attach to core files.
+|**enableAutoVariableSummaries**|bool| | Enable auto generated summaries for variables when no summaries exist for a given type. This feature can cause performance delays in large projects when viewing variables.
+|**enableDisplayExtendedBacktrace**|bool| | Enable language specific extended backtraces.
+|**enableSyntheticChildDebugging**|bool| | If a variable is displayed using a synthetic children, also display the actual contents of the variable at the end under a [raw] entry. This is useful when creating sythetic child plug-ins as it lets you see the actual contents of the variable.
 
 ### Example configurations
 

diff  --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index c5c4b09f15622b..51765cd28df8a6 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -112,6 +112,9 @@ typedef void (*RequestCallback)(const llvm::json::Object &command);
 
 enum LaunchMethod { Launch, Attach, AttachForSuspendedLaunch };
 
+/// Page size used for reporting addtional frames in the 'stackTrace' request.
+constexpr int StackPageSize = 20;
+
 /// Prints a welcome message on the editor if the preprocessor variable
 /// LLDB_DAP_WELCOME_MESSAGE is defined.
 static void PrintWelcomeMessage() {
@@ -638,6 +641,79 @@ void SetSourceMapFromArguments(const llvm::json::Object &arguments) {
   }
 }
 
+// Fill in the stack frames of the thread.
+//
+// Threads stacks may contain runtime specific extended backtraces, when
+// constructing a stack trace first report the full thread stack trace then
+// perform a breadth first traversal of any extended backtrace frames.
+//
+// For example:
+//
+// Thread (id=th0) stack=[s0, s1, s2, s3]
+//   \ Extended backtrace "libdispatch" Thread (id=th1) stack=[s0, s1]
+//     \ Extended backtrace "libdispatch" Thread (id=th2) stack=[s0, s1]
+//   \ Extended backtrace "Application Specific Backtrace" Thread (id=th3)
+//   stack=[s0, s1, s2]
+//
+// Which will flatten into:
+//
+//  0. th0->s0
+//  1. th0->s1
+//  2. th0->s2
+//  3. th0->s3
+//  4. label - Enqueued from th1, sf=-1, i=-4
+//  5. th1->s0
+//  6. th1->s1
+//  7. label - Enqueued from th2
+//  8. th2->s0
+//  9. th2->s1
+// 10. label - Application Specific Backtrace
+// 11. th3->s0
+// 12. th3->s1
+// 13. th3->s2
+//
+// s=3,l=3 = [th0->s3, label1, th1->s0]
+bool FillStackFrames(lldb::SBThread &thread, llvm::json::Array &stack_frames,
+                     int64_t &offset, const int64_t start_frame,
+                     const int64_t levels) {
+  bool reached_end_of_stack = false;
+  for (int64_t i = start_frame;
+       static_cast<int64_t>(stack_frames.size()) < levels; i++) {
+    if (i == -1) {
+      stack_frames.emplace_back(CreateExtendedStackFrameLabel(thread));
+      continue;
+    }
+
+    lldb::SBFrame frame = thread.GetFrameAtIndex(i);
+    if (!frame.IsValid()) {
+      offset += thread.GetNumFrames() + 1 /* label between threads */;
+      reached_end_of_stack = true;
+      break;
+    }
+
+    stack_frames.emplace_back(CreateStackFrame(frame));
+  }
+
+  if (g_dap.enable_display_extended_backtrace && reached_end_of_stack) {
+    // Check for any extended backtraces.
+    for (uint32_t bt = 0;
+         bt < thread.GetProcess().GetNumExtendedBacktraceTypes(); bt++) {
+      lldb::SBThread backtrace = thread.GetExtendedBacktraceThread(
+          thread.GetProcess().GetExtendedBacktraceTypeAtIndex(bt));
+      if (!backtrace.IsValid())
+        continue;
+
+      reached_end_of_stack = FillStackFrames(
+          backtrace, stack_frames, offset,
+          (start_frame - offset) > 0 ? start_frame - offset : -1, levels);
+      if (static_cast<int64_t>(stack_frames.size()) >= levels)
+        break;
+    }
+  }
+
+  return reached_end_of_stack;
+}
+
 // "AttachRequest": {
 //   "allOf": [ { "$ref": "#/definitions/Request" }, {
 //     "type": "object",
@@ -1020,6 +1096,103 @@ void request_disconnect(const llvm::json::Object &request) {
   g_dap.disconnecting = true;
 }
 
+// "ExceptionInfoRequest": {
+//   "allOf": [ { "$ref": "#/definitions/Request" }, {
+//     "type": "object",
+//     "description": "Retrieves the details of the exception that
+//     caused this event to be raised. Clients should only call this request if
+//     the corresponding capability `supportsExceptionInfoRequest` is true.",
+//     "properties": {
+//       "command": {
+//         "type": "string",
+//         "enum": [ "exceptionInfo" ]
+//       },
+//       "arguments": {
+//         "$ref": "#/definitions/ExceptionInfoArguments"
+//       }
+//     },
+//     "required": [ "command", "arguments"  ]
+//   }]
+// },
+// "ExceptionInfoArguments": {
+//   "type": "object",
+//   "description": "Arguments for `exceptionInfo` request.",
+//   "properties": {
+//     "threadId": {
+//       "type": "integer",
+//       "description": "Thread for which exception information should be
+//       retrieved."
+//     }
+//   },
+//   "required": [ "threadId" ]
+// },
+// "ExceptionInfoResponse": {
+//   "allOf": [ { "$ref": "#/definitions/Response" }, {
+//     "type": "object",
+//     "description": "Response to `exceptionInfo` request.",
+//     "properties": {
+//       "body": {
+//         "type": "object",
+//         "properties": {
+//           "exceptionId": {
+//             "type": "string",
+//             "description": "ID of the exception that was thrown."
+//           },
+//           "description": {
+//             "type": "string",
+//             "description": "Descriptive text for the exception."
+//           },
+//           "breakMode": {
+//          "$ref": "#/definitions/ExceptionBreakMode",
+//            "description": "Mode that caused the exception notification to
+//            be raised."
+//           },
+//           "details": {
+//             "$ref": "#/definitions/ExceptionDetails",
+//            "description": "Detailed information about the exception."
+//           }
+//         },
+//         "required": [ "exceptionId", "breakMode" ]
+//       }
+//     },
+//     "required": [ "body" ]
+//   }]
+// }
+// "ExceptionDetails": {
+//   "type": "object",
+//   "description": "Detailed information about an exception that has
+//   occurred.", "properties": {
+//     "message": {
+//       "type": "string",
+//       "description": "Message contained in the exception."
+//     },
+//     "typeName": {
+//       "type": "string",
+//       "description": "Short type name of the exception object."
+//     },
+//     "fullTypeName": {
+//       "type": "string",
+//       "description": "Fully-qualified type name of the exception object."
+//     },
+//     "evaluateName": {
+//       "type": "string",
+//       "description": "An expression that can be evaluated in the current
+//       scope to obtain the exception object."
+//     },
+//     "stackTrace": {
+//       "type": "string",
+//       "description": "Stack trace at the time the exception was thrown."
+//     },
+//     "innerException": {
+//       "type": "array",
+//       "items": {
+//         "$ref": "#/definitions/ExceptionDetails"
+//       },
+//       "description": "Details of the exception contained by this exception,
+//       if any."
+//     }
+//   }
+// },
 void request_exceptionInfo(const llvm::json::Object &request) {
   llvm::json::Object response;
   FillResponse(request, response);
@@ -1048,6 +1221,27 @@ void request_exceptionInfo(const llvm::json::Object &request) {
       }
     }
     body.try_emplace("breakMode", "always");
+    auto exception = thread.GetCurrentException();
+    if (exception.IsValid()) {
+      llvm::json::Object details;
+      lldb::SBStream stream;
+      if (exception.GetDescription(stream)) {
+        EmplaceSafeString(details, "message", stream.GetData());
+      }
+
+      auto exceptionBacktrace = thread.GetCurrentExceptionBacktrace();
+      if (exceptionBacktrace.IsValid()) {
+        lldb::SBStream stream;
+        exceptionBacktrace.GetDescription(stream);
+        for (uint32_t i = 0; i < exceptionBacktrace.GetNumFrames(); i++) {
+          lldb::SBFrame frame = exceptionBacktrace.GetFrameAtIndex(i);
+          frame.GetDescription(stream);
+        }
+        EmplaceSafeString(details, "stackTrace", stream.GetData());
+      }
+
+      body.try_emplace("details", std::move(details));
+    }
     // auto excInfoCount = thread.GetStopReasonDataCount();
     // for (auto i=0; i<excInfoCount; ++i) {
     //   uint64_t exc_data = thread.GetStopReasonDataAtIndex(i);
@@ -3066,7 +3260,9 @@ void request_source(const llvm::json::Object &request) {
 //     },
 //     "format": {
 //       "$ref": "#/definitions/StackFrameFormat",
-//       "description": "Specifies details on how to format the stack frames."
+//       "description": "Specifies details on how to format the stack frames.
+//       The attribute is only honored by a debug adapter if the corresponding
+//       capability `supportsValueFormattingOptions` is true."
 //     }
 //  },
 //   "required": [ "threadId" ]
@@ -3074,7 +3270,7 @@ void request_source(const llvm::json::Object &request) {
 // "StackTraceResponse": {
 //   "allOf": [ { "$ref": "#/definitions/Response" }, {
 //     "type": "object",
-//     "description": "Response to 'stackTrace' request.",
+//     "description": "Response to `stackTrace` request.",
 //     "properties": {
 //       "body": {
 //         "type": "object",
@@ -3090,7 +3286,13 @@ void request_source(const llvm::json::Object &request) {
 //           },
 //           "totalFrames": {
 //             "type": "integer",
-//             "description": "The total number of frames available."
+//             "description": "The total number of frames available in the
+//             stack. If omitted or if `totalFrames` is larger than the
+//             available frames, a client is expected to request frames until
+//             a request returns less frames than requested (which indicates
+//             the end of the stack). Returning monotonically increasing
+//             `totalFrames` values for subsequent requests can be used to
+//             enforce paging in the client."
 //           }
 //         },
 //         "required": [ "stackFrames" ]
@@ -3105,85 +3307,22 @@ void request_stackTrace(const llvm::json::Object &request) {
   lldb::SBError error;
   auto arguments = request.getObject("arguments");
   lldb::SBThread thread = g_dap.GetLLDBThread(*arguments);
-  llvm::json::Array stackFrames;
+  llvm::json::Array stack_frames;
   llvm::json::Object body;
 
   if (thread.IsValid()) {
-    const auto startFrame = GetUnsigned(arguments, "startFrame", 0);
+    const auto start_frame = GetUnsigned(arguments, "startFrame", 0);
     const auto levels = GetUnsigned(arguments, "levels", 0);
-    const auto endFrame = (levels == 0) ? INT64_MAX : (startFrame + levels);
-    auto totalFrames = thread.GetNumFrames();
-
-    // This will always return an invalid thread when
-    // libBacktraceRecording.dylib is not loaded or if there is no extended
-    // backtrace.
-    lldb::SBThread queue_backtrace_thread;
-    if (g_dap.enable_display_extended_backtrace)
-      queue_backtrace_thread = thread.GetExtendedBacktraceThread("libdispatch");
-    if (queue_backtrace_thread.IsValid()) {
-      // One extra frame as a label to mark the enqueued thread.
-      totalFrames += queue_backtrace_thread.GetNumFrames() + 1;
-    }
-
-    // This will always return an invalid thread when there is no exception in
-    // the current thread.
-    lldb::SBThread exception_backtrace_thread;
-    if (g_dap.enable_display_extended_backtrace)
-      exception_backtrace_thread = thread.GetCurrentExceptionBacktrace();
-
-    if (exception_backtrace_thread.IsValid()) {
-      // One extra frame as a label to mark the exception thread.
-      totalFrames += exception_backtrace_thread.GetNumFrames() + 1;
-    }
-
-    for (uint32_t i = startFrame; i < endFrame; ++i) {
-      lldb::SBFrame frame;
-      std::string prefix;
-      if (i < thread.GetNumFrames()) {
-        frame = thread.GetFrameAtIndex(i);
-      } else if (queue_backtrace_thread.IsValid() &&
-                 i < (thread.GetNumFrames() +
-                      queue_backtrace_thread.GetNumFrames() + 1)) {
-        if (i == thread.GetNumFrames()) {
-          const uint32_t thread_idx =
-              queue_backtrace_thread.GetExtendedBacktraceOriginatingIndexID();
-          const char *queue_name = queue_backtrace_thread.GetQueueName();
-          auto name = llvm::formatv("Enqueued from {0} (Thread {1})",
-                                    queue_name, thread_idx);
-          stackFrames.emplace_back(
-              llvm::json::Object{{"id", thread.GetThreadID() + 1},
-                                 {"name", name},
-                                 {"presentationHint", "label"}});
-          continue;
-        }
-        frame = queue_backtrace_thread.GetFrameAtIndex(
-            i - thread.GetNumFrames() - 1);
-      } else if (exception_backtrace_thread.IsValid()) {
-        if (i == thread.GetNumFrames() +
-                     (queue_backtrace_thread.IsValid()
-                          ? queue_backtrace_thread.GetNumFrames() + 1
-                          : 0)) {
-          stackFrames.emplace_back(
-              llvm::json::Object{{"id", thread.GetThreadID() + 2},
-                                 {"name", "Original Exception Backtrace"},
-                                 {"presentationHint", "label"}});
-          continue;
-        }
-
-        frame = exception_backtrace_thread.GetFrameAtIndex(
-            i - thread.GetNumFrames() -
-            (queue_backtrace_thread.IsValid()
-                 ? queue_backtrace_thread.GetNumFrames() + 1
-                 : 0));
-      }
-      if (!frame.IsValid())
-        break;
-      stackFrames.emplace_back(CreateStackFrame(frame));
-    }
-
-    body.try_emplace("totalFrames", totalFrames);
+    int64_t offset = 0;
+    bool reached_end_of_stack =
+        FillStackFrames(thread, stack_frames, offset, start_frame,
+                        levels == 0 ? INT64_MAX : levels);
+    body.try_emplace("totalFrames",
+                     start_frame + stack_frames.size() +
+                         (reached_end_of_stack ? 0 : StackPageSize));
   }
-  body.try_emplace("stackFrames", std::move(stackFrames));
+
+  body.try_emplace("stackFrames", std::move(stack_frames));
   response.try_emplace("body", std::move(body));
   g_dap.SendJSON(llvm::json::Value(std::move(response)));
 }
@@ -3487,7 +3626,6 @@ void request_stepOut(const llvm::json::Object &request) {
 //   }]
 // }
 void request_threads(const llvm::json::Object &request) {
-
   lldb::SBProcess process = g_dap.target.GetProcess();
   llvm::json::Object response;
   FillResponse(request, response);

diff  --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json
index 6c7885fa81f7c4..d35accfb6ec4e8 100644
--- a/lldb/tools/lldb-dap/package.json
+++ b/lldb/tools/lldb-dap/package.json
@@ -237,7 +237,12 @@
               },
               "exitCommands": {
                 "type": "array",
-                "description": "Commands executed at the end of debugging session.",
+                "description": "Commands executed when the program exits.",
+                "default": []
+              },
+              "terminateCommands": {
+                "type": "array",
+                "description": "Commands executed when the debugging session ends.",
                 "default": []
               },
               "runInTerminal": {
@@ -254,6 +259,11 @@
                 "description": "Enable auto generated summaries for variables when no summaries exist for a given type. This feature can cause performance delays in large projects when viewing variables.",
                 "default": false
               },
+              "enableDisplayExtendedBacktrace": {
+                "type": "boolean",
+                "description": "Enable language specific extended backtraces.",
+                "default": false
+              },
               "enableSyntheticChildDebugging": {
                 "type": "boolean",
                 "description": "If a variable is displayed using a synthetic children, also display the actual contents of the variable at the end under a [raw] entry. This is useful when creating sythetic child plug-ins as it lets you see the actual contents of the variable.",
@@ -342,7 +352,12 @@
               },
               "exitCommands": {
                 "type": "array",
-                "description": "Commands executed at the end of debugging session.",
+                "description": "Commands executed when the program exits.",
+                "default": []
+              },
+              "terminateCommands": {
+                "type": "array",
+                "description": "Commands executed when the debugging session ends.",
                 "default": []
               },
               "coreFile": {
@@ -369,6 +384,11 @@
                 "description": "Enable auto generated summaries for variables when no summaries exist for a given type. This feature can cause performance delays in large projects when viewing variables.",
                 "default": false
               },
+              "enableDisplayExtendedBacktrace": {
+                "type": "boolean",
+                "description": "Enable language specific extended backtraces.",
+                "default": false
+              },
               "enableSyntheticChildDebugging": {
                 "type": "boolean",
                 "description": "If a variable is displayed using a synthetic children, also display the actual contents of the variable at the end under a [raw] entry. This is useful when creating sythetic child plug-ins as it lets you see the actual contents of the variable.",


        


More information about the lldb-commits mailing list