[Lldb-commits] [lldb] [lldb-dap] Improve `stackTrace` and `exceptionInfo` DAP request handlers (PR #105905)
John Harrison via lldb-commits
lldb-commits at lists.llvm.org
Tue Sep 10 09:53:52 PDT 2024
https://github.com/ashgti updated https://github.com/llvm/llvm-project/pull/105905
>From 6612efb0e51d700eeb545a421a34f7a57aafc509 Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Fri, 23 Aug 2024 16:04:44 -0700
Subject: [PATCH 1/8] [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.
---
.../Python/lldbsuite/test/lldbplatformutil.py | 16 ++
.../test/tools/lldb-dap/dap_server.py | 11 +
.../test/tools/lldb-dap/lldbdap_testcase.py | 9 +-
.../API/tools/lldb-dap/exception/Makefile | 2 +-
.../lldb-dap/exception/TestDAP_exception.py | 8 +-
.../API/tools/lldb-dap/exception/cpp/Makefile | 3 +
.../exception/cpp/TestDAP_exception_cpp.py | 26 ++
.../API/tools/lldb-dap/exception/cpp/main.cpp | 6 +
.../lldb-dap/exception/{main.cpp => main.c} | 2 +-
.../tools/lldb-dap/exception/objc/Makefile | 9 +
.../exception/objc/TestDAP_exception_objc.py | 27 ++
.../API/tools/lldb-dap/exception/objc/main.m | 8 +
.../lldb-dap/extendedStackTrace/Makefile | 5 +
.../TestDAP_extendedStackTrace.py | 69 +++++
.../tools/lldb-dap/extendedStackTrace/main.m | 28 ++
.../lldb-dap/stackTrace/TestDAP_stackTrace.py | 4 +-
.../TestDAP_stackTraceMissingFunctionName.py | 5 -
lldb/tools/lldb-dap/DAP.cpp | 1 -
lldb/tools/lldb-dap/DAP.h | 2 +-
lldb/tools/lldb-dap/JSONUtils.cpp | 40 +++
lldb/tools/lldb-dap/JSONUtils.h | 3 +
lldb/tools/lldb-dap/lldb-dap.cpp | 253 +++++++++++++-----
22 files changed, 449 insertions(+), 88 deletions(-)
create mode 100644 lldb/test/API/tools/lldb-dap/exception/cpp/Makefile
create mode 100644 lldb/test/API/tools/lldb-dap/exception/cpp/TestDAP_exception_cpp.py
create mode 100644 lldb/test/API/tools/lldb-dap/exception/cpp/main.cpp
rename lldb/test/API/tools/lldb-dap/exception/{main.cpp => main.c} (56%)
create mode 100644 lldb/test/API/tools/lldb-dap/exception/objc/Makefile
create mode 100644 lldb/test/API/tools/lldb-dap/exception/objc/TestDAP_exception_objc.py
create mode 100644 lldb/test/API/tools/lldb-dap/exception/objc/main.m
create mode 100644 lldb/test/API/tools/lldb-dap/extendedStackTrace/Makefile
create mode 100644 lldb/test/API/tools/lldb-dap/extendedStackTrace/TestDAP_extendedStackTrace.py
create mode 100644 lldb/test/API/tools/lldb-dap/extendedStackTrace/main.m
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..59d0f08bec9a24 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",
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..1f20c03922a2a5 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
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..efc907085b2c21
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/extendedStackTrace/TestDAP_extendedStackTrace.py
@@ -0,0 +1,69 @@
+"""
+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,
+ ],
+ )
+ 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()
+ print("huh", events)
+ stackFrames = self.get_stackFrames(threadId=events[0]["body"]["threadId"])
+ self.assertGreaterEqual(len(stackFrames), 3, "expect >= 3 frames")
+ self.assertEqual(stackFrames[0]["name"], "one")
+ self.assertEqual(stackFrames[1]["name"], "two")
+ self.assertEqual(stackFrames[2]["name"], "three")
+
+ stackLabels = [
+ frame
+ for frame in stackFrames
+ if frame.get("presentationHint", "") == "label"
+ ]
+ self.assertEqual(len(stackLabels), 2, "expected two label stack frames")
+ self.assertRegex(
+ stackLabels[0]["name"],
+ "Enqueued from com.apple.root.default-qos \(Thread \d\)",
+ )
+ self.assertRegex(
+ stackLabels[1]["name"], "Enqueued from com.apple.main-thread \(Thread \d\)"
+ )
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..fad5419140f454 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 *
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/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index 6012ee52110b73..a4ae0638308028 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -36,7 +36,6 @@ DAP::DAP()
focus_tid(LLDB_INVALID_THREAD_ID), stop_at_entry(false), is_attach(false),
enable_auto_variable_summaries(false),
enable_synthetic_child_debugging(false),
- enable_display_extended_backtrace(false),
restarting_process_id(LLDB_INVALID_PROCESS_ID),
configuration_done_sent(false), waiting_for_run_in_terminal(false),
progress_event_reporter(
diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h
index f4fdec6e895ad1..0e61231751b98b 100644
--- a/lldb/tools/lldb-dap/DAP.h
+++ b/lldb/tools/lldb-dap/DAP.h
@@ -185,7 +185,6 @@ struct DAP {
bool is_attach;
bool enable_auto_variable_summaries;
bool enable_synthetic_child_debugging;
- bool enable_display_extended_backtrace;
// The process event thread normally responds to process exited events by
// shutting down the entire adapter. When we're restarting, we keep the id of
// the old process here so we can detect this case and keep running.
@@ -197,6 +196,7 @@ struct DAP {
// Keep track of the last stop thread index IDs as threads won't go away
// unless we send a "thread" event to indicate the thread exited.
llvm::DenseSet<lldb::tid_t> thread_ids;
+ std::map<lldb::tid_t, uint32_t> thread_stack_size_cache;
uint32_t reverse_request_seq;
std::mutex call_mutex;
std::map<int /* request_seq */, ResponseCallback /* reply handler */>
diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp
index 7338e7cf41eb03..720cb67e41ed92 100644
--- a/lldb/tools/lldb-dap/JSONUtils.cpp
+++ b/lldb/tools/lldb-dap/JSONUtils.cpp
@@ -1330,6 +1330,46 @@ llvm::json::Value CreateVariable(lldb::SBValue v, int64_t variablesReference,
return llvm::json::Value(std::move(object));
}
+// "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."
+// }
+// }
+// },
+llvm::json::Value CreateExceptionDetails() {
+ llvm::json::Object object;
+ return llvm::json::Value(std::move(object));
+}
+
llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit unit) {
llvm::json::Object object;
char unit_path_arr[PATH_MAX];
diff --git a/lldb/tools/lldb-dap/JSONUtils.h b/lldb/tools/lldb-dap/JSONUtils.h
index b6356630b72682..ab699d77d50318 100644
--- a/lldb/tools/lldb-dap/JSONUtils.h
+++ b/lldb/tools/lldb-dap/JSONUtils.h
@@ -478,6 +478,9 @@ llvm::json::Value CreateVariable(lldb::SBValue v, int64_t variablesReference,
bool is_name_duplicated = false,
std::optional<std::string> custom_name = {});
+/// Create a "ExceptionDetail" object for a LLDB
+llvm::json::Value CreateExceptionDetails();
+
llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit unit);
/// Create a runInTerminal reverse request object
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index c5c4b09f15622b..fbc9c282a41294 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -701,8 +701,6 @@ void request_attach(const llvm::json::Object &request) {
GetBoolean(arguments, "enableAutoVariableSummaries", false);
g_dap.enable_synthetic_child_debugging =
GetBoolean(arguments, "enableSyntheticChildDebugging", false);
- g_dap.enable_display_extended_backtrace =
- GetBoolean(arguments, "enableDisplayExtendedBacktrace", false);
g_dap.command_escape_prefix =
GetString(arguments, "commandEscapePrefix", "`");
g_dap.SetFrameFormat(GetString(arguments, "customFrameFormat"));
@@ -1020,6 +1018,68 @@ 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" ]
+// }]
+// }
void request_exceptionInfo(const llvm::json::Object &request) {
llvm::json::Object response;
FillResponse(request, response);
@@ -1048,6 +1108,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);
@@ -1929,8 +2010,6 @@ void request_launch(const llvm::json::Object &request) {
GetBoolean(arguments, "enableAutoVariableSummaries", false);
g_dap.enable_synthetic_child_debugging =
GetBoolean(arguments, "enableSyntheticChildDebugging", false);
- g_dap.enable_display_extended_backtrace =
- GetBoolean(arguments, "enableDisplayExtendedBacktrace", false);
g_dap.command_escape_prefix =
GetString(arguments, "commandEscapePrefix", "`");
g_dap.SetFrameFormat(GetString(arguments, "customFrameFormat"));
@@ -3066,7 +3145,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 +3155,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 +3171,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" ]
@@ -3108,80 +3195,105 @@ void request_stackTrace(const llvm::json::Object &request) {
llvm::json::Array stackFrames;
llvm::json::Object body;
+ // 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
+ // 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
+
if (thread.IsValid()) {
const auto startFrame = 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;
- }
+ bool done = false;
+ int64_t offset = 0;
+ lldb::SBProcess process = thread.GetProcess();
+ llvm::SmallVector<lldb::SBThread> threadCluster{{thread}};
+
+ for (uint32_t i = startFrame; i < endFrame && !threadCluster.empty(); ++i) {
+ lldb::SBThread current = threadCluster.front();
+ lldb::SBFrame frame = current.GetFrameAtIndex(i - offset);
+
+ // If we don't have a valid frame, check if we have any extended frames to
+ // report.
+ // *NOTE*: Threads can be chained across mutliple backtraces, so we
+ // need to keep track of each backtrace we've traversed fully in the
+ // offset.
+ while (!frame.IsValid() && current.IsValid() && !threadCluster.empty()) {
+ offset += current.GetNumFrames() +
+ 1 /* one extra frame for a label between threads*/;
+ threadCluster.pop_back();
+
+ // Check for any extended backtraces.
+ for (uint32_t i = 0; i < process.GetNumExtendedBacktraceTypes(); i++) {
+ lldb::SBThread backtrace = current.GetExtendedBacktraceThread(
+ process.GetExtendedBacktraceTypeAtIndex(i));
+ if (backtrace.IsValid()) {
+ threadCluster.emplace_back(backtrace);
+ }
+ }
- // 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 (threadCluster.empty())
+ break;
- if (exception_backtrace_thread.IsValid()) {
- // One extra frame as a label to mark the exception thread.
- totalFrames += exception_backtrace_thread.GetNumFrames() + 1;
- }
+ current = threadCluster.front();
+ frame = current.GetFrameAtIndex(0);
+ }
- 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;
- }
+ // If we're out of extended backtraces, no more frames to load.
+ if (!frame.IsValid() && threadCluster.empty()) {
+ done = true;
+ break;
+ }
- frame = exception_backtrace_thread.GetFrameAtIndex(
- i - thread.GetNumFrames() -
- (queue_backtrace_thread.IsValid()
- ? queue_backtrace_thread.GetNumFrames() + 1
- : 0));
+ // Between the thread and extended backtrace add a label.
+ if (offset != 0 && (i - offset) == 0) {
+ const uint32_t thread_idx =
+ current.GetExtendedBacktraceOriginatingIndexID();
+ const char *queue_name = current.GetQueueName();
+ std::string name;
+ if (queue_name != nullptr) {
+ name = llvm::formatv("Enqueued from {0} (Thread {1})", queue_name,
+ thread_idx);
+ } else {
+ name = llvm::formatv("Thread {0}", thread_idx);
+ }
+ stackFrames.emplace_back(
+ llvm::json::Object{{"id", thread.GetThreadID() + 1},
+ {"name", name},
+ {"presentationHint", "label"}});
+ } else {
+ stackFrames.emplace_back(CreateStackFrame(frame));
}
- if (!frame.IsValid())
- break;
- stackFrames.emplace_back(CreateStackFrame(frame));
}
- body.try_emplace("totalFrames", totalFrames);
+ // If we loaded all the frames, set the total frame to the current total,
+ // otherwise use the totalFrames to indiciate more data is available.
+ body.try_emplace("totalFrames",
+ startFrame + stackFrames.size() + (done ? 0 : 1));
}
body.try_emplace("stackFrames", std::move(stackFrames));
response.try_emplace("body", std::move(body));
@@ -3487,7 +3599,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);
>From 6f37c898dd18043a28277159f08788d6fce90d72 Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Fri, 23 Aug 2024 16:23:30 -0700
Subject: [PATCH 2/8] Move the ExceptionDetails type info closer to
request_exceptionInfo
---
lldb/tools/lldb-dap/JSONUtils.cpp | 40 -------------------------------
lldb/tools/lldb-dap/JSONUtils.h | 3 ---
lldb/tools/lldb-dap/lldb-dap.cpp | 35 +++++++++++++++++++++++++++
3 files changed, 35 insertions(+), 43 deletions(-)
diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp
index 720cb67e41ed92..7338e7cf41eb03 100644
--- a/lldb/tools/lldb-dap/JSONUtils.cpp
+++ b/lldb/tools/lldb-dap/JSONUtils.cpp
@@ -1330,46 +1330,6 @@ llvm::json::Value CreateVariable(lldb::SBValue v, int64_t variablesReference,
return llvm::json::Value(std::move(object));
}
-// "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."
-// }
-// }
-// },
-llvm::json::Value CreateExceptionDetails() {
- llvm::json::Object object;
- return llvm::json::Value(std::move(object));
-}
-
llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit unit) {
llvm::json::Object object;
char unit_path_arr[PATH_MAX];
diff --git a/lldb/tools/lldb-dap/JSONUtils.h b/lldb/tools/lldb-dap/JSONUtils.h
index ab699d77d50318..b6356630b72682 100644
--- a/lldb/tools/lldb-dap/JSONUtils.h
+++ b/lldb/tools/lldb-dap/JSONUtils.h
@@ -478,9 +478,6 @@ llvm::json::Value CreateVariable(lldb::SBValue v, int64_t variablesReference,
bool is_name_duplicated = false,
std::optional<std::string> custom_name = {});
-/// Create a "ExceptionDetail" object for a LLDB
-llvm::json::Value CreateExceptionDetails();
-
llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit unit);
/// Create a runInTerminal reverse request object
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index fbc9c282a41294..11214421be2a62 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -1080,6 +1080,41 @@ void request_disconnect(const llvm::json::Object &request) {
// "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);
>From c04f7e6d32db1183d1615a42f9ea742411cf913c Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Fri, 23 Aug 2024 16:25:26 -0700
Subject: [PATCH 3/8] Cleaning up unused variables.
---
lldb/tools/lldb-dap/DAP.h | 1 -
1 file changed, 1 deletion(-)
diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h
index 0e61231751b98b..6e71240237a210 100644
--- a/lldb/tools/lldb-dap/DAP.h
+++ b/lldb/tools/lldb-dap/DAP.h
@@ -196,7 +196,6 @@ struct DAP {
// Keep track of the last stop thread index IDs as threads won't go away
// unless we send a "thread" event to indicate the thread exited.
llvm::DenseSet<lldb::tid_t> thread_ids;
- std::map<lldb::tid_t, uint32_t> thread_stack_size_cache;
uint32_t reverse_request_seq;
std::mutex call_mutex;
std::map<int /* request_seq */, ResponseCallback /* reply handler */>
>From 86dc497e92008f59ab37826deb3ad5d541d57c3d Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Fri, 23 Aug 2024 16:35:02 -0700
Subject: [PATCH 4/8] Cleanup debug logs
---
.../lldb-dap/extendedStackTrace/TestDAP_extendedStackTrace.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lldb/test/API/tools/lldb-dap/extendedStackTrace/TestDAP_extendedStackTrace.py b/lldb/test/API/tools/lldb-dap/extendedStackTrace/TestDAP_extendedStackTrace.py
index efc907085b2c21..ec62151c318acc 100644
--- a/lldb/test/API/tools/lldb-dap/extendedStackTrace/TestDAP_extendedStackTrace.py
+++ b/lldb/test/API/tools/lldb-dap/extendedStackTrace/TestDAP_extendedStackTrace.py
@@ -47,7 +47,7 @@ def test_stackTrace(self):
)
events = self.continue_to_next_stop()
- print("huh", events)
+
stackFrames = self.get_stackFrames(threadId=events[0]["body"]["threadId"])
self.assertGreaterEqual(len(stackFrames), 3, "expect >= 3 frames")
self.assertEqual(stackFrames[0]["name"], "one")
>From a89a45ab28d8f4268e639c8d9dc4250343fa2c14 Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Wed, 28 Aug 2024 14:25:57 -0700
Subject: [PATCH 5/8] Adjusting the lldb-dap stackTrace to use a
StackPageSize=20 const instead of only a +1.
This helps improve pagination of deep stack traces.
---
.../lldb-dap/stackTrace/TestDAP_stackTrace.py | 19 +++++++++++++++----
.../test/API/tools/lldb-dap/stackTrace/main.c | 2 +-
lldb/tools/lldb-dap/lldb-dap.cpp | 7 +++++--
3 files changed, 21 insertions(+), 7 deletions(-)
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 fad5419140f454..da9332eea3e0e0 100644
--- a/lldb/test/API/tools/lldb-dap/stackTrace/TestDAP_stackTrace.py
+++ b/lldb/test/API/tools/lldb-dap/stackTrace/TestDAP_stackTrace.py
@@ -15,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
@@ -31,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:
@@ -81,10 +84,18 @@ 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/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index 11214421be2a62..135a8246767e26 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() {
@@ -3326,9 +3329,9 @@ void request_stackTrace(const llvm::json::Object &request) {
}
// If we loaded all the frames, set the total frame to the current total,
- // otherwise use the totalFrames to indiciate more data is available.
+ // otherwise use the totalFrames to indicate more data is available.
body.try_emplace("totalFrames",
- startFrame + stackFrames.size() + (done ? 0 : 1));
+ startFrame + stackFrames.size() + (done ? 0 : StackPageSize));
}
body.try_emplace("stackFrames", std::move(stackFrames));
response.try_emplace("body", std::move(body));
>From 41ede6253855148b1a94b3f84438c0a2aac7fdff Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Wed, 28 Aug 2024 14:37:32 -0700
Subject: [PATCH 6/8] Applying clang-format and python format.
---
.../tools/lldb-dap/stackTrace/TestDAP_stackTrace.py | 12 +++++++++---
lldb/tools/lldb-dap/lldb-dap.cpp | 4 ++--
2 files changed, 11 insertions(+), 5 deletions(-)
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 da9332eea3e0e0..56ed1ebdf7ab44 100644
--- a/lldb/test/API/tools/lldb-dap/stackTrace/TestDAP_stackTrace.py
+++ b/lldb/test/API/tools/lldb-dap/stackTrace/TestDAP_stackTrace.py
@@ -87,15 +87,21 @@ def test_stackTrace(self):
frameCount, 40, "verify we get at least 40 frames for all frames"
)
self.assertEqual(
- totalFrames, frameCount, "verify total frames returns a speculative page size"
+ 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)
+ (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, len(stackFrames) + self.page_size, "verify total frames returns a speculative page size"
+ totalFrames,
+ len(stackFrames) + self.page_size,
+ "verify total frames returns a speculative page size",
)
self.verify_stackFrames(startFrame, stackFrames)
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index 135a8246767e26..61cf76c8293b66 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -3330,8 +3330,8 @@ void request_stackTrace(const llvm::json::Object &request) {
// If we loaded all the frames, set the total frame to the current total,
// otherwise use the totalFrames to indicate more data is available.
- body.try_emplace("totalFrames",
- startFrame + stackFrames.size() + (done ? 0 : StackPageSize));
+ body.try_emplace("totalFrames", startFrame + stackFrames.size() +
+ (done ? 0 : StackPageSize));
}
body.try_emplace("stackFrames", std::move(stackFrames));
response.try_emplace("body", std::move(body));
>From 5d49b8e8c7502355f602abcf1e503a105d08df6e Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Thu, 29 Aug 2024 14:03:27 -0700
Subject: [PATCH 7/8] Only display the extended stack trace if
`enableDisplayExtendedBacktrace` is true and document
`enableDisplayExtendedBacktrace`.
---
.../test/tools/lldb-dap/dap_server.py | 2 ++
.../test/tools/lldb-dap/lldbdap_testcase.py | 4 ++++
.../TestDAP_extendedStackTrace.py | 1 +
lldb/tools/lldb-dap/DAP.cpp | 1 +
lldb/tools/lldb-dap/DAP.h | 1 +
lldb/tools/lldb-dap/README.md | 6 +++++
lldb/tools/lldb-dap/lldb-dap.cpp | 8 +++++++
lldb/tools/lldb-dap/package.json | 24 +++++++++++++++++--
8 files changed, 45 insertions(+), 2 deletions(-)
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 59d0f08bec9a24..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
@@ -765,6 +765,7 @@ def request_launch(
runInTerminal=False,
postRunCommands=None,
enableAutoVariableSummaries=False,
+ enableDisplayExtendedBacktrace=False,
enableSyntheticChildDebugging=False,
commandEscapePrefix=None,
customFrameFormat=None,
@@ -817,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 1f20c03922a2a5..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
@@ -386,6 +386,7 @@ def launch(
expectFailure=False,
postRunCommands=None,
enableAutoVariableSummaries=False,
+ enableDisplayExtendedBacktrace=False,
enableSyntheticChildDebugging=False,
commandEscapePrefix=None,
customFrameFormat=None,
@@ -427,6 +428,7 @@ def cleanup():
runInTerminal=runInTerminal,
postRunCommands=postRunCommands,
enableAutoVariableSummaries=enableAutoVariableSummaries,
+ enableDisplayExtendedBacktrace=enableDisplayExtendedBacktrace,
enableSyntheticChildDebugging=enableSyntheticChildDebugging,
commandEscapePrefix=commandEscapePrefix,
customFrameFormat=customFrameFormat,
@@ -466,6 +468,7 @@ def build_and_launch(
postRunCommands=None,
lldbDAPEnv=None,
enableAutoVariableSummaries=False,
+ enableDisplayExtendedBacktrace=False,
enableSyntheticChildDebugging=False,
commandEscapePrefix=None,
customFrameFormat=None,
@@ -502,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/extendedStackTrace/TestDAP_extendedStackTrace.py b/lldb/test/API/tools/lldb-dap/extendedStackTrace/TestDAP_extendedStackTrace.py
index ec62151c318acc..b78b046c264fad 100644
--- a/lldb/test/API/tools/lldb-dap/extendedStackTrace/TestDAP_extendedStackTrace.py
+++ b/lldb/test/API/tools/lldb-dap/extendedStackTrace/TestDAP_extendedStackTrace.py
@@ -36,6 +36,7 @@ def test_stackTrace(self):
"DYLD_LIBRARY_PATH=/usr/lib/system/introspection",
"DYLD_INSERT_LIBRARIES=" + backtrace_recording_lib,
],
+ enableDisplayExtendedBacktrace=True,
)
source = "main.m"
breakpoint = line_number(source, "breakpoint 1")
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index a4ae0638308028..6012ee52110b73 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -36,6 +36,7 @@ DAP::DAP()
focus_tid(LLDB_INVALID_THREAD_ID), stop_at_entry(false), is_attach(false),
enable_auto_variable_summaries(false),
enable_synthetic_child_debugging(false),
+ enable_display_extended_backtrace(false),
restarting_process_id(LLDB_INVALID_PROCESS_ID),
configuration_done_sent(false), waiting_for_run_in_terminal(false),
progress_event_reporter(
diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h
index 6e71240237a210..f4fdec6e895ad1 100644
--- a/lldb/tools/lldb-dap/DAP.h
+++ b/lldb/tools/lldb-dap/DAP.h
@@ -185,6 +185,7 @@ struct DAP {
bool is_attach;
bool enable_auto_variable_summaries;
bool enable_synthetic_child_debugging;
+ bool enable_display_extended_backtrace;
// The process event thread normally responds to process exited events by
// shutting down the entire adapter. When we're restarting, we keep the id of
// the old process here so we can detect this case and keep running.
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 61cf76c8293b66..938017ecc53bf8 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -704,6 +704,8 @@ void request_attach(const llvm::json::Object &request) {
GetBoolean(arguments, "enableAutoVariableSummaries", false);
g_dap.enable_synthetic_child_debugging =
GetBoolean(arguments, "enableSyntheticChildDebugging", false);
+ g_dap.enable_display_extended_backtrace =
+ GetBoolean(arguments, "enableDisplayExtendedBacktrace", false);
g_dap.command_escape_prefix =
GetString(arguments, "commandEscapePrefix", "`");
g_dap.SetFrameFormat(GetString(arguments, "customFrameFormat"));
@@ -2048,6 +2050,8 @@ void request_launch(const llvm::json::Object &request) {
GetBoolean(arguments, "enableAutoVariableSummaries", false);
g_dap.enable_synthetic_child_debugging =
GetBoolean(arguments, "enableSyntheticChildDebugging", false);
+ g_dap.enable_display_extended_backtrace =
+ GetBoolean(arguments, "enableDisplayExtendedBacktrace", false);
g_dap.command_escape_prefix =
GetString(arguments, "commandEscapePrefix", "`");
g_dap.SetFrameFormat(GetString(arguments, "customFrameFormat"));
@@ -3285,6 +3289,10 @@ void request_stackTrace(const llvm::json::Object &request) {
1 /* one extra frame for a label between threads*/;
threadCluster.pop_back();
+ if (!g_dap.enable_display_extended_backtrace) {
+ break;
+ }
+
// Check for any extended backtraces.
for (uint32_t i = 0; i < process.GetNumExtendedBacktraceTypes(); i++) {
lldb::SBThread backtrace = current.GetExtendedBacktraceThread(
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.",
>From ecfd5b16ba7dce86ccb700f6559980d0ce61f56b Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Mon, 9 Sep 2024 13:46:23 -0700
Subject: [PATCH 8/8] Restructuring the stacktrace generation code to use more
helpers and to simplify the implementation.
---
.../TestDAP_extendedStackTrace.py | 44 ++++-
lldb/tools/lldb-dap/JSONUtils.cpp | 24 +++
lldb/tools/lldb-dap/JSONUtils.h | 19 ++
lldb/tools/lldb-dap/lldb-dap.cpp | 185 ++++++++----------
4 files changed, 165 insertions(+), 107 deletions(-)
diff --git a/lldb/test/API/tools/lldb-dap/extendedStackTrace/TestDAP_extendedStackTrace.py b/lldb/test/API/tools/lldb-dap/extendedStackTrace/TestDAP_extendedStackTrace.py
index b78b046c264fad..0cc8534daf4e94 100644
--- a/lldb/test/API/tools/lldb-dap/extendedStackTrace/TestDAP_extendedStackTrace.py
+++ b/lldb/test/API/tools/lldb-dap/extendedStackTrace/TestDAP_extendedStackTrace.py
@@ -49,22 +49,56 @@ def test_stackTrace(self):
events = self.continue_to_next_stop()
- stackFrames = self.get_stackFrames(threadId=events[0]["body"]["threadId"])
+ 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 = [
- frame
- for frame in stackFrames
+ (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]["name"],
+ stackLabels[0][1]["name"],
"Enqueued from com.apple.root.default-qos \(Thread \d\)",
)
self.assertRegex(
- stackLabels[1]["name"], "Enqueued from com.apple.main-thread \(Thread \d\)"
+ 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/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp
index 7338e7cf41eb03..b35013fa105912 100644
--- a/lldb/tools/lldb-dap/JSONUtils.cpp
+++ b/lldb/tools/lldb-dap/JSONUtils.cpp
@@ -769,6 +769,30 @@ 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/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index 938017ecc53bf8..51765cd28df8a6 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -641,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",
@@ -3234,114 +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;
- // 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
- // 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
-
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);
- bool done = false;
int64_t offset = 0;
- lldb::SBProcess process = thread.GetProcess();
- llvm::SmallVector<lldb::SBThread> threadCluster{{thread}};
-
- for (uint32_t i = startFrame; i < endFrame && !threadCluster.empty(); ++i) {
- lldb::SBThread current = threadCluster.front();
- lldb::SBFrame frame = current.GetFrameAtIndex(i - offset);
-
- // If we don't have a valid frame, check if we have any extended frames to
- // report.
- // *NOTE*: Threads can be chained across mutliple backtraces, so we
- // need to keep track of each backtrace we've traversed fully in the
- // offset.
- while (!frame.IsValid() && current.IsValid() && !threadCluster.empty()) {
- offset += current.GetNumFrames() +
- 1 /* one extra frame for a label between threads*/;
- threadCluster.pop_back();
-
- if (!g_dap.enable_display_extended_backtrace) {
- break;
- }
-
- // Check for any extended backtraces.
- for (uint32_t i = 0; i < process.GetNumExtendedBacktraceTypes(); i++) {
- lldb::SBThread backtrace = current.GetExtendedBacktraceThread(
- process.GetExtendedBacktraceTypeAtIndex(i));
- if (backtrace.IsValid()) {
- threadCluster.emplace_back(backtrace);
- }
- }
-
- if (threadCluster.empty())
- break;
-
- current = threadCluster.front();
- frame = current.GetFrameAtIndex(0);
- }
-
- // If we're out of extended backtraces, no more frames to load.
- if (!frame.IsValid() && threadCluster.empty()) {
- done = true;
- break;
- }
-
- // Between the thread and extended backtrace add a label.
- if (offset != 0 && (i - offset) == 0) {
- const uint32_t thread_idx =
- current.GetExtendedBacktraceOriginatingIndexID();
- const char *queue_name = current.GetQueueName();
- std::string name;
- if (queue_name != nullptr) {
- name = llvm::formatv("Enqueued from {0} (Thread {1})", queue_name,
- thread_idx);
- } else {
- name = llvm::formatv("Thread {0}", thread_idx);
- }
- stackFrames.emplace_back(
- llvm::json::Object{{"id", thread.GetThreadID() + 1},
- {"name", name},
- {"presentationHint", "label"}});
- } else {
- stackFrames.emplace_back(CreateStackFrame(frame));
- }
- }
-
- // If we loaded all the frames, set the total frame to the current total,
- // otherwise use the totalFrames to indicate more data is available.
- body.try_emplace("totalFrames", startFrame + stackFrames.size() +
- (done ? 0 : StackPageSize));
+ 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)));
}
More information about the lldb-commits
mailing list