ashgti created this revision. Herald added a project: All. ashgti requested review of this revision. Herald added a project: LLDB. Herald added a subscriber: lldb-commits.
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. Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D156493 Files: lldb/tools/lldb-vscode/JSONUtils.cpp lldb/tools/lldb-vscode/lldb-vscode.cpp
Index: lldb/tools/lldb-vscode/lldb-vscode.cpp =================================================================== --- lldb/tools/lldb-vscode/lldb-vscode.cpp +++ 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,212 @@ 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", llvm::json::fixUTF8(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 +3543,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); Index: lldb/tools/lldb-vscode/JSONUtils.cpp =================================================================== --- lldb/tools/lldb-vscode/JSONUtils.cpp +++ 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<llvm::json::Value> 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", "<compiler-generated>"); - } 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 = "<unknown>"; 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)); }
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits