| //===-- JSONUtils.cpp -------------------------------------------*- 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 |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include <algorithm> |
| #include <iomanip> |
| #include <sstream> |
| |
| #include "llvm/ADT/Optional.h" |
| #include "llvm/Support/FormatAdapters.h" |
| #include "llvm/Support/Path.h" |
| #include "llvm/Support/ScopedPrinter.h" |
| |
| #include "lldb/API/SBBreakpoint.h" |
| #include "lldb/API/SBBreakpointLocation.h" |
| #include "lldb/API/SBDeclaration.h" |
| #include "lldb/API/SBValue.h" |
| #include "lldb/Host/PosixApi.h" |
| |
| #include "ExceptionBreakpoint.h" |
| #include "JSONUtils.h" |
| #include "LLDBUtils.h" |
| #include "VSCode.h" |
| |
| namespace lldb_vscode { |
| |
| void EmplaceSafeString(llvm::json::Object &obj, llvm::StringRef key, |
| llvm::StringRef str) { |
| if (LLVM_LIKELY(llvm::json::isUTF8(str))) |
| obj.try_emplace(key, str.str()); |
| else |
| obj.try_emplace(key, llvm::json::fixUTF8(str)); |
| } |
| |
| llvm::StringRef GetAsString(const llvm::json::Value &value) { |
| if (auto s = value.getAsString()) |
| return *s; |
| return llvm::StringRef(); |
| } |
| |
| // Gets a string from a JSON object using the key, or returns an empty string. |
| llvm::StringRef GetString(const llvm::json::Object &obj, llvm::StringRef key) { |
| if (llvm::Optional<llvm::StringRef> value = obj.getString(key)) |
| return *value; |
| return llvm::StringRef(); |
| } |
| |
| llvm::StringRef GetString(const llvm::json::Object *obj, llvm::StringRef key) { |
| if (obj == nullptr) |
| return llvm::StringRef(); |
| return GetString(*obj, key); |
| } |
| |
| // Gets an unsigned integer from a JSON object using the key, or returns the |
| // specified fail value. |
| uint64_t GetUnsigned(const llvm::json::Object &obj, llvm::StringRef key, |
| uint64_t fail_value) { |
| if (auto value = obj.getInteger(key)) |
| return (uint64_t)*value; |
| return fail_value; |
| } |
| |
| uint64_t GetUnsigned(const llvm::json::Object *obj, llvm::StringRef key, |
| uint64_t fail_value) { |
| if (obj == nullptr) |
| return fail_value; |
| return GetUnsigned(*obj, key, fail_value); |
| } |
| |
| bool GetBoolean(const llvm::json::Object &obj, llvm::StringRef key, |
| bool fail_value) { |
| if (auto value = obj.getBoolean(key)) |
| return *value; |
| if (auto value = obj.getInteger(key)) |
| return *value != 0; |
| return fail_value; |
| } |
| |
| bool GetBoolean(const llvm::json::Object *obj, llvm::StringRef key, |
| bool fail_value) { |
| if (obj == nullptr) |
| return fail_value; |
| return GetBoolean(*obj, key, fail_value); |
| } |
| |
| int64_t GetSigned(const llvm::json::Object &obj, llvm::StringRef key, |
| int64_t fail_value) { |
| if (auto value = obj.getInteger(key)) |
| return *value; |
| return fail_value; |
| } |
| |
| int64_t GetSigned(const llvm::json::Object *obj, llvm::StringRef key, |
| int64_t fail_value) { |
| if (obj == nullptr) |
| return fail_value; |
| return GetSigned(*obj, key, fail_value); |
| } |
| |
| bool ObjectContainsKey(const llvm::json::Object &obj, llvm::StringRef key) { |
| return obj.find(key) != obj.end(); |
| } |
| |
| std::vector<std::string> GetStrings(const llvm::json::Object *obj, |
| llvm::StringRef key) { |
| std::vector<std::string> strs; |
| auto json_array = obj->getArray(key); |
| if (!json_array) |
| return strs; |
| for (const auto &value : *json_array) { |
| switch (value.kind()) { |
| case llvm::json::Value::String: |
| strs.push_back(value.getAsString()->str()); |
| break; |
| case llvm::json::Value::Number: |
| case llvm::json::Value::Boolean: |
| strs.push_back(llvm::to_string(value)); |
| break; |
| case llvm::json::Value::Null: |
| case llvm::json::Value::Object: |
| case llvm::json::Value::Array: |
| break; |
| } |
| } |
| return strs; |
| } |
| |
| void SetValueForKey(lldb::SBValue &v, llvm::json::Object &object, |
| llvm::StringRef key) { |
| |
| llvm::StringRef value = v.GetValue(); |
| llvm::StringRef summary = v.GetSummary(); |
| llvm::StringRef type_name = v.GetType().GetDisplayTypeName(); |
| |
| std::string result; |
| llvm::raw_string_ostream strm(result); |
| if (!value.empty()) { |
| strm << value; |
| if (!summary.empty()) |
| strm << ' ' << summary; |
| } else if (!summary.empty()) { |
| strm << ' ' << summary; |
| } else if (!type_name.empty()) { |
| strm << type_name; |
| lldb::addr_t address = v.GetLoadAddress(); |
| if (address != LLDB_INVALID_ADDRESS) |
| strm << " @ " << llvm::format_hex(address, 0); |
| } |
| strm.flush(); |
| EmplaceSafeString(object, key, result); |
| } |
| |
| void FillResponse(const llvm::json::Object &request, |
| llvm::json::Object &response) { |
| // Fill in all of the needed response fields to a "request" and set "success" |
| // to true by default. |
| response.try_emplace("type", "response"); |
| response.try_emplace("seq", (int64_t)0); |
| EmplaceSafeString(response, "command", GetString(request, "command")); |
| const int64_t seq = GetSigned(request, "seq", 0); |
| response.try_emplace("request_seq", seq); |
| response.try_emplace("success", true); |
| } |
| |
| // "Scope": { |
| // "type": "object", |
| // "description": "A Scope is a named container for variables. Optionally |
| // a scope can map to a source or a range within a source.", |
| // "properties": { |
| // "name": { |
| // "type": "string", |
| // "description": "Name of the scope such as 'Arguments', 'Locals'." |
| // }, |
| // "presentationHint": { |
| // "type": "string", |
| // "description": "An optional hint for how to present this scope in the |
| // UI. If this attribute is missing, the scope is shown |
| // with a generic UI.", |
| // "_enum": [ "arguments", "locals", "registers" ], |
| // }, |
| // "variablesReference": { |
| // "type": "integer", |
| // "description": "The variables of this scope can be retrieved by |
| // passing the value of variablesReference to the |
| // VariablesRequest." |
| // }, |
| // "namedVariables": { |
| // "type": "integer", |
| // "description": "The number of named variables in this scope. The |
| // client can use this optional information to present |
| // the variables in a paged UI and fetch them in chunks." |
| // }, |
| // "indexedVariables": { |
| // "type": "integer", |
| // "description": "The number of indexed variables in this scope. The |
| // client can use this optional information to present |
| // the variables in a paged UI and fetch them in chunks." |
| // }, |
| // "expensive": { |
| // "type": "boolean", |
| // "description": "If true, the number of variables in this scope is |
| // large or expensive to retrieve." |
| // }, |
| // "source": { |
| // "$ref": "#/definitions/Source", |
| // "description": "Optional source for this scope." |
| // }, |
| // "line": { |
| // "type": "integer", |
| // "description": "Optional start line of the range covered by this |
| // scope." |
| // }, |
| // "column": { |
| // "type": "integer", |
| // "description": "Optional start column of the range covered by this |
| // scope." |
| // }, |
| // "endLine": { |
| // "type": "integer", |
| // "description": "Optional end line of the range covered by this scope." |
| // }, |
| // "endColumn": { |
| // "type": "integer", |
| // "description": "Optional end column of the range covered by this |
| // scope." |
| // } |
| // }, |
| // "required": [ "name", "variablesReference", "expensive" ] |
| // } |
| llvm::json::Value CreateScope(const llvm::StringRef name, |
| int64_t variablesReference, |
| int64_t namedVariables, bool expensive) { |
| llvm::json::Object object; |
| EmplaceSafeString(object, "name", name.str()); |
| |
| // TODO: Support "arguments" scope. At the moment lldb-vscode includes the |
| // arguments into the "locals" scope. |
| if (variablesReference == VARREF_LOCALS) { |
| object.try_emplace("presentationHint", "locals"); |
| } else if (variablesReference == VARREF_REGS) { |
| object.try_emplace("presentationHint", "registers"); |
| } |
| |
| object.try_emplace("variablesReference", variablesReference); |
| object.try_emplace("expensive", expensive); |
| object.try_emplace("namedVariables", namedVariables); |
| return llvm::json::Value(std::move(object)); |
| } |
| |
| // "Breakpoint": { |
| // "type": "object", |
| // "description": "Information about a Breakpoint created in setBreakpoints |
| // or setFunctionBreakpoints.", |
| // "properties": { |
| // "id": { |
| // "type": "integer", |
| // "description": "An optional unique identifier for the breakpoint." |
| // }, |
| // "verified": { |
| // "type": "boolean", |
| // "description": "If true breakpoint could be set (but not necessarily |
| // at the desired location)." |
| // }, |
| // "message": { |
| // "type": "string", |
| // "description": "An optional message about the state of the breakpoint. |
| // This is shown to the user and can be used to explain |
| // why a breakpoint could not be verified." |
| // }, |
| // "source": { |
| // "$ref": "#/definitions/Source", |
| // "description": "The source where the breakpoint is located." |
| // }, |
| // "line": { |
| // "type": "integer", |
| // "description": "The start line of the actual range covered by the |
| // breakpoint." |
| // }, |
| // "column": { |
| // "type": "integer", |
| // "description": "An optional start column of the actual range covered |
| // by the breakpoint." |
| // }, |
| // "endLine": { |
| // "type": "integer", |
| // "description": "An optional end line of the actual range covered by |
| // the breakpoint." |
| // }, |
| // "endColumn": { |
| // "type": "integer", |
| // "description": "An optional end column of the actual range covered by |
| // the breakpoint. If no end line is given, then the end |
| // column is assumed to be in the start line." |
| // } |
| // }, |
| // "required": [ "verified" ] |
| // } |
| llvm::json::Value CreateBreakpoint(lldb::SBBreakpoint &bp, |
| llvm::Optional<llvm::StringRef> request_path, |
| llvm::Optional<uint32_t> request_line) { |
| // Each breakpoint location is treated as a separate breakpoint for VS code. |
| // They don't have the notion of a single breakpoint with multiple locations. |
| llvm::json::Object object; |
| if (!bp.IsValid()) |
| return llvm::json::Value(std::move(object)); |
| |
| object.try_emplace("verified", bp.GetNumResolvedLocations() > 0); |
| object.try_emplace("id", bp.GetID()); |
| // VS Code DAP doesn't currently allow one breakpoint to have multiple |
| // locations so we just report the first one. If we report all locations |
| // then the IDE starts showing the wrong line numbers and locations for |
| // other source file and line breakpoints in the same file. |
| |
| // Below we search for the first resolved location in a breakpoint and report |
| // this as the breakpoint location since it will have a complete location |
| // that is at least loaded in the current process. |
| lldb::SBBreakpointLocation bp_loc; |
| const auto num_locs = bp.GetNumLocations(); |
| for (size_t i = 0; i < num_locs; ++i) { |
| bp_loc = bp.GetLocationAtIndex(i); |
| if (bp_loc.IsResolved()) |
| break; |
| } |
| // If not locations are resolved, use the first location. |
| if (!bp_loc.IsResolved()) |
| bp_loc = bp.GetLocationAtIndex(0); |
| auto bp_addr = bp_loc.GetAddress(); |
| |
| if (request_path) |
| object.try_emplace("source", CreateSource(*request_path)); |
| |
| if (bp_addr.IsValid()) { |
| auto line_entry = bp_addr.GetLineEntry(); |
| const auto line = line_entry.GetLine(); |
| if (line != UINT32_MAX) |
| object.try_emplace("line", line); |
| object.try_emplace("source", CreateSource(line_entry)); |
| } |
| // We try to add request_line as a fallback |
| if (request_line) |
| object.try_emplace("line", *request_line); |
| return llvm::json::Value(std::move(object)); |
| } |
| |
| static uint64_t GetDebugInfoSizeInSection(lldb::SBSection section) { |
| uint64_t debug_info_size = 0; |
| llvm::StringRef section_name(section.GetName()); |
| if (section_name.startswith(".debug") || section_name.startswith("__debug") || |
| section_name.startswith(".apple") || section_name.startswith("__apple")) |
| debug_info_size += section.GetFileByteSize(); |
| size_t num_sub_sections = section.GetNumSubSections(); |
| for (size_t i = 0; i < num_sub_sections; i++) { |
| debug_info_size += |
| GetDebugInfoSizeInSection(section.GetSubSectionAtIndex(i)); |
| } |
| return debug_info_size; |
| } |
| |
| static uint64_t GetDebugInfoSize(lldb::SBModule module) { |
| uint64_t debug_info_size = 0; |
| size_t num_sections = module.GetNumSections(); |
| for (size_t i = 0; i < num_sections; i++) { |
| debug_info_size += GetDebugInfoSizeInSection(module.GetSectionAtIndex(i)); |
| } |
| return debug_info_size; |
| } |
| |
| static std::string ConvertDebugInfoSizeToString(uint64_t debug_info) { |
| std::ostringstream oss; |
| oss << std::fixed << std::setprecision(1); |
| if (debug_info < 1024) { |
| oss << debug_info << "B"; |
| } else if (debug_info < 1024 * 1024) { |
| double kb = double(debug_info) / 1024.0; |
| oss << kb << "KB"; |
| } else if (debug_info < 1024 * 1024 * 1024) { |
| double mb = double(debug_info) / (1024.0 * 1024.0); |
| oss << mb << "MB"; |
| } else { |
| double gb = double(debug_info) / (1024.0 * 1024.0 * 1024.0); |
| oss << gb << "GB"; |
| } |
| return oss.str(); |
| } |
| llvm::json::Value CreateModule(lldb::SBModule &module) { |
| llvm::json::Object object; |
| if (!module.IsValid()) |
| return llvm::json::Value(std::move(object)); |
| const char *uuid = module.GetUUIDString(); |
| object.try_emplace("id", uuid ? std::string(uuid) : std::string("")); |
| object.try_emplace("name", std::string(module.GetFileSpec().GetFilename())); |
| char module_path_arr[PATH_MAX]; |
| module.GetFileSpec().GetPath(module_path_arr, sizeof(module_path_arr)); |
| std::string module_path(module_path_arr); |
| object.try_emplace("path", module_path); |
| if (module.GetNumCompileUnits() > 0) { |
| std::string symbol_str = "Symbols loaded."; |
| std::string debug_info_size; |
| uint64_t debug_info = GetDebugInfoSize(module); |
| if (debug_info > 0) { |
| debug_info_size = ConvertDebugInfoSizeToString(debug_info); |
| } |
| object.try_emplace("symbolStatus", symbol_str); |
| object.try_emplace("debugInfoSize", debug_info_size); |
| char symbol_path_arr[PATH_MAX]; |
| module.GetSymbolFileSpec().GetPath(symbol_path_arr, |
| sizeof(symbol_path_arr)); |
| std::string symbol_path(symbol_path_arr); |
| object.try_emplace("symbolFilePath", symbol_path); |
| } else { |
| object.try_emplace("symbolStatus", "Symbols not found."); |
| } |
| std::string loaded_addr = std::to_string( |
| module.GetObjectFileHeaderAddress().GetLoadAddress(g_vsc.target)); |
| object.try_emplace("addressRange", loaded_addr); |
| std::string version_str; |
| uint32_t version_nums[3]; |
| uint32_t num_versions = |
| module.GetVersion(version_nums, sizeof(version_nums) / sizeof(uint32_t)); |
| for (uint32_t i = 0; i < num_versions; ++i) { |
| if (!version_str.empty()) |
| version_str += "."; |
| version_str += std::to_string(version_nums[i]); |
| } |
| if (!version_str.empty()) |
| object.try_emplace("version", version_str); |
| return llvm::json::Value(std::move(object)); |
| } |
| |
| void AppendBreakpoint(lldb::SBBreakpoint &bp, llvm::json::Array &breakpoints, |
| llvm::Optional<llvm::StringRef> request_path, |
| llvm::Optional<uint32_t> request_line) { |
| breakpoints.emplace_back(CreateBreakpoint(bp, request_path, request_line)); |
| } |
| |
| // "Event": { |
| // "allOf": [ { "$ref": "#/definitions/ProtocolMessage" }, { |
| // "type": "object", |
| // "description": "Server-initiated event.", |
| // "properties": { |
| // "type": { |
| // "type": "string", |
| // "enum": [ "event" ] |
| // }, |
| // "event": { |
| // "type": "string", |
| // "description": "Type of event." |
| // }, |
| // "body": { |
| // "type": [ "array", "boolean", "integer", "null", "number" , |
| // "object", "string" ], |
| // "description": "Event-specific information." |
| // } |
| // }, |
| // "required": [ "type", "event" ] |
| // }] |
| // }, |
| // "ProtocolMessage": { |
| // "type": "object", |
| // "description": "Base class of requests, responses, and events.", |
| // "properties": { |
| // "seq": { |
| // "type": "integer", |
| // "description": "Sequence number." |
| // }, |
| // "type": { |
| // "type": "string", |
| // "description": "Message type.", |
| // "_enum": [ "request", "response", "event" ] |
| // } |
| // }, |
| // "required": [ "seq", "type" ] |
| // } |
| llvm::json::Object CreateEventObject(const llvm::StringRef event_name) { |
| llvm::json::Object event; |
| event.try_emplace("seq", 0); |
| event.try_emplace("type", "event"); |
| EmplaceSafeString(event, "event", event_name); |
| return event; |
| } |
| |
| // "ExceptionBreakpointsFilter": { |
| // "type": "object", |
| // "description": "An ExceptionBreakpointsFilter is shown in the UI as an |
| // option for configuring how exceptions are dealt with.", |
| // "properties": { |
| // "filter": { |
| // "type": "string", |
| // "description": "The internal ID of the filter. This value is passed |
| // to the setExceptionBreakpoints request." |
| // }, |
| // "label": { |
| // "type": "string", |
| // "description": "The name of the filter. This will be shown in the UI." |
| // }, |
| // "default": { |
| // "type": "boolean", |
| // "description": "Initial value of the filter. If not specified a value |
| // 'false' is assumed." |
| // } |
| // }, |
| // "required": [ "filter", "label" ] |
| // } |
| llvm::json::Value |
| CreateExceptionBreakpointFilter(const ExceptionBreakpoint &bp) { |
| llvm::json::Object object; |
| EmplaceSafeString(object, "filter", bp.filter); |
| EmplaceSafeString(object, "label", bp.label); |
| object.try_emplace("default", bp.default_value); |
| return llvm::json::Value(std::move(object)); |
| } |
| |
| // "Source": { |
| // "type": "object", |
| // "description": "A Source is a descriptor for source code. It is returned |
| // from the debug adapter as part of a StackFrame and it is |
| // used by clients when specifying breakpoints.", |
| // "properties": { |
| // "name": { |
| // "type": "string", |
| // "description": "The short name of the source. Every source returned |
| // from the debug adapter has a name. When sending a |
| // source to the debug adapter this name is optional." |
| // }, |
| // "path": { |
| // "type": "string", |
| // "description": "The path of the source to be shown in the UI. It is |
| // only used to locate and load the content of the |
| // source if no sourceReference is specified (or its |
| // value is 0)." |
| // }, |
| // "sourceReference": { |
| // "type": "number", |
| // "description": "If sourceReference > 0 the contents of the source must |
| // be retrieved through the SourceRequest (even if a path |
| // is specified). A sourceReference is only valid for a |
| // session, so it must not be used to persist a source." |
| // }, |
| // "presentationHint": { |
| // "type": "string", |
| // "description": "An optional hint for how to present the source in the |
| // UI. A value of 'deemphasize' can be used to indicate |
| // that the source is not available or that it is |
| // skipped on stepping.", |
| // "enum": [ "normal", "emphasize", "deemphasize" ] |
| // }, |
| // "origin": { |
| // "type": "string", |
| // "description": "The (optional) origin of this source: possible values |
| // 'internal module', 'inlined content from source map', |
| // etc." |
| // }, |
| // "sources": { |
| // "type": "array", |
| // "items": { |
| // "$ref": "#/definitions/Source" |
| // }, |
| // "description": "An optional list of sources that are related to this |
| // source. These may be the source that generated this |
| // source." |
| // }, |
| // "adapterData": { |
| // "type":["array","boolean","integer","null","number","object","string"], |
| // "description": "Optional data that a debug adapter might want to loop |
| // through the client. The client should leave the data |
| // intact and persist it across sessions. The client |
| // should not interpret the data." |
| // }, |
| // "checksums": { |
| // "type": "array", |
| // "items": { |
| // "$ref": "#/definitions/Checksum" |
| // }, |
| // "description": "The checksums associated with this file." |
| // } |
| // } |
| // } |
| llvm::json::Value CreateSource(lldb::SBLineEntry &line_entry) { |
| llvm::json::Object object; |
| lldb::SBFileSpec file = line_entry.GetFileSpec(); |
| if (file.IsValid()) { |
| const char *name = file.GetFilename(); |
| if (name) |
| EmplaceSafeString(object, "name", name); |
| char path[PATH_MAX] = ""; |
| file.GetPath(path, sizeof(path)); |
| if (path[0]) { |
| EmplaceSafeString(object, "path", std::string(path)); |
| } |
| } |
| return llvm::json::Value(std::move(object)); |
| } |
| |
| llvm::json::Value CreateSource(llvm::StringRef source_path) { |
| llvm::json::Object source; |
| llvm::StringRef name = llvm::sys::path::filename(source_path); |
| EmplaceSafeString(source, "name", name); |
| EmplaceSafeString(source, "path", source_path); |
| return llvm::json::Value(std::move(source)); |
| } |
| |
| llvm::json::Value CreateSource(lldb::SBFrame &frame, int64_t &disasm_line) { |
| disasm_line = 0; |
| auto line_entry = frame.GetLineEntry(); |
| if (line_entry.GetFileSpec().IsValid()) |
| 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) { |
| EmplaceSafeString(object, "name", frame.GetFunctionName()); |
| 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)); |
| } |
| |
| // "StackFrame": { |
| // "type": "object", |
| // "description": "A Stackframe contains the source location.", |
| // "properties": { |
| // "id": { |
| // "type": "integer", |
| // "description": "An identifier for the stack frame. It must be unique |
| // across all threads. This id can be used to retrieve |
| // the scopes of the frame with the 'scopesRequest' or |
| // to restart the execution of a stackframe." |
| // }, |
| // "name": { |
| // "type": "string", |
| // "description": "The name of the stack frame, typically a method name." |
| // }, |
| // "source": { |
| // "$ref": "#/definitions/Source", |
| // "description": "The optional source of the frame." |
| // }, |
| // "line": { |
| // "type": "integer", |
| // "description": "The line within the file of the frame. If source is |
| // null or doesn't exist, line is 0 and must be ignored." |
| // }, |
| // "column": { |
| // "type": "integer", |
| // "description": "The column within the line. If source is null or |
| // doesn't exist, column is 0 and must be ignored." |
| // }, |
| // "endLine": { |
| // "type": "integer", |
| // "description": "An optional end line of the range covered by the |
| // stack frame." |
| // }, |
| // "endColumn": { |
| // "type": "integer", |
| // "description": "An optional end column of the range covered by the |
| // stack frame." |
| // }, |
| // "moduleId": { |
| // "type": ["integer", "string"], |
| // "description": "The module associated with this frame, if any." |
| // }, |
| // "presentationHint": { |
| // "type": "string", |
| // "enum": [ "normal", "label", "subtle" ], |
| // "description": "An optional hint for how to present this frame in |
| // the UI. A value of 'label' can be used to indicate |
| // that the frame is an artificial frame that is used |
| // as a visual label or separator. A value of 'subtle' |
| // can be used to change the appearance of a frame in |
| // a 'subtle' way." |
| // } |
| // }, |
| // "required": [ "id", "name", "line", "column" ] |
| // } |
| llvm::json::Value CreateStackFrame(lldb::SBFrame &frame) { |
| llvm::json::Object object; |
| int64_t frame_id = MakeVSCodeFrameID(frame); |
| object.try_emplace("id", frame_id); |
| EmplaceSafeString(object, "name", frame.GetFunctionName()); |
| int64_t disasm_line = 0; |
| object.try_emplace("source", CreateSource(frame, disasm_line)); |
| |
| auto line_entry = frame.GetLineEntry(); |
| if (disasm_line > 0) { |
| object.try_emplace("line", disasm_line); |
| } else { |
| auto line = line_entry.GetLine(); |
| if (line == UINT32_MAX) |
| line = 0; |
| object.try_emplace("line", line); |
| } |
| object.try_emplace("column", line_entry.GetColumn()); |
| return llvm::json::Value(std::move(object)); |
| } |
| |
| // "Thread": { |
| // "type": "object", |
| // "description": "A Thread", |
| // "properties": { |
| // "id": { |
| // "type": "integer", |
| // "description": "Unique identifier for the thread." |
| // }, |
| // "name": { |
| // "type": "string", |
| // "description": "A name of the thread." |
| // } |
| // }, |
| // "required": [ "id", "name" ] |
| // } |
| llvm::json::Value CreateThread(lldb::SBThread &thread) { |
| llvm::json::Object object; |
| object.try_emplace("id", (int64_t)thread.GetThreadID()); |
| char thread_str[64]; |
| snprintf(thread_str, sizeof(thread_str), "Thread #%u", thread.GetIndexID()); |
| const char *name = thread.GetName(); |
| if (name) { |
| std::string thread_with_name(thread_str); |
| thread_with_name += ' '; |
| thread_with_name += name; |
| EmplaceSafeString(object, "name", thread_with_name); |
| } else { |
| EmplaceSafeString(object, "name", std::string(thread_str)); |
| } |
| return llvm::json::Value(std::move(object)); |
| } |
| |
| // "StoppedEvent": { |
| // "allOf": [ { "$ref": "#/definitions/Event" }, { |
| // "type": "object", |
| // "description": "Event message for 'stopped' event type. The event |
| // indicates that the execution of the debuggee has stopped |
| // due to some condition. This can be caused by a break |
| // point previously set, a stepping action has completed, |
| // by executing a debugger statement etc.", |
| // "properties": { |
| // "event": { |
| // "type": "string", |
| // "enum": [ "stopped" ] |
| // }, |
| // "body": { |
| // "type": "object", |
| // "properties": { |
| // "reason": { |
| // "type": "string", |
| // "description": "The reason for the event. For backward |
| // compatibility this string is shown in the UI if |
| // the 'description' attribute is missing (but it |
| // must not be translated).", |
| // "_enum": [ "step", "breakpoint", "exception", "pause", "entry" ] |
| // }, |
| // "description": { |
| // "type": "string", |
| // "description": "The full reason for the event, e.g. 'Paused |
| // on exception'. This string is shown in the UI |
| // as is." |
| // }, |
| // "threadId": { |
| // "type": "integer", |
| // "description": "The thread which was stopped." |
| // }, |
| // "text": { |
| // "type": "string", |
| // "description": "Additional information. E.g. if reason is |
| // 'exception', text contains the exception name. |
| // This string is shown in the UI." |
| // }, |
| // "allThreadsStopped": { |
| // "type": "boolean", |
| // "description": "If allThreadsStopped is true, a debug adapter |
| // can announce that all threads have stopped. |
| // The client should use this information to |
| // enable that all threads can be expanded to |
| // access their stacktraces. If the attribute |
| // is missing or false, only the thread with the |
| // given threadId can be expanded." |
| // } |
| // }, |
| // "required": [ "reason" ] |
| // } |
| // }, |
| // "required": [ "event", "body" ] |
| // }] |
| // } |
| llvm::json::Value CreateThreadStopped(lldb::SBThread &thread, |
| uint32_t stop_id) { |
| llvm::json::Object event(CreateEventObject("stopped")); |
| llvm::json::Object body; |
| switch (thread.GetStopReason()) { |
| case lldb::eStopReasonTrace: |
| case lldb::eStopReasonPlanComplete: |
| body.try_emplace("reason", "step"); |
| break; |
| case lldb::eStopReasonBreakpoint: { |
| ExceptionBreakpoint *exc_bp = g_vsc.GetExceptionBPFromStopReason(thread); |
| if (exc_bp) { |
| body.try_emplace("reason", "exception"); |
| EmplaceSafeString(body, "description", exc_bp->label); |
| } else { |
| body.try_emplace("reason", "breakpoint"); |
| char desc_str[64]; |
| uint64_t bp_id = thread.GetStopReasonDataAtIndex(0); |
| uint64_t bp_loc_id = thread.GetStopReasonDataAtIndex(1); |
| snprintf(desc_str, sizeof(desc_str), "breakpoint %" PRIu64 ".%" PRIu64, |
| bp_id, bp_loc_id); |
| EmplaceSafeString(body, "description", desc_str); |
| } |
| } break; |
| case lldb::eStopReasonWatchpoint: |
| case lldb::eStopReasonInstrumentation: |
| body.try_emplace("reason", "breakpoint"); |
| break; |
| case lldb::eStopReasonProcessorTrace: |
| body.try_emplace("reason", "processor trace"); |
| break; |
| case lldb::eStopReasonSignal: |
| case lldb::eStopReasonException: |
| body.try_emplace("reason", "exception"); |
| break; |
| case lldb::eStopReasonExec: |
| body.try_emplace("reason", "entry"); |
| break; |
| case lldb::eStopReasonFork: |
| body.try_emplace("reason", "fork"); |
| break; |
| case lldb::eStopReasonVFork: |
| body.try_emplace("reason", "vfork"); |
| break; |
| case lldb::eStopReasonVForkDone: |
| body.try_emplace("reason", "vforkdone"); |
| break; |
| case lldb::eStopReasonThreadExiting: |
| case lldb::eStopReasonInvalid: |
| case lldb::eStopReasonNone: |
| break; |
| } |
| if (stop_id == 0) |
| body.try_emplace("reason", "entry"); |
| const lldb::tid_t tid = thread.GetThreadID(); |
| body.try_emplace("threadId", (int64_t)tid); |
| // If no description has been set, then set it to the default thread stopped |
| // description. If we have breakpoints that get hit and shouldn't be reported |
| // as breakpoints, then they will set the description above. |
| if (ObjectContainsKey(body, "description")) { |
| char description[1024]; |
| if (thread.GetStopDescription(description, sizeof(description))) { |
| EmplaceSafeString(body, "description", std::string(description)); |
| } |
| } |
| if (tid == g_vsc.focus_tid) { |
| body.try_emplace("threadCausedFocus", true); |
| } |
| body.try_emplace("preserveFocusHint", tid != g_vsc.focus_tid); |
| body.try_emplace("allThreadsStopped", true); |
| event.try_emplace("body", std::move(body)); |
| return llvm::json::Value(std::move(event)); |
| } |
| |
| const char *GetNonNullVariableName(lldb::SBValue v) { |
| const char *name = v.GetName(); |
| return name ? name : "<null>"; |
| } |
| |
| std::string CreateUniqueVariableNameForDisplay(lldb::SBValue v, |
| bool is_name_duplicated) { |
| lldb::SBStream name_builder; |
| name_builder.Print(GetNonNullVariableName(v)); |
| if (is_name_duplicated) { |
| lldb::SBDeclaration declaration = v.GetDeclaration(); |
| const char *file_name = declaration.GetFileSpec().GetFilename(); |
| const uint32_t line = declaration.GetLine(); |
| |
| if (file_name != nullptr && line > 0) |
| name_builder.Printf(" @ %s:%u", file_name, line); |
| else if (const char *location = v.GetLocation()) |
| name_builder.Printf(" @ %s", location); |
| } |
| return name_builder.GetData(); |
| } |
| |
| // "Variable": { |
| // "type": "object", |
| // "description": "A Variable is a name/value pair. Optionally a variable |
| // can have a 'type' that is shown if space permits or when |
| // hovering over the variable's name. An optional 'kind' is |
| // used to render additional properties of the variable, |
| // e.g. different icons can be used to indicate that a |
| // variable is public or private. If the value is |
| // structured (has children), a handle is provided to |
| // retrieve the children with the VariablesRequest. If |
| // the number of named or indexed children is large, the |
| // numbers should be returned via the optional |
| // 'namedVariables' and 'indexedVariables' attributes. The |
| // client can use this optional information to present the |
| // children in a paged UI and fetch them in chunks.", |
| // "properties": { |
| // "name": { |
| // "type": "string", |
| // "description": "The variable's name." |
| // }, |
| // "value": { |
| // "type": "string", |
| // "description": "The variable's value. This can be a multi-line text, |
| // e.g. for a function the body of a function." |
| // }, |
| // "type": { |
| // "type": "string", |
| // "description": "The type of the variable's value. Typically shown in |
| // the UI when hovering over the value." |
| // }, |
| // "presentationHint": { |
| // "$ref": "#/definitions/VariablePresentationHint", |
| // "description": "Properties of a variable that can be used to determine |
| // how to render the variable in the UI." |
| // }, |
| // "evaluateName": { |
| // "type": "string", |
| // "description": "Optional evaluatable name of this variable which can |
| // be passed to the 'EvaluateRequest' to fetch the |
| // variable's value." |
| // }, |
| // "variablesReference": { |
| // "type": "integer", |
| // "description": "If variablesReference is > 0, the variable is |
| // structured and its children can be retrieved by |
| // passing variablesReference to the VariablesRequest." |
| // }, |
| // "namedVariables": { |
| // "type": "integer", |
| // "description": "The number of named child variables. The client can |
| // use this optional information to present the children |
| // in a paged UI and fetch them in chunks." |
| // }, |
| // "indexedVariables": { |
| // "type": "integer", |
| // "description": "The number of indexed child variables. The client |
| // can use this optional information to present the |
| // children in a paged UI and fetch them in chunks." |
| // } |
| // }, |
| // "required": [ "name", "value", "variablesReference" ] |
| // } |
| llvm::json::Value CreateVariable(lldb::SBValue v, int64_t variablesReference, |
| int64_t varID, bool format_hex, |
| bool is_name_duplicated) { |
| llvm::json::Object object; |
| EmplaceSafeString(object, "name", |
| CreateUniqueVariableNameForDisplay(v, is_name_duplicated)); |
| |
| if (format_hex) |
| v.SetFormat(lldb::eFormatHex); |
| SetValueForKey(v, object, "value"); |
| auto type_cstr = v.GetType().GetDisplayTypeName(); |
| EmplaceSafeString(object, "type", type_cstr ? type_cstr : NO_TYPENAME); |
| if (varID != INT64_MAX) |
| object.try_emplace("id", varID); |
| if (v.MightHaveChildren()) |
| object.try_emplace("variablesReference", variablesReference); |
| else |
| object.try_emplace("variablesReference", (int64_t)0); |
| lldb::SBStream evaluateStream; |
| v.GetExpressionPath(evaluateStream); |
| const char *evaluateName = evaluateStream.GetData(); |
| if (evaluateName && evaluateName[0]) |
| EmplaceSafeString(object, "evaluateName", std::string(evaluateName)); |
| return llvm::json::Value(std::move(object)); |
| } |
| |
| llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit unit) { |
| llvm::json::Object object; |
| char unit_path_arr[PATH_MAX]; |
| unit.GetFileSpec().GetPath(unit_path_arr, sizeof(unit_path_arr)); |
| std::string unit_path(unit_path_arr); |
| object.try_emplace("compileUnitPath", unit_path); |
| return llvm::json::Value(std::move(object)); |
| } |
| |
| /// See |
| /// https://microsoft.github.io/debug-adapter-protocol/specification#Reverse_Requests_RunInTerminal |
| llvm::json::Object |
| CreateRunInTerminalReverseRequest(const llvm::json::Object &launch_request, |
| llvm::StringRef debug_adaptor_path, |
| llvm::StringRef comm_file) { |
| llvm::json::Object reverse_request; |
| reverse_request.try_emplace("type", "request"); |
| reverse_request.try_emplace("command", "runInTerminal"); |
| |
| llvm::json::Object run_in_terminal_args; |
| // This indicates the IDE to open an embedded terminal, instead of opening the |
| // terminal in a new window. |
| run_in_terminal_args.try_emplace("kind", "integrated"); |
| |
| auto launch_request_arguments = launch_request.getObject("arguments"); |
| // The program path must be the first entry in the "args" field |
| std::vector<std::string> args = { |
| debug_adaptor_path.str(), "--comm-file", comm_file.str(), |
| "--launch-target", GetString(launch_request_arguments, "program").str()}; |
| std::vector<std::string> target_args = |
| GetStrings(launch_request_arguments, "args"); |
| args.insert(args.end(), target_args.begin(), target_args.end()); |
| run_in_terminal_args.try_emplace("args", args); |
| |
| const auto cwd = GetString(launch_request_arguments, "cwd"); |
| if (!cwd.empty()) |
| run_in_terminal_args.try_emplace("cwd", cwd); |
| |
| // We need to convert the input list of environments variables into a |
| // dictionary |
| std::vector<std::string> envs = GetStrings(launch_request_arguments, "env"); |
| llvm::json::Object environment; |
| for (const std::string &env : envs) { |
| size_t index = env.find('='); |
| environment.try_emplace(env.substr(0, index), env.substr(index + 1)); |
| } |
| run_in_terminal_args.try_emplace("env", |
| llvm::json::Value(std::move(environment))); |
| |
| reverse_request.try_emplace( |
| "arguments", llvm::json::Value(std::move(run_in_terminal_args))); |
| return reverse_request; |
| } |
| |
| std::string JSONToString(const llvm::json::Value &json) { |
| std::string data; |
| llvm::raw_string_ostream os(data); |
| os << json; |
| os.flush(); |
| return data; |
| } |
| |
| } // namespace lldb_vscode |