//===-- 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 "JSONUtils.h" #include "DAP.h" #include "ExceptionBreakpoint.h" #include "LLDBUtils.h" #include "ProtocolUtils.h" #include "lldb/API/SBAddress.h" #include "lldb/API/SBCompileUnit.h" #include "lldb/API/SBDeclaration.h" #include "lldb/API/SBEnvironment.h" #include "lldb/API/SBError.h" #include "lldb/API/SBFileSpec.h" #include "lldb/API/SBFrame.h" #include "lldb/API/SBFunction.h" #include "lldb/API/SBInstructionList.h" #include "lldb/API/SBLineEntry.h" #include "lldb/API/SBModule.h" #include "lldb/API/SBQueue.h" #include "lldb/API/SBSection.h" #include "lldb/API/SBStream.h" #include "lldb/API/SBStringList.h" #include "lldb/API/SBStructuredData.h" #include "lldb/API/SBTarget.h" #include "lldb/API/SBThread.h" #include "lldb/API/SBType.h" #include "lldb/API/SBValue.h" #include "lldb/Host/PosixApi.h" // IWYU pragma: keep #include "lldb/lldb-defines.h" #include "lldb/lldb-enumerations.h" #include "lldb/lldb-types.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Compiler.h" #include "llvm/Support/Format.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/JSON.h" #include "llvm/Support/Path.h" #include "llvm/Support/ScopedPrinter.h" #include "llvm/Support/raw_ostream.h" #include #include #include #include #include #include #include #include namespace lldb_dap { 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. std::optional GetString(const llvm::json::Object &obj, llvm::StringRef key) { return obj.getString(key); } std::optional GetString(const llvm::json::Object *obj, llvm::StringRef key) { if (obj == nullptr) return std::nullopt; return GetString(*obj, key); } std::optional GetBoolean(const llvm::json::Object &obj, llvm::StringRef key) { if (auto value = obj.getBoolean(key)) return *value; if (auto value = obj.getInteger(key)) return *value != 0; return std::nullopt; } std::optional GetBoolean(const llvm::json::Object *obj, llvm::StringRef key) { if (obj != nullptr) return GetBoolean(*obj, key); return std::nullopt; } bool ObjectContainsKey(const llvm::json::Object &obj, llvm::StringRef key) { return obj.find(key) != obj.end(); } std::string EncodeMemoryReference(lldb::addr_t addr) { return "0x" + llvm::utohexstr(addr); } std::optional DecodeMemoryReference(llvm::StringRef memoryReference) { if (!memoryReference.starts_with("0x")) return std::nullopt; lldb::addr_t addr; if (memoryReference.consumeInteger(0, addr)) return std::nullopt; return addr; } std::vector GetStrings(const llvm::json::Object *obj, llvm::StringRef key) { std::vector strs; const 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; } std::unordered_map GetStringMap(const llvm::json::Object &obj, llvm::StringRef key) { std::unordered_map strs; const auto *const json_object = obj.getObject(key); if (!json_object) return strs; for (const auto &[key, value] : *json_object) { switch (value.kind()) { case llvm::json::Value::String: strs.emplace(key.str(), value.getAsString()->str()); break; case llvm::json::Value::Number: case llvm::json::Value::Boolean: strs.emplace(key.str(), llvm::to_string(value)); break; case llvm::json::Value::Null: case llvm::json::Value::Object: case llvm::json::Value::Array: break; } } return strs; } static bool IsClassStructOrUnionType(lldb::SBType t) { return (t.GetTypeClass() & (lldb::eTypeClassUnion | lldb::eTypeClassStruct | lldb::eTypeClassArray)) != 0; } /// Create a short summary for a container that contains the summary of its /// first children, so that the user can get a glimpse of its contents at a /// glance. static std::optional TryCreateAutoSummaryForContainer(lldb::SBValue &v) { if (!v.MightHaveChildren()) return std::nullopt; /// As this operation can be potentially slow, we limit the total time spent /// fetching children to a few ms. const auto max_evaluation_time = std::chrono::milliseconds(10); /// We don't want to generate a extremely long summary string, so we limit its /// length. const size_t max_length = 32; auto start = std::chrono::steady_clock::now(); std::string summary; llvm::raw_string_ostream os(summary); os << "{"; llvm::StringRef separator = ""; for (size_t i = 0, e = v.GetNumChildren(); i < e; ++i) { // If we reached the time limit or exceeded the number of characters, we // dump `...` to signal that there are more elements in the collection. if (summary.size() > max_length || (std::chrono::steady_clock::now() - start) > max_evaluation_time) { os << separator << "..."; break; } lldb::SBValue child = v.GetChildAtIndex(i); if (llvm::StringRef name = child.GetName(); !name.empty()) { llvm::StringRef desc; if (llvm::StringRef summary = child.GetSummary(); !summary.empty()) desc = summary; else if (llvm::StringRef value = child.GetValue(); !value.empty()) desc = value; else if (IsClassStructOrUnionType(child.GetType())) desc = "{...}"; else continue; // If the child is an indexed entry, we don't show its index to save // characters. if (name.starts_with("[")) os << separator << desc; else os << separator << name << ":" << desc; separator = ", "; } } os << "}"; if (summary == "{...}" || summary == "{}") return std::nullopt; return summary; } /// Try to create a summary string for the given value that doesn't have a /// summary of its own. static std::optional TryCreateAutoSummary(lldb::SBValue &value) { // We use the dereferenced value for generating the summary. if (value.GetType().IsPointerType() || value.GetType().IsReferenceType()) value = value.Dereference(); // We only support auto summaries for containers. return TryCreateAutoSummaryForContainer(value); } 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").value_or("")); const uint64_t seq = GetInteger(request, "seq").value_or(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-dap 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)); } static uint64_t GetDebugInfoSizeInSection(lldb::SBSection section) { uint64_t debug_info_size = 0; llvm::StringRef section_name(section.GetName()); if (section_name.starts_with(".debug") || section_name.starts_with("__debug") || section_name.starts_with(".apple") || section_name.starts_with("__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::SBTarget &target, lldb::SBModule &module, bool id_only) { llvm::json::Object object; if (!target.IsValid() || !module.IsValid()) return llvm::json::Value(std::move(object)); const char *uuid = module.GetUUIDString(); object.try_emplace("id", uuid ? std::string(uuid) : std::string("")); if (id_only) return llvm::json::Value(std::move(object)); 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 load_address = llvm::formatv("{0:x}", module.GetObjectFileHeaderAddress().GetLoadAddress(target)) .str(); object.try_emplace("addressRange", load_address); 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)); } // "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; } // "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." // }, // "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." // }, // "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(DAP &dap, lldb::SBFrame &frame, lldb::SBFormat &format) { llvm::json::Object object; int64_t frame_id = MakeDAPFrameID(frame); object.try_emplace("id", frame_id); std::string frame_name; lldb::SBStream stream; if (format && frame.GetDescriptionWithFormat(format, stream).Success()) { frame_name = stream.GetData(); // `function_name` can be a nullptr, which throws an error when assigned to // an `std::string`. } else if (const char *name = frame.GetDisplayFunctionName()) { frame_name = name; } if (frame_name.empty()) { // If the function name is unavailable, display the pc address as a 16-digit // hex string, e.g. "0x0000000000012345" frame_name = GetLoadAddressString(frame.GetPC()); } // We only include `[opt]` if a custom frame format is not specified. if (!format && frame.GetFunction().GetIsOptimized()) frame_name += " [opt]"; EmplaceSafeString(object, "name", frame_name); auto target = frame.GetThread().GetProcess().GetTarget(); std::optional source = dap.ResolveSource(frame.GetPCAddress()); if (source && !IsAssemblySource(*source)) { // This is a normal source with a valid line entry. auto line_entry = frame.GetLineEntry(); object.try_emplace("line", line_entry.GetLine()); auto column = line_entry.GetColumn(); object.try_emplace("column", column); } else if (frame.GetSymbol().IsValid()) { // This is a source where the disassembly is used, but there is a valid // symbol. Calculate the line of the current PC from the start of the // current symbol. lldb::SBTarget target = frame.GetThread().GetProcess().GetTarget(); lldb::SBInstructionList inst_list = target.ReadInstructions( frame.GetSymbol().GetStartAddress(), frame.GetPCAddress(), nullptr); size_t inst_line = inst_list.GetSize(); // Line numbers are 1-based. object.try_emplace("line", inst_line + 1); object.try_emplace("column", 1); } else { // No valid line entry or symbol. object.try_emplace("line", 1); object.try_emplace("column", 1); } if (source) object.try_emplace("source", std::move(source).value()); const auto pc = frame.GetPC(); if (pc != LLDB_INVALID_ADDRESS) { std::string formatted_addr = "0x" + llvm::utohexstr(pc); object.try_emplace("instructionPointerReference", formatted_addr); } if (frame.IsArtificial() || frame.IsHidden()) object.try_emplace("presentationHint", "subtle"); return llvm::json::Value(std::move(object)); } llvm::json::Value CreateExtendedStackFrameLabel(lldb::SBThread &thread, lldb::SBFormat &format) { std::string name; lldb::SBStream stream; if (format && thread.GetDescriptionWithFormat(format, stream).Success()) { name = stream.GetData(); } else { const uint32_t thread_idx = thread.GetExtendedBacktraceOriginatingIndexID(); const char *queue_name = thread.GetQueueName(); if (queue_name != nullptr) { name = llvm::formatv("Enqueued from {0} (Thread {1})", queue_name, thread_idx); } else { name = llvm::formatv("Thread {0}", thread_idx); } } return llvm::json::Value(llvm::json::Object{{"id", thread.GetThreadID() + 1}, {"name", name}, {"presentationHint", "label"}}); } // "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(DAP &dap, 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 = dap.GetExceptionBPFromStopReason(thread); if (exc_bp) { body.try_emplace("reason", "exception"); EmplaceSafeString(body, "description", exc_bp->GetLabel()); } else { InstructionBreakpoint *inst_bp = dap.GetInstructionBPFromStopReason(thread); if (inst_bp) { body.try_emplace("reason", "instruction breakpoint"); } else { body.try_emplace("reason", "breakpoint"); } lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(0); lldb::break_id_t bp_loc_id = thread.GetStopReasonDataAtIndex(1); std::string desc_str = llvm::formatv("breakpoint {0}.{1}", bp_id, bp_loc_id); body.try_emplace("hitBreakpointIds", llvm::json::Array{llvm::json::Value(bp_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::eStopReasonHistoryBoundary: body.try_emplace("reason", "history boundary"); 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::eStopReasonInterrupt: body.try_emplace("reason", "async interrupt"); 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", description); } } // "threadCausedFocus" is used in tests to validate breaking behavior. if (tid == dap.focus_tid) { body.try_emplace("threadCausedFocus", true); } body.try_emplace("preserveFocusHint", tid != dap.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 : ""; } 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(); } VariableDescription::VariableDescription(lldb::SBValue v, bool auto_variable_summaries, bool format_hex, bool is_name_duplicated, std::optional custom_name) : v(v) { name = custom_name ? *custom_name : CreateUniqueVariableNameForDisplay(v, is_name_duplicated); type_obj = v.GetType(); std::string raw_display_type_name = llvm::StringRef(type_obj.GetDisplayTypeName()).str(); display_type_name = !raw_display_type_name.empty() ? raw_display_type_name : NO_TYPENAME; // Only format hex/default if there is no existing special format. if (v.GetFormat() == lldb::eFormatDefault || v.GetFormat() == lldb::eFormatHex) { if (format_hex) v.SetFormat(lldb::eFormatHex); else v.SetFormat(lldb::eFormatDefault); } llvm::raw_string_ostream os_display_value(display_value); if (lldb::SBError sb_error = v.GetError(); sb_error.Fail()) { error = sb_error.GetCString(); os_display_value << ""; } else { value = llvm::StringRef(v.GetValue()).str(); summary = llvm::StringRef(v.GetSummary()).str(); if (summary.empty() && auto_variable_summaries) auto_summary = TryCreateAutoSummary(v); std::optional effective_summary = !summary.empty() ? summary : auto_summary; if (!value.empty()) { os_display_value << value; if (effective_summary) os_display_value << " " << *effective_summary; } else if (effective_summary) { os_display_value << *effective_summary; // As last resort, we print its type and address if available. } else { if (!raw_display_type_name.empty()) { os_display_value << raw_display_type_name; lldb::addr_t address = v.GetLoadAddress(); if (address != LLDB_INVALID_ADDRESS) os_display_value << " @ " << llvm::format_hex(address, 0); } } } lldb::SBStream evaluateStream; v.GetExpressionPath(evaluateStream); evaluate_name = llvm::StringRef(evaluateStream.GetData()).str(); } llvm::json::Object VariableDescription::GetVariableExtensionsJSON() { llvm::json::Object extensions; if (error) EmplaceSafeString(extensions, "error", *error); if (!value.empty()) EmplaceSafeString(extensions, "value", value); if (!summary.empty()) EmplaceSafeString(extensions, "summary", summary); if (auto_summary) EmplaceSafeString(extensions, "autoSummary", *auto_summary); if (lldb::SBDeclaration decl = v.GetDeclaration(); decl.IsValid()) { llvm::json::Object decl_obj; if (lldb::SBFileSpec file = decl.GetFileSpec(); file.IsValid()) { char path[PATH_MAX] = ""; if (file.GetPath(path, sizeof(path)) && lldb::SBFileSpec::ResolvePath(path, path, PATH_MAX)) { decl_obj.try_emplace("path", std::string(path)); } } if (int line = decl.GetLine()) decl_obj.try_emplace("line", line); if (int column = decl.GetColumn()) decl_obj.try_emplace("column", column); if (!decl_obj.empty()) extensions.try_emplace("declaration", std::move(decl_obj)); } return extensions; } std::string VariableDescription::GetResult(llvm::StringRef context) { // In repl context, the results can be displayed as multiple lines so more // detailed descriptions can be returned. if (context != "repl") return display_value; if (!v.IsValid()) return display_value; // Try the SBValue::GetDescription(), which may call into language runtime // specific formatters (see ValueObjectPrinter). lldb::SBStream stream; v.GetDescription(stream); llvm::StringRef description = stream.GetData(); return description.trim().str(); } bool ValuePointsToCode(lldb::SBValue v) { if (!v.GetType().GetPointeeType().IsFunctionType()) return false; lldb::addr_t addr = v.GetValueAsAddress(); lldb::SBLineEntry line_entry = v.GetTarget().ResolveLoadAddress(addr).GetLineEntry(); return line_entry.IsValid(); } int64_t PackLocation(int64_t var_ref, bool is_value_location) { return var_ref << 1 | is_value_location; } std::pair UnpackLocation(int64_t location_id) { return std::pair{location_id >> 1, location_id & 1}; } // "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." // }, // "memoryReference": { // "type": "string", // "description": "A memory reference associated with this variable. // For pointer type variables, this is generally a // reference to the memory address contained in the // pointer. For executable data, this reference may later // be used in a `disassemble` request. This attribute may // be returned by a debug adapter if corresponding // capability `supportsMemoryReferences` is true." // }, // "declarationLocationReference": { // "type": "integer", // "description": "A reference that allows the client to request the // location where the variable is declared. This should be // present only if the adapter is likely to be able to // resolve the location.\n\nThis reference shares the same // lifetime as the `variablesReference`. See 'Lifetime of // Object References' in the Overview section for // details." // }, // "valueLocationReference": { // "type": "integer", // "description": "A reference that allows the client to request the // location where the variable's value is declared. For // example, if the variable contains a function pointer, // the adapter may be able to look up the function's // location. This should be present only if the adapter // is likely to be able to resolve the location.\n\nThis // reference shares the same lifetime as the // `variablesReference`. See 'Lifetime of Object // References' in the Overview section for details." // }, // // "$__lldb_extensions": { // "description": "Unofficial extensions to the protocol", // "properties": { // "declaration": { // "type": "object", // "description": "The source location where the variable was // declared. This value won't be present if no // declaration is available. // Superseded by `declarationLocationReference`", // "properties": { // "path": { // "type": "string", // "description": "The source file path where the variable was // declared." // }, // "line": { // "type": "number", // "description": "The 1-indexed source line where the variable // was declared." // }, // "column": { // "type": "number", // "description": "The 1-indexed source column where the variable // was declared." // } // } // }, // "value": { // "type": "string", // "description": "The internal value of the variable as returned by // This is effectively SBValue.GetValue(). The other // `value` entry in the top-level variable response // is, on the other hand, just a display string for // the variable." // }, // "summary": { // "type": "string", // "description": "The summary string of the variable. This is // effectively SBValue.GetSummary()." // }, // "autoSummary": { // "type": "string", // "description": "The auto generated summary if using // `enableAutoVariableSummaries`." // }, // "error": { // "type": "string", // "description": "An error message generated if LLDB couldn't inspect // the variable." // } // } // } // }, // "required": [ "name", "value", "variablesReference" ] // } llvm::json::Value CreateVariable(lldb::SBValue v, int64_t var_ref, bool format_hex, bool auto_variable_summaries, bool synthetic_child_debugging, bool is_name_duplicated, std::optional custom_name) { VariableDescription desc(v, auto_variable_summaries, format_hex, is_name_duplicated, custom_name); llvm::json::Object object; EmplaceSafeString(object, "name", desc.name); EmplaceSafeString(object, "value", desc.display_value); if (!desc.evaluate_name.empty()) EmplaceSafeString(object, "evaluateName", desc.evaluate_name); // If we have a type with many children, we would like to be able to // give a hint to the IDE that the type has indexed children so that the // request can be broken up in grabbing only a few children at a time. We // want to be careful and only call "v.GetNumChildren()" if we have an array // type or if we have a synthetic child provider producing indexed children. // We don't want to call "v.GetNumChildren()" on all objects as class, struct // and union types don't need to be completed if they are never expanded. So // we want to avoid calling this to only cases where we it makes sense to keep // performance high during normal debugging. // If we have an array type, say that it is indexed and provide the number // of children in case we have a huge array. If we don't do this, then we // might take a while to produce all children at onces which can delay your // debug session. if (desc.type_obj.IsArrayType()) { object.try_emplace("indexedVariables", v.GetNumChildren()); } else if (v.IsSynthetic()) { // For a type with a synthetic child provider, the SBType of "v" won't tell // us anything about what might be displayed. Instead, we check if the first // child's name is "[0]" and then say it is indexed. We call // GetNumChildren() only if the child name matches to avoid a potentially // expensive operation. if (lldb::SBValue first_child = v.GetChildAtIndex(0)) { llvm::StringRef first_child_name = first_child.GetName(); if (first_child_name == "[0]") { size_t num_children = v.GetNumChildren(); // If we are creating a "[raw]" fake child for each synthetic type, we // have to account for it when returning indexed variables. if (synthetic_child_debugging) ++num_children; object.try_emplace("indexedVariables", num_children); } } } EmplaceSafeString(object, "type", desc.display_type_name); // A unique variable identifier to help in properly identifying variables with // the same name. This is an extension to the VS protocol. object.try_emplace("id", var_ref); if (v.MightHaveChildren()) object.try_emplace("variablesReference", var_ref); else object.try_emplace("variablesReference", 0); if (v.GetDeclaration().IsValid()) object.try_emplace("declarationLocationReference", PackLocation(var_ref, false)); if (ValuePointsToCode(v)) object.try_emplace("valueLocationReference", PackLocation(var_ref, true)); if (lldb::addr_t addr = v.GetLoadAddress(); addr != LLDB_INVALID_ADDRESS) object.try_emplace("memoryReference", EncodeMemoryReference(addr)); object.try_emplace("$__lldb_extensions", desc.GetVariableExtensionsJSON()); 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( llvm::StringRef program, const std::vector &args, const llvm::StringMap &env, llvm::StringRef cwd, llvm::StringRef comm_file, lldb::pid_t debugger_pid) { 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"); // The program path must be the first entry in the "args" field std::vector req_args = {DAP::debug_adapter_path.str(), "--comm-file", comm_file.str()}; if (debugger_pid != LLDB_INVALID_PROCESS_ID) { req_args.push_back("--debugger-pid"); req_args.push_back(std::to_string(debugger_pid)); } req_args.push_back("--launch-target"); req_args.push_back(program.str()); req_args.insert(req_args.end(), args.begin(), args.end()); run_in_terminal_args.try_emplace("args", req_args); if (!cwd.empty()) run_in_terminal_args.try_emplace("cwd", cwd); if (!env.empty()) { llvm::json::Object env_json; for (const auto &kv : env) { if (!kv.first().empty()) env_json.try_emplace(kv.first(), kv.second); } run_in_terminal_args.try_emplace("env", llvm::json::Value(std::move(env_json))); } return run_in_terminal_args; } // Keep all the top level items from the statistics dump, except for the // "modules" array. It can be huge and cause delay // Array and dictionary value will return as pairs static void FilterAndGetValueForKey(const lldb::SBStructuredData data, const char *key, llvm::json::Object &out) { lldb::SBStructuredData value = data.GetValueForKey(key); std::string key_utf8 = llvm::json::fixUTF8(key); if (llvm::StringRef(key) == "modules") return; switch (value.GetType()) { case lldb::eStructuredDataTypeFloat: out.try_emplace(key_utf8, value.GetFloatValue()); break; case lldb::eStructuredDataTypeUnsignedInteger: out.try_emplace(key_utf8, value.GetIntegerValue((uint64_t)0)); break; case lldb::eStructuredDataTypeSignedInteger: out.try_emplace(key_utf8, value.GetIntegerValue((int64_t)0)); break; case lldb::eStructuredDataTypeArray: { lldb::SBStream contents; value.GetAsJSON(contents); out.try_emplace(key_utf8, llvm::json::fixUTF8(contents.GetData())); } break; case lldb::eStructuredDataTypeBoolean: out.try_emplace(key_utf8, value.GetBooleanValue()); break; case lldb::eStructuredDataTypeString: { // Get the string size before reading const size_t str_length = value.GetStringValue(nullptr, 0); std::string str(str_length + 1, 0); value.GetStringValue(&str[0], str_length); out.try_emplace(key_utf8, llvm::json::fixUTF8(str)); } break; case lldb::eStructuredDataTypeDictionary: { lldb::SBStream contents; value.GetAsJSON(contents); out.try_emplace(key_utf8, llvm::json::fixUTF8(contents.GetData())); } break; case lldb::eStructuredDataTypeNull: case lldb::eStructuredDataTypeGeneric: case lldb::eStructuredDataTypeInvalid: break; } } static void addStatistic(lldb::SBTarget &target, llvm::json::Object &event) { lldb::SBStructuredData statistics = target.GetStatistics(); bool is_dictionary = statistics.GetType() == lldb::eStructuredDataTypeDictionary; if (!is_dictionary) return; llvm::json::Object stats_body; lldb::SBStringList keys; if (!statistics.GetKeys(keys)) return; for (size_t i = 0; i < keys.GetSize(); i++) { const char *key = keys.GetStringAtIndex(i); FilterAndGetValueForKey(statistics, key, stats_body); } llvm::json::Object body{{"$__lldb_statistics", std::move(stats_body)}}; event.try_emplace("body", std::move(body)); } llvm::json::Object CreateTerminatedEventObject(lldb::SBTarget &target) { llvm::json::Object event(CreateEventObject("terminated")); addStatistic(target, event); return event; } std::string JSONToString(const llvm::json::Value &json) { std::string data; llvm::raw_string_ostream os(data); os << json; return data; } } // namespace lldb_dap