diff --git a/lldb/tools/lldb-vscode/JSONUtils.cpp b/lldb/tools/lldb-vscode/JSONUtils.cpp --- a/lldb/tools/lldb-vscode/JSONUtils.cpp +++ b/lldb/tools/lldb-vscode/JSONUtils.cpp @@ -342,6 +342,9 @@ 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 @@ 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 @@ 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 @@ // "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,47 @@ 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 = llvm::json::fixUTF8(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"); + } + + lldb::addr_t inst_ptr = LLDB_INVALID_ADDRESS; + lldb::SBFunction func = frame.GetFunction(); + if (func.IsValid()) { + inst_ptr = func.GetStartAddress().GetLoadAddress(g_vsc.target); + } else { + lldb::SBSymbol symbol = frame.GetSymbol(); + if (symbol.IsValid()) { + inst_ptr = symbol.GetStartAddress().GetLoadAddress(g_vsc.target); + } } - object.try_emplace("column", line_entry.GetColumn()); + + if (inst_ptr != LLDB_INVALID_ADDRESS) { + std::string formatted_addr = "0x" + llvm::utohexstr(inst_ptr); + object.try_emplace("instructionPointerReference", formatted_addr); + } + return llvm::json::Value(std::move(object)); } diff --git a/lldb/tools/lldb-vscode/lldb-vscode.cpp b/lldb/tools/lldb-vscode/lldb-vscode.cpp --- 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 @@ 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("."); @@ -3290,6 +3293,211 @@ 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 @@ -3334,6 +3542,7 @@ 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);