https://github.com/ashgti updated https://github.com/llvm/llvm-project/pull/105905
>From ebcf4842c7d6c5ac0da4976c9cffb68bc3b22807 Mon Sep 17 00:00:00 2001 From: John Harrison <harj...@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 874383a13e2bb6..167142779cf12c 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 86eba355da83db..40d61636380588 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 @@ -100,13 +100,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: @@ -177,6 +178,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 57b93c28ce9301..1fd560f21904ab 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 0fc77ac1e81683..c621123f18097f 100644 --- a/lldb/tools/lldb-dap/DAP.h +++ b/lldb/tools/lldb-dap/DAP.h @@ -181,7 +181,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. @@ -193,6 +192,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 c080fd395b7288..9ea3180a7d1b67 100644 --- a/lldb/tools/lldb-dap/JSONUtils.cpp +++ b/lldb/tools/lldb-dap/JSONUtils.cpp @@ -1260,6 +1260,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 1515f5ba2e5f4d..88df05756a0eaa 100644 --- a/lldb/tools/lldb-dap/JSONUtils.h +++ b/lldb/tools/lldb-dap/JSONUtils.h @@ -467,6 +467,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 495ed0256120e8..aa34c275fad589 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); @@ -1927,8 +2008,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")); @@ -3064,7 +3143,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" ] @@ -3072,7 +3153,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", @@ -3088,7 +3169,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" ] @@ -3106,80 +3193,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)); @@ -3485,7 +3597,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 f7e658f0e7960be99279e42fd23a7e4d76151eb9 Mon Sep 17 00:00:00 2001 From: John Harrison <harj...@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 9ea3180a7d1b67..c080fd395b7288 100644 --- a/lldb/tools/lldb-dap/JSONUtils.cpp +++ b/lldb/tools/lldb-dap/JSONUtils.cpp @@ -1260,46 +1260,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 88df05756a0eaa..1515f5ba2e5f4d 100644 --- a/lldb/tools/lldb-dap/JSONUtils.h +++ b/lldb/tools/lldb-dap/JSONUtils.h @@ -467,9 +467,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 aa34c275fad589..f8e22f1a791802 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 aacdd80d6e5f2a109e4de0f64bf3eab6edad6896 Mon Sep 17 00:00:00 2001 From: John Harrison <harj...@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 c621123f18097f..27ea6c7ff8423f 100644 --- a/lldb/tools/lldb-dap/DAP.h +++ b/lldb/tools/lldb-dap/DAP.h @@ -192,7 +192,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 029a174b20ab538a04802d942b0bce1b2818c376 Mon Sep 17 00:00:00 2001 From: John Harrison <harj...@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 7960402faaeabeba610cc56cba04638e355563c8 Mon Sep 17 00:00:00 2001 From: John Harrison <harj...@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 f8e22f1a791802..b5ac9b32361ab8 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() { @@ -3324,9 +3327,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 52c133ce60797358afd589fad8dfbb678f5c5c74 Mon Sep 17 00:00:00 2001 From: John Harrison <harj...@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 b5ac9b32361ab8..9ba82008aa8294 100644 --- a/lldb/tools/lldb-dap/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/lldb-dap.cpp @@ -3328,8 +3328,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 4b90ee43223a360c0ca58d321af54300eacc2672 Mon Sep 17 00:00:00 2001 From: John Harrison <harj...@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 167142779cf12c..6e8425b3694e30 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 40d61636380588..64a2669d73d437 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 @@ -383,6 +383,7 @@ def launch( expectFailure=False, postRunCommands=None, enableAutoVariableSummaries=False, + enableDisplayExtendedBacktrace=False, enableSyntheticChildDebugging=False, commandEscapePrefix=None, customFrameFormat=None, @@ -424,6 +425,7 @@ def cleanup(): runInTerminal=runInTerminal, postRunCommands=postRunCommands, enableAutoVariableSummaries=enableAutoVariableSummaries, + enableDisplayExtendedBacktrace=enableDisplayExtendedBacktrace, enableSyntheticChildDebugging=enableSyntheticChildDebugging, commandEscapePrefix=commandEscapePrefix, customFrameFormat=customFrameFormat, @@ -463,6 +465,7 @@ def build_and_launch( postRunCommands=None, lldbDAPEnv=None, enableAutoVariableSummaries=False, + enableDisplayExtendedBacktrace=False, enableSyntheticChildDebugging=False, commandEscapePrefix=None, customFrameFormat=None, @@ -499,6 +502,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 1fd560f21904ab..57b93c28ce9301 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 27ea6c7ff8423f..0fc77ac1e81683 100644 --- a/lldb/tools/lldb-dap/DAP.h +++ b/lldb/tools/lldb-dap/DAP.h @@ -181,6 +181,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 9ba82008aa8294..2d68fb534bbf29 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")); @@ -2046,6 +2048,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")); @@ -3283,6 +3287,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 4f4261d1718c01..e488846649b8a7 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 caf309f06d4800594957d1e04757ef12761e0cba Mon Sep 17 00:00:00 2001 From: John Harrison <harj...@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 c080fd395b7288..d61d6dc26c9bf3 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"}}); +} + // "Thread": { // "type": "object", // "description": "A Thread", diff --git a/lldb/tools/lldb-dap/JSONUtils.h b/lldb/tools/lldb-dap/JSONUtils.h index 1515f5ba2e5f4d..ae2e48c2686f52 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 "Thread" object for a LLDB thread object. /// /// This function will fill in the following keys in the returned diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp index 2d68fb534bbf29..a0a3855a12a32f 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", @@ -3232,114 +3305,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))); } _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits