From ca71dc1b5404ac685c3190c45c342b01e94e4761 Mon Sep 17 00:00:00 2001 From: John Harrison Date: Mon, 31 Jul 2023 12:44:06 -0400 Subject: [PATCH] [lldb-vscode] Adding support for the "disassemble" request. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of creating psuedo source files for each stack frame this change adopts the new DAP “disassemble” request, allowing clients to inspect assembly instructions of files with debug info in addition to files without debug info. [[ https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Disassemble | spec ]] See attached screenshot of the disassembly view. {F28473848} Reviewed By: wallace Differential Revision: https://reviews.llvm.org/D156493 --- .../tools/lldb-vscode/lldbvscode_testcase.py | 13 + .../test/tools/lldb-vscode/vscode.py | 19 +- .../coreFile/TestVSCode_coreFile.py | 6 +- .../tools/lldb-vscode/disassemble/Makefile | 3 + .../disassemble/TestVSCode_disassemble.py | 41 ++++ .../API/tools/lldb-vscode/disassemble/main.c | 30 +++ lldb/tools/lldb-vscode/JSONUtils.cpp | 139 +++-------- lldb/tools/lldb-vscode/JSONUtils.h | 25 -- lldb/tools/lldb-vscode/SourceReference.h | 32 --- lldb/tools/lldb-vscode/VSCode.cpp | 12 - lldb/tools/lldb-vscode/VSCode.h | 4 - lldb/tools/lldb-vscode/VSCodeForward.h | 1 - lldb/tools/lldb-vscode/lldb-vscode.cpp | 222 +++++++++++++++++- 13 files changed, 354 insertions(+), 193 deletions(-) create mode 100644 lldb/test/API/tools/lldb-vscode/disassemble/Makefile create mode 100644 lldb/test/API/tools/lldb-vscode/disassemble/TestVSCode_disassemble.py create mode 100644 lldb/test/API/tools/lldb-vscode/disassemble/main.c delete mode 100644 lldb/tools/lldb-vscode/SourceReference.h diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py index adc8436a7dac..a6c370ccd203 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py @@ -257,6 +257,19 @@ class VSCodeTestCaseBase(TestBase): "exitCode == %i" % (exitCode), ) + def disassemble(self, threadId=None, frameIndex=None): + stackFrames = self.get_stackFrames( + threadId=threadId, startFrame=frameIndex, levels=1 + ) + self.assertIsNotNone(stackFrames) + memoryReference = stackFrames[0]["instructionPointerReference"] + self.assertIsNotNone(memoryReference) + + if memoryReference not in self.vscode.disassembled_instructions: + self.vscode.request_disassemble(memoryReference=memoryReference) + + return self.vscode.disassembled_instructions[memoryReference] + def attach( self, program=None, diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py index 5a6bc0f59402..14f0bf0a2d4e 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py @@ -135,6 +135,7 @@ class DebugCommunication(object): self.configuration_done_sent = False self.frame_scopes = {} self.init_commands = init_commands + self.disassembled_instructions = {} @classmethod def encode_content(cls, s): @@ -427,7 +428,7 @@ class DebugCommunication(object): def get_stackFrame(self, frameIndex=0, threadId=None): """Get a single "StackFrame" object from a "stackTrace" request and - return the "StackFrame as a python dictionary, or None on failure + return the "StackFrame" as a python dictionary, or None on failure """ if threadId is None: threadId = self.get_thread_id() @@ -647,6 +648,22 @@ class DebugCommunication(object): "arguments": args_dict, } return self.send_recv(command_dict) + + def request_disassemble(self, memoryReference, offset=-50, instructionCount=200, resolveSymbols=True): + args_dict = { + "memoryReference": memoryReference, + "offset": offset, + "instructionCount": instructionCount, + "resolveSymbols": resolveSymbols + } + command_dict = { + "command": "disassemble", + "type": "request", + "arguments": args_dict, + } + instructions = self.send_recv(command_dict)["body"]["instructions"] + for inst in instructions: + self.disassembled_instructions[inst["address"]] = inst def request_evaluate(self, expression, frameIndex=0, threadId=None, context=None): stackFrame = self.get_stackFrame(frameIndex=frameIndex, threadId=threadId) diff --git a/lldb/test/API/tools/lldb-vscode/coreFile/TestVSCode_coreFile.py b/lldb/test/API/tools/lldb-vscode/coreFile/TestVSCode_coreFile.py index 44b93e146225..8cbdb8fa7e98 100644 --- a/lldb/test/API/tools/lldb-vscode/coreFile/TestVSCode_coreFile.py +++ b/lldb/test/API/tools/lldb-vscode/coreFile/TestVSCode_coreFile.py @@ -25,25 +25,25 @@ class TestVSCode_coreFile(lldbvscode_testcase.VSCodeTestCaseBase): expected_frames = [ { - "column": 0, "id": 524288, "line": 4, "name": "bar", "source": {"name": "main.c", "path": "/home/labath/test/main.c"}, + "instructionPointerReference": "0x40011C", }, { - "column": 0, "id": 524289, "line": 10, "name": "foo", "source": {"name": "main.c", "path": "/home/labath/test/main.c"}, + "instructionPointerReference": "0x400142", }, { - "column": 0, "id": 524290, "line": 16, "name": "_start", "source": {"name": "main.c", "path": "/home/labath/test/main.c"}, + "instructionPointerReference": "0x40015F", }, ] diff --git a/lldb/test/API/tools/lldb-vscode/disassemble/Makefile b/lldb/test/API/tools/lldb-vscode/disassemble/Makefile new file mode 100644 index 000000000000..10495940055b --- /dev/null +++ b/lldb/test/API/tools/lldb-vscode/disassemble/Makefile @@ -0,0 +1,3 @@ +C_SOURCES := main.c + +include Makefile.rules diff --git a/lldb/test/API/tools/lldb-vscode/disassemble/TestVSCode_disassemble.py b/lldb/test/API/tools/lldb-vscode/disassemble/TestVSCode_disassemble.py new file mode 100644 index 000000000000..6d3ebdf296e2 --- /dev/null +++ b/lldb/test/API/tools/lldb-vscode/disassemble/TestVSCode_disassemble.py @@ -0,0 +1,41 @@ +""" +Test lldb-vscode disassemble request +""" + + +import vscode +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil +import lldbvscode_testcase +import os + + +class TestVSCode_disassemble(lldbvscode_testcase.VSCodeTestCaseBase): + @skipIfWindows + @skipIfRemote + def test_disassemble(self): + """ + Tests the 'disassemble' request. + """ + program = self.getBuildArtifact("a.out") + self.build_and_launch(program) + source = "main.c" + self.source_path = os.path.join(os.getcwd(), source) + self.set_source_breakpoints( + source, + [ + line_number(source, "// breakpoint 1"), + ], + ) + self.continue_to_next_stop() + + pc_assembly = self.disassemble(frameIndex=0) + self.assertTrue("location" in pc_assembly, "Source location missing.") + self.assertTrue("instruction" in pc_assembly, "Assembly instruction missing.") + + # The calling frame (qsort) is coming from a system library, as a result + # we should not have a source location. + qsort_assembly = self.disassemble(frameIndex=1) + self.assertFalse("location" in qsort_assembly, "Source location not expected.") + self.assertTrue("instruction" in pc_assembly, "Assembly instruction missing.") diff --git a/lldb/test/API/tools/lldb-vscode/disassemble/main.c b/lldb/test/API/tools/lldb-vscode/disassemble/main.c new file mode 100644 index 000000000000..8dd32f263c28 --- /dev/null +++ b/lldb/test/API/tools/lldb-vscode/disassemble/main.c @@ -0,0 +1,30 @@ +#include +#include +#include + +int compare_ints(const void* a, const void* b) +{ + int arg1 = *(const int*)a; + int arg2 = *(const int*)b; + + // breakpoint 1 + + if (arg1 < arg2) return -1; + if (arg1 > arg2) return 1; + return 0; +} + +int main(void) +{ + int ints[] = { -2, 99, 0, -743, 2, INT_MIN, 4 }; + int size = sizeof ints / sizeof *ints; + + qsort(ints, size, sizeof(int), compare_ints); + + for (int i = 0; i < size; i++) { + printf("%d ", ints[i]); + } + + printf("\n"); + return 0; +} \ No newline at end of file diff --git a/lldb/tools/lldb-vscode/JSONUtils.cpp b/lldb/tools/lldb-vscode/JSONUtils.cpp index 06359e1c76ff..5ece7c01346b 100644 --- a/lldb/tools/lldb-vscode/JSONUtils.cpp +++ b/lldb/tools/lldb-vscode/JSONUtils.cpp @@ -342,6 +342,9 @@ llvm::json::Value CreateBreakpoint(lldb::SBBreakpoint &bp, object.try_emplace("source", CreateSource(*request_path)); if (bp_addr.IsValid()) { + std::string formatted_addr = + "0x" + llvm::utohexstr(bp_addr.GetLoadAddress(g_vsc.target)); + object.try_emplace("instructionReference", formatted_addr); auto line_entry = bp_addr.GetLineEntry(); const auto line = line_entry.GetLine(); if (line != UINT32_MAX) @@ -600,8 +603,8 @@ llvm::json::Value CreateSource(lldb::SBLineEntry &line_entry) { if (name) EmplaceSafeString(object, "name", name); char path[PATH_MAX] = ""; - file.GetPath(path, sizeof(path)); - if (path[0]) { + if (file.GetPath(path, sizeof(path)) && + lldb::SBFileSpec::ResolvePath(path, path, PATH_MAX)) { EmplaceSafeString(object, "path", std::string(path)); } } @@ -616,97 +619,14 @@ llvm::json::Value CreateSource(llvm::StringRef source_path) { return llvm::json::Value(std::move(source)); } -llvm::json::Value CreateSource(lldb::SBFrame &frame, int64_t &disasm_line) { - disasm_line = 0; +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 so don't return early with the line entry. + // file is associated with the frame. if (line_entry.GetFileSpec().IsValid() && line_entry.GetLine() != 0) return CreateSource(line_entry); - llvm::json::Object object; - const auto pc = frame.GetPC(); - - lldb::SBInstructionList insts; - lldb::SBFunction function = frame.GetFunction(); - lldb::addr_t low_pc = LLDB_INVALID_ADDRESS; - if (function.IsValid()) { - low_pc = function.GetStartAddress().GetLoadAddress(g_vsc.target); - auto addr_srcref = g_vsc.addr_to_source_ref.find(low_pc); - if (addr_srcref != g_vsc.addr_to_source_ref.end()) { - // We have this disassembly cached already, return the existing - // sourceReference - object.try_emplace("sourceReference", addr_srcref->second); - disasm_line = g_vsc.GetLineForPC(addr_srcref->second, pc); - } else { - insts = function.GetInstructions(g_vsc.target); - } - } else { - lldb::SBSymbol symbol = frame.GetSymbol(); - if (symbol.IsValid()) { - low_pc = symbol.GetStartAddress().GetLoadAddress(g_vsc.target); - auto addr_srcref = g_vsc.addr_to_source_ref.find(low_pc); - if (addr_srcref != g_vsc.addr_to_source_ref.end()) { - // We have this disassembly cached already, return the existing - // sourceReference - object.try_emplace("sourceReference", addr_srcref->second); - disasm_line = g_vsc.GetLineForPC(addr_srcref->second, pc); - } else { - insts = symbol.GetInstructions(g_vsc.target); - } - } - } - const auto num_insts = insts.GetSize(); - if (low_pc != LLDB_INVALID_ADDRESS && num_insts > 0) { - if (line_entry.GetLine() == 0) { - EmplaceSafeString(object, "name", ""); - } else { - EmplaceSafeString(object, "name", frame.GetDisplayFunctionName()); - } - SourceReference source; - llvm::raw_string_ostream src_strm(source.content); - std::string line; - for (size_t i = 0; i < num_insts; ++i) { - lldb::SBInstruction inst = insts.GetInstructionAtIndex(i); - const auto inst_addr = inst.GetAddress().GetLoadAddress(g_vsc.target); - const char *m = inst.GetMnemonic(g_vsc.target); - const char *o = inst.GetOperands(g_vsc.target); - const char *c = inst.GetComment(g_vsc.target); - if (pc == inst_addr) - disasm_line = i + 1; - const auto inst_offset = inst_addr - low_pc; - int spaces = 0; - if (inst_offset < 10) - spaces = 3; - else if (inst_offset < 100) - spaces = 2; - else if (inst_offset < 1000) - spaces = 1; - line.clear(); - llvm::raw_string_ostream line_strm(line); - line_strm << llvm::formatv("{0:X+}: <{1}> {2} {3,12} {4}", inst_addr, - inst_offset, llvm::fmt_repeat(' ', spaces), m, - o); - - // If there is a comment append it starting at column 60 or after one - // space past the last char - const uint32_t comment_row = std::max(line_strm.str().size(), (size_t)60); - if (c && c[0]) { - if (line.size() < comment_row) - line_strm.indent(comment_row - line_strm.str().size()); - line_strm << " # " << c; - } - src_strm << line_strm.str() << "\n"; - source.addr_to_line[inst_addr] = i + 1; - } - // Flush the source stream - src_strm.str(); - auto sourceReference = VSCode::GetNextSourceReference(); - g_vsc.source_map[sourceReference] = std::move(source); - g_vsc.addr_to_source_ref[low_pc] = sourceReference; - object.try_emplace("sourceReference", sourceReference); - } - return llvm::json::Value(std::move(object)); + return {}; } // "StackFrame": { @@ -748,6 +668,12 @@ llvm::json::Value CreateSource(lldb::SBFrame &frame, int64_t &disasm_line) { // "description": "An optional end column of the range covered by the // stack frame." // }, +// "instructionPointerReference": { +// "type": "string", +// "description": "A memory reference for the current instruction +// pointer +// in this frame." +// }, // "moduleId": { // "type": ["integer", "string"], // "description": "The module associated with this frame, if any." @@ -770,30 +696,37 @@ llvm::json::Value CreateStackFrame(lldb::SBFrame &frame) { int64_t frame_id = MakeVSCodeFrameID(frame); object.try_emplace("id", frame_id); - std::string frame_name; - const char *func_name = frame.GetFunctionName(); - if (func_name) - frame_name = func_name; - else + std::string frame_name = frame.GetDisplayFunctionName(); + if (frame_name.empty()) frame_name = ""; bool is_optimized = frame.GetFunction().GetIsOptimized(); if (is_optimized) frame_name += " [opt]"; EmplaceSafeString(object, "name", frame_name); - int64_t disasm_line = 0; - object.try_emplace("source", CreateSource(frame, disasm_line)); + auto source = CreateSource(frame); - auto line_entry = frame.GetLineEntry(); - if (disasm_line > 0) { - object.try_emplace("line", disasm_line); - } else { + if (source) { + object.try_emplace("source", *source); + auto line_entry = frame.GetLineEntry(); auto line = line_entry.GetLine(); - if (line == UINT32_MAX) - line = 0; - object.try_emplace("line", line); + if (line && line != LLDB_INVALID_LINE_NUMBER) + object.try_emplace("line", line); + auto column = line_entry.GetColumn(); + if (column && column != LLDB_INVALID_COLUMN_NUMBER) + object.try_emplace("column", column); + } else { + object.try_emplace("line", 0); + object.try_emplace("column", 0); + object.try_emplace("presentationHint", "subtle"); } - object.try_emplace("column", line_entry.GetColumn()); + + const auto pc = frame.GetPC(); + if (pc != LLDB_INVALID_ADDRESS) { + std::string formatted_addr = "0x" + llvm::utohexstr(pc); + object.try_emplace("instructionPointerReference", formatted_addr); + } + return llvm::json::Value(std::move(object)); } diff --git a/lldb/tools/lldb-vscode/JSONUtils.h b/lldb/tools/lldb-vscode/JSONUtils.h index 45f699659140..516bc462eae1 100644 --- a/lldb/tools/lldb-vscode/JSONUtils.h +++ b/lldb/tools/lldb-vscode/JSONUtils.h @@ -324,31 +324,6 @@ llvm::json::Value CreateSource(lldb::SBLineEntry &line_entry); /// definition outlined by Microsoft. llvm::json::Value CreateSource(llvm::StringRef source_path); -/// Create a "Source" object for a given frame. -/// -/// When there is no source file information for a stack frame, we will -/// create disassembly for a function and store a permanent -/// "sourceReference" that contains the textual disassembly for a -/// function along with address to line information. The "Source" object -/// that is created will contain a "sourceReference" that the VSCode -/// protocol can later fetch as text in order to display disassembly. -/// The PC will be extracted from the frame and the disassembly line -/// within the source referred to by "sourceReference" will be filled -/// in. -/// -/// \param[in] frame -/// The LLDB stack frame to use when populating out the "Source" -/// object. -/// -/// \param[out] disasm_line -/// The line within the "sourceReference" file that the PC from -/// \a frame matches. -/// -/// \return -/// A "Source" JSON object with that follows the formal JSON -/// definition outlined by Microsoft. -llvm::json::Value CreateSource(lldb::SBFrame &frame, int64_t &disasm_line); - /// Create a "StackFrame" object for a LLDB frame object. /// /// This function will fill in the following keys in the returned diff --git a/lldb/tools/lldb-vscode/SourceReference.h b/lldb/tools/lldb-vscode/SourceReference.h deleted file mode 100644 index cf62fb6680e2..000000000000 --- a/lldb/tools/lldb-vscode/SourceReference.h +++ /dev/null @@ -1,32 +0,0 @@ -//===-- SourceReference.h ---------------------------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef LLDB_TOOLS_LLDB_VSCODE_SOURCEREFERENCE_H -#define LLDB_TOOLS_LLDB_VSCODE_SOURCEREFERENCE_H - -#include "lldb/lldb-types.h" -#include "llvm/ADT/DenseMap.h" -#include - -namespace lldb_vscode { - -struct SourceReference { - std::string content; - llvm::DenseMap addr_to_line; - - int64_t GetLineForPC(lldb::addr_t pc) const { - auto addr_line = addr_to_line.find(pc); - if (addr_line != addr_to_line.end()) - return addr_line->second; - return 0; - } -}; - -} // namespace lldb_vscode - -#endif diff --git a/lldb/tools/lldb-vscode/VSCode.cpp b/lldb/tools/lldb-vscode/VSCode.cpp index c4011158ab5a..b6bf67c7bcfc 100644 --- a/lldb/tools/lldb-vscode/VSCode.cpp +++ b/lldb/tools/lldb-vscode/VSCode.cpp @@ -64,13 +64,6 @@ VSCode::VSCode() VSCode::~VSCode() = default; -int64_t VSCode::GetLineForPC(int64_t sourceReference, lldb::addr_t pc) const { - auto pos = source_map.find(sourceReference); - if (pos != source_map.end()) - return pos->second.GetLineForPC(pc); - return 0; -} - ExceptionBreakpoint *VSCode::GetExceptionBreakpoint(const std::string &filter) { for (auto &bp : exception_breakpoints) { if (bp.filter == filter) @@ -341,11 +334,6 @@ VSCode::SendFormattedOutput(OutputType o, const char *format, ...) { o, llvm::StringRef(buffer, std::min(actual_length, sizeof(buffer)))); } -int64_t VSCode::GetNextSourceReference() { - static int64_t ref = 0; - return ++ref; -} - ExceptionBreakpoint * VSCode::GetExceptionBPFromStopReason(lldb::SBThread &thread) { const auto num = thread.GetStopReasonDataCount(); diff --git a/lldb/tools/lldb-vscode/VSCode.h b/lldb/tools/lldb-vscode/VSCode.h index 4a2779fa4972..730223046b3c 100644 --- a/lldb/tools/lldb-vscode/VSCode.h +++ b/lldb/tools/lldb-vscode/VSCode.h @@ -55,7 +55,6 @@ #include "ProgressEvent.h" #include "RunInTerminal.h" #include "SourceBreakpoint.h" -#include "SourceReference.h" #define VARREF_LOCALS (int64_t)1 #define VARREF_GLOBALS (int64_t)2 @@ -153,8 +152,6 @@ struct VSCode { std::thread event_thread; std::thread progress_event_thread; std::unique_ptr log; - llvm::DenseMap addr_to_source_ref; - llvm::DenseMap source_map; llvm::StringMap source_breakpoints; FunctionBreakpointMap function_breakpoints; std::vector exception_breakpoints; @@ -194,7 +191,6 @@ struct VSCode { ~VSCode(); VSCode(const VSCode &rhs) = delete; void operator=(const VSCode &rhs) = delete; - int64_t GetLineForPC(int64_t sourceReference, lldb::addr_t pc) const; ExceptionBreakpoint *GetExceptionBreakpoint(const std::string &filter); ExceptionBreakpoint *GetExceptionBreakpoint(const lldb::break_id_t bp_id); diff --git a/lldb/tools/lldb-vscode/VSCodeForward.h b/lldb/tools/lldb-vscode/VSCodeForward.h index 6be5a9635e7e..92eb5757d180 100644 --- a/lldb/tools/lldb-vscode/VSCodeForward.h +++ b/lldb/tools/lldb-vscode/VSCodeForward.h @@ -14,7 +14,6 @@ struct BreakpointBase; struct ExceptionBreakpoint; struct FunctionBreakpoint; struct SourceBreakpoint; -struct SourceReference; } // namespace lldb_vscode namespace lldb { diff --git a/lldb/tools/lldb-vscode/lldb-vscode.cpp b/lldb/tools/lldb-vscode/lldb-vscode.cpp index 442120256606..1680bead2491 100644 --- a/lldb/tools/lldb-vscode/lldb-vscode.cpp +++ b/lldb/tools/lldb-vscode/lldb-vscode.cpp @@ -50,6 +50,7 @@ #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/ScopeExit.h" +#include "llvm/ADT/StringExtras.h" #include "llvm/Option/Arg.h" #include "llvm/Option/ArgList.h" #include "llvm/Option/Option.h" @@ -1563,6 +1564,8 @@ void request_initialize(const llvm::json::Object &request) { body.try_emplace("supportsStepInTargetsRequest", false); // The debug adapter supports the completions request. body.try_emplace("supportsCompletionsRequest", true); + // The debug adapter supports the disassembly request. + body.try_emplace("supportsDisassembleRequest", true); llvm::json::Array completion_characters; completion_characters.emplace_back("."); @@ -2588,18 +2591,7 @@ void request_setFunctionBreakpoints(const llvm::json::Object &request) { void request_source(const llvm::json::Object &request) { llvm::json::Object response; FillResponse(request, response); - llvm::json::Object body; - - auto arguments = request.getObject("arguments"); - auto source = arguments->getObject("source"); - auto sourceReference = GetSigned(source, "sourceReference", -1); - auto pos = g_vsc.source_map.find((lldb::addr_t)sourceReference); - if (pos != g_vsc.source_map.end()) { - EmplaceSafeString(body, "content", pos->second.content); - } else { - response["success"] = llvm::json::Value(false); - } - EmplaceSafeString(body, "mimeType", "text/x-lldb.disassembly"); + llvm::json::Object body{{"content", ""}}; response.try_emplace("body", std::move(body)); g_vsc.SendJSON(llvm::json::Value(std::move(response))); } @@ -3301,6 +3293,211 @@ void request_variables(const llvm::json::Object &request) { g_vsc.SendJSON(llvm::json::Value(std::move(response))); } +// "DisassembleRequest": { +// "allOf": [ { "$ref": "#/definitions/Request" }, { +// "type": "object", +// "description": "Disassembles code stored at the provided +// location.\nClients should only call this request if the corresponding +// capability `supportsDisassembleRequest` is true.", "properties": { +// "command": { +// "type": "string", +// "enum": [ "disassemble" ] +// }, +// "arguments": { +// "$ref": "#/definitions/DisassembleArguments" +// } +// }, +// "required": [ "command", "arguments" ] +// }] +// }, +// "DisassembleArguments": { +// "type": "object", +// "description": "Arguments for `disassemble` request.", +// "properties": { +// "memoryReference": { +// "type": "string", +// "description": "Memory reference to the base location containing the +// instructions to disassemble." +// }, +// "offset": { +// "type": "integer", +// "description": "Offset (in bytes) to be applied to the reference +// location before disassembling. Can be negative." +// }, +// "instructionOffset": { +// "type": "integer", +// "description": "Offset (in instructions) to be applied after the byte +// offset (if any) before disassembling. Can be negative." +// }, +// "instructionCount": { +// "type": "integer", +// "description": "Number of instructions to disassemble starting at the +// specified location and offset.\nAn adapter must return exactly this +// number of instructions - any unavailable instructions should be +// replaced with an implementation-defined 'invalid instruction' value." +// }, +// "resolveSymbols": { +// "type": "boolean", +// "description": "If true, the adapter should attempt to resolve memory +// addresses and other values to symbolic names." +// } +// }, +// "required": [ "memoryReference", "instructionCount" ] +// }, +// "DisassembleResponse": { +// "allOf": [ { "$ref": "#/definitions/Response" }, { +// "type": "object", +// "description": "Response to `disassemble` request.", +// "properties": { +// "body": { +// "type": "object", +// "properties": { +// "instructions": { +// "type": "array", +// "items": { +// "$ref": "#/definitions/DisassembledInstruction" +// }, +// "description": "The list of disassembled instructions." +// } +// }, +// "required": [ "instructions" ] +// } +// } +// }] +// } +void request_disassemble(const llvm::json::Object &request) { + llvm::json::Object response; + FillResponse(request, response); + auto arguments = request.getObject("arguments"); + + auto memoryReference = GetString(arguments, "memoryReference"); + lldb::addr_t addr_ptr; + if (memoryReference.consumeInteger(0, addr_ptr)) { + response["success"] = false; + response["message"] = + "Malformed memory reference: " + memoryReference.str(); + g_vsc.SendJSON(llvm::json::Value(std::move(response))); + return; + } + + addr_ptr += GetSigned(arguments, "instructionOffset", 0); + lldb::SBAddress addr(addr_ptr, g_vsc.target); + if (!addr.IsValid()) { + response["success"] = false; + response["message"] = "Memory reference not found in the current binary."; + g_vsc.SendJSON(llvm::json::Value(std::move(response))); + return; + } + + const auto inst_count = GetUnsigned(arguments, "instructionCount", 0); + lldb::SBInstructionList insts = + g_vsc.target.ReadInstructions(addr, inst_count); + + if (!insts.IsValid()) { + response["success"] = false; + response["message"] = "Failed to find instructions for memory address."; + g_vsc.SendJSON(llvm::json::Value(std::move(response))); + return; + } + + const bool resolveSymbols = GetBoolean(arguments, "resolveSymbols", false); + llvm::json::Array instructions; + const auto num_insts = insts.GetSize(); + for (size_t i = 0; i < num_insts; ++i) { + lldb::SBInstruction inst = insts.GetInstructionAtIndex(i); + auto addr = inst.GetAddress(); + const auto inst_addr = addr.GetLoadAddress(g_vsc.target); + const char *m = inst.GetMnemonic(g_vsc.target); + const char *o = inst.GetOperands(g_vsc.target); + const char *c = inst.GetComment(g_vsc.target); + auto d = inst.GetData(g_vsc.target); + + std::string bytes; + llvm::raw_string_ostream sb(bytes); + for (unsigned i = 0; i < inst.GetByteSize(); i++) { + lldb::SBError error; + uint8_t b = d.GetUnsignedInt8(error, i); + if (error.Success()) { + sb << llvm::format("%2.2x ", b); + } + } + sb.flush(); + + llvm::json::Object disassembled_inst{ + {"address", "0x" + llvm::utohexstr(inst_addr)}, + {"instructionBytes", + bytes.size() > 0 ? bytes.substr(0, bytes.size() - 1) : ""}, + }; + + std::string instruction; + llvm::raw_string_ostream si(instruction); + + lldb::SBSymbol symbol = addr.GetSymbol(); + // Only add the symbol on the first line of the function. + if (symbol.IsValid() && symbol.GetStartAddress() == addr) { + // If we have a valid symbol, append it as a label prefix for the first + // instruction. This is so you can see the start of a function/callsite + // in the assembly, at the moment VS Code (1.80) does not visualize the + // symbol associated with the assembly instruction. + si << (symbol.GetMangledName() != nullptr ? symbol.GetMangledName() + : symbol.GetName()) + << ": "; + + if (resolveSymbols) { + disassembled_inst.try_emplace("symbol", symbol.GetDisplayName()); + } + } + + si << llvm::formatv("{0,7} {1,12}", m, o); + if (c && c[0]) { + si << " ; " << c; + } + si.flush(); + + disassembled_inst.try_emplace("instruction", instruction); + + auto line_entry = addr.GetLineEntry(); + // If the line number is 0 then the entry represents a compiler generated + // location. + if (line_entry.GetStartAddress() == addr && line_entry.IsValid() && + line_entry.GetFileSpec().IsValid() && line_entry.GetLine() != 0) { + auto source = CreateSource(line_entry); + disassembled_inst.try_emplace("location", source); + + const auto line = line_entry.GetLine(); + if (line && line != LLDB_INVALID_LINE_NUMBER) { + disassembled_inst.try_emplace("line", line); + } + const auto column = line_entry.GetColumn(); + if (column && column != LLDB_INVALID_COLUMN_NUMBER) { + disassembled_inst.try_emplace("column", column); + } + + auto end_line_entry = line_entry.GetEndAddress().GetLineEntry(); + if (end_line_entry.IsValid() && + end_line_entry.GetFileSpec() == line_entry.GetFileSpec()) { + const auto end_line = end_line_entry.GetLine(); + if (end_line && end_line != LLDB_INVALID_LINE_NUMBER && + end_line != line) { + disassembled_inst.try_emplace("endLine", end_line); + + const auto end_column = end_line_entry.GetColumn(); + if (end_column && end_column != LLDB_INVALID_COLUMN_NUMBER && + end_column != column) { + disassembled_inst.try_emplace("endColumn", end_column - 1); + } + } + } + } + + instructions.emplace_back(std::move(disassembled_inst)); + } + + llvm::json::Object body; + body.try_emplace("instructions", std::move(instructions)); + response.try_emplace("body", std::move(body)); + g_vsc.SendJSON(llvm::json::Value(std::move(response))); +} // A request used in testing to get the details on all breakpoints that are // currently set in the target. This helps us to test "setBreakpoints" and // "setFunctionBreakpoints" requests to verify we have the correct set of @@ -3345,6 +3542,7 @@ void RegisterRequestCallbacks() { g_vsc.RegisterRequestCallback("stepOut", request_stepOut); g_vsc.RegisterRequestCallback("threads", request_threads); g_vsc.RegisterRequestCallback("variables", request_variables); + g_vsc.RegisterRequestCallback("disassemble", request_disassemble); // Custom requests g_vsc.RegisterRequestCallback("compileUnits", request_compileUnits); g_vsc.RegisterRequestCallback("modules", request_modules);