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 00e068d0954ea..391378cf027bc 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 @@ -1068,6 +1068,19 @@ def request_stackTrace( print("[%3u] %s" % (idx, name)) return response + def request_source(self, sourceReference): + """Request a source from a 'Source' reference.""" + command_dict = { + "command": "source", + "type": "request", + "arguments": { + "source": {"sourceReference": sourceReference}, + # legacy version of the request + "sourceReference": sourceReference, + }, + } + return self.send_recv(command_dict) + def request_threads(self): """Request a list of all threads and combine any information from any "stopped" events since those contain more information about why a diff --git a/lldb/test/API/tools/lldb-dap/source/Makefile b/lldb/test/API/tools/lldb-dap/source/Makefile new file mode 100644 index 0000000000000..10495940055b6 --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/source/Makefile @@ -0,0 +1,3 @@ +C_SOURCES := main.c + +include Makefile.rules diff --git a/lldb/test/API/tools/lldb-dap/source/TestDAP_source.py b/lldb/test/API/tools/lldb-dap/source/TestDAP_source.py new file mode 100644 index 0000000000000..edf0af0bba2ba --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/source/TestDAP_source.py @@ -0,0 +1,106 @@ +""" +Test lldb-dap source request +""" + + +import os + +import lldbdap_testcase +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * + + +class TestDAP_source(lldbdap_testcase.DAPTestCaseBase): + @skipIfWindows + def test_source(self): + """ + Tests the 'source' packet. + """ + program = self.getBuildArtifact("a.out") + self.build_and_launch(program) + source = self.getSourcePath("main.c") + breakpoint_line = line_number(source, "breakpoint") + + lines = [breakpoint_line] + breakpoint_ids = self.set_source_breakpoints(source, lines) + self.assertEqual( + len(breakpoint_ids), len(lines), "expect correct number of breakpoints" + ) + + self.continue_to_breakpoints(breakpoint_ids) + + response = self.dap_server.request_source(sourceReference=0) + self.assertFalse(response["success"], "verify invalid sourceReference fails") + + (stackFrames, totalFrames) = self.get_stackFrames_and_totalFramesCount() + frameCount = len(stackFrames) + self.assertGreaterEqual(frameCount, 3, "verify we got up to main at least") + self.assertEqual( + totalFrames, + frameCount, + "verify total frames returns a speculative page size", + ) + wantFrames = [ + { + "name": "handler", + "line": 8, + "source": { + "name": "main.c", + "path": source, + "containsSourceReference": False, + }, + }, + { + "name": "add", + "source": { + "name": "add", + "path": program + "`add", + "containsSourceReference": True, + }, + }, + { + "name": "main", + "line": 12, + "source": { + "name": "main.c", + "path": source, + "containsSourceReference": False, + }, + }, + ] + for idx, want in enumerate(wantFrames): + got = stackFrames[idx] + name = self.get_dict_value(got, ["name"]) + self.assertEqual(name, want["name"]) + + if "line" in want: + line = self.get_dict_value(got, ["line"]) + self.assertEqual(line, want["line"]) + + wantSource = want["source"] + source_name = self.get_dict_value(got, ["source", "name"]) + self.assertEqual(source_name, wantSource["name"]) + + source_path = self.get_dict_value(got, ["source", "path"]) + self.assertEqual(source_path, wantSource["path"]) + + if wantSource["containsSourceReference"]: + sourceReference = self.get_dict_value( + got, ["source", "sourceReference"] + ) + response = self.dap_server.request_source( + sourceReference=sourceReference + ) + self.assertTrue(response["success"]) + self.assertGreater( + len(self.get_dict_value(response, ["body", "content"])), + 0, + "verify content returned disassembly", + ) + self.assertEqual( + self.get_dict_value(response, ["body", "mimeType"]), + "text/x-lldb.disassembly", + "verify mime type returned", + ) + else: + self.assertNotIn("sourceReference", got["source"]) diff --git a/lldb/test/API/tools/lldb-dap/source/main.c b/lldb/test/API/tools/lldb-dap/source/main.c new file mode 100644 index 0000000000000..39420dba7874f --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/source/main.c @@ -0,0 +1,14 @@ +#include + +__attribute__((nodebug)) static void add(int i, int j, void handler(int)) { + handler(i + j); +} + +static void handler(int result) { + printf("result %d\n", result); // breakpoint +} + +int main(int argc, char const *argv[]) { + add(2, 3, handler); + return 0; +} diff --git a/lldb/tools/lldb-dap/Handler/SourceRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/SourceRequestHandler.cpp index 03561c9e64922..7a1ef7f33b6fb 100644 --- a/lldb/tools/lldb-dap/Handler/SourceRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/SourceRequestHandler.cpp @@ -9,7 +9,15 @@ #include "DAP.h" #include "EventHelper.h" #include "JSONUtils.h" +#include "LLDBUtils.h" #include "RequestHandler.h" +#include "lldb/API/SBFrame.h" +#include "lldb/API/SBInstructionList.h" +#include "lldb/API/SBProcess.h" +#include "lldb/API/SBStream.h" +#include "lldb/API/SBTarget.h" +#include "lldb/API/SBThread.h" +#include "llvm/Support/JSON.h" namespace lldb_dap { @@ -74,8 +82,37 @@ namespace lldb_dap { void SourceRequestHandler::operator()(const llvm::json::Object &request) { llvm::json::Object response; FillResponse(request, response); - llvm::json::Object body{{"content", ""}}; - response.try_emplace("body", std::move(body)); + const auto *arguments = request.getObject("arguments"); + const auto *source = arguments->getObject("source"); + llvm::json::Object body; + int64_t source_ref = GetUnsigned( + source, "sourceReference", GetUnsigned(arguments, "sourceReference", 0)); + + if (source_ref) { + lldb::SBProcess process = dap.target.GetProcess(); + // Upper 32 bits is the thread index ID + lldb::SBThread thread = + process.GetThreadByIndexID(GetLLDBThreadIndexID(source_ref)); + // Lower 32 bits is the frame index + lldb::SBFrame frame = thread.GetFrameAtIndex(GetLLDBFrameID(source_ref)); + if (!frame.IsValid()) { + response["success"] = false; + response["message"] = "source not found"; + } else { + lldb::SBInstructionList insts = + frame.GetSymbol().GetInstructions(dap.target); + lldb::SBStream stream; + insts.GetDescription(stream); + body["content"] = stream.GetData(); + body["mimeType"] = "text/x-lldb.disassembly"; + response.try_emplace("body", std::move(body)); + } + } else { + response["success"] = false; + response["message"] = + "invalid arguments, expected source.sourceReference to be set"; + } + dap.SendJSON(llvm::json::Value(std::move(response))); } diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp index 6ca4dfb4711a1..9f08efb2a3ac1 100644 --- a/lldb/tools/lldb-dap/JSONUtils.cpp +++ b/lldb/tools/lldb-dap/JSONUtils.cpp @@ -7,7 +7,6 @@ //===----------------------------------------------------------------------===// #include "JSONUtils.h" - #include "BreakpointBase.h" #include "DAP.h" #include "ExceptionBreakpoint.h" @@ -41,11 +40,11 @@ #include "llvm/Support/Compiler.h" #include "llvm/Support/Format.h" #include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/JSON.h" #include "llvm/Support/Path.h" #include "llvm/Support/ScopedPrinter.h" #include "llvm/Support/raw_ostream.h" #include -#include #include #include #include @@ -698,16 +697,6 @@ llvm::json::Value CreateSource(llvm::StringRef source_path) { return llvm::json::Value(std::move(source)); } -static std::optional CreateSource(lldb::SBFrame &frame) { - auto line_entry = frame.GetLineEntry(); - // A line entry of 0 indicates the line is compiler generated i.e. no source - // file is associated with the frame. - if (line_entry.GetFileSpec().IsValid() && line_entry.GetLine() != 0) - return CreateSource(line_entry); - - return {}; -} - // "StackFrame": { // "type": "object", // "description": "A Stackframe contains the source location.", @@ -799,21 +788,40 @@ llvm::json::Value CreateStackFrame(lldb::SBFrame &frame, EmplaceSafeString(object, "name", frame_name); - auto source = CreateSource(frame); - - if (source) { - object.try_emplace("source", *source); - auto line_entry = frame.GetLineEntry(); - auto line = line_entry.GetLine(); - if (line && line != LLDB_INVALID_LINE_NUMBER) - object.try_emplace("line", line); - else - object.try_emplace("line", 0); + auto line_entry = frame.GetLineEntry(); + // A line entry of 0 indicates the line is compiler generated i.e. no source + // file is associated with the frame. + if (line_entry.GetFileSpec().IsValid() && + (line_entry.GetLine() != 0 || + line_entry.GetLine() != LLDB_INVALID_LINE_NUMBER)) { + object.try_emplace("source", CreateSource(line_entry)); + object.try_emplace("line", line_entry.GetLine()); auto column = line_entry.GetColumn(); object.try_emplace("column", column); - } else { - object.try_emplace("line", 0); - object.try_emplace("column", 0); + } else if (frame.GetSymbol().IsValid()) { + // If no source is associated with the frame, use the DAPFrameID to track + // the 'source' and generate assembly. + llvm::json::Object source; + EmplaceSafeString(source, "name", frame_name); + char buf[PATH_MAX] = {0}; + size_t size = frame.GetModule().GetFileSpec().GetPath(buf, PATH_MAX); + EmplaceSafeString(source, "path", + std::string(buf, size) + '`' + frame_name); + source.try_emplace("sourceReference", MakeDAPFrameID(frame)); + // Mark the source as deemphasized since users will only be able to view + // assembly for these frames. + EmplaceSafeString(source, "presentationHint", "deemphasize"); + object.try_emplace("source", std::move(source)); + + // Calculate the line of the current PC from the start of the current + // symbol. + lldb::addr_t inst_offset = frame.GetPCAddress().GetOffset() - + frame.GetSymbol().GetStartAddress().GetOffset(); + lldb::addr_t inst_line = + inst_offset / (frame.GetThread().GetProcess().GetAddressByteSize() / 2); + // Line numbers are 1-based. + object.try_emplace("line", inst_line + 1); + object.try_emplace("column", 1); } const auto pc = frame.GetPC();