1 | //===-- JSONUtils.cpp -------------------------------------------*- C++ -*-===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "JSONUtils.h" |
10 | #include "DAP.h" |
11 | #include "ExceptionBreakpoint.h" |
12 | #include "LLDBUtils.h" |
13 | #include "ProtocolUtils.h" |
14 | #include "lldb/API/SBAddress.h" |
15 | #include "lldb/API/SBCompileUnit.h" |
16 | #include "lldb/API/SBDeclaration.h" |
17 | #include "lldb/API/SBEnvironment.h" |
18 | #include "lldb/API/SBError.h" |
19 | #include "lldb/API/SBFileSpec.h" |
20 | #include "lldb/API/SBFrame.h" |
21 | #include "lldb/API/SBFunction.h" |
22 | #include "lldb/API/SBInstructionList.h" |
23 | #include "lldb/API/SBLineEntry.h" |
24 | #include "lldb/API/SBModule.h" |
25 | #include "lldb/API/SBQueue.h" |
26 | #include "lldb/API/SBSection.h" |
27 | #include "lldb/API/SBStream.h" |
28 | #include "lldb/API/SBStringList.h" |
29 | #include "lldb/API/SBStructuredData.h" |
30 | #include "lldb/API/SBTarget.h" |
31 | #include "lldb/API/SBThread.h" |
32 | #include "lldb/API/SBType.h" |
33 | #include "lldb/API/SBValue.h" |
34 | #include "lldb/Host/PosixApi.h" // IWYU pragma: keep |
35 | #include "lldb/lldb-defines.h" |
36 | #include "lldb/lldb-enumerations.h" |
37 | #include "lldb/lldb-types.h" |
38 | #include "llvm/ADT/DenseMap.h" |
39 | #include "llvm/ADT/StringExtras.h" |
40 | #include "llvm/ADT/StringRef.h" |
41 | #include "llvm/Support/Compiler.h" |
42 | #include "llvm/Support/Format.h" |
43 | #include "llvm/Support/FormatVariadic.h" |
44 | #include "llvm/Support/JSON.h" |
45 | #include "llvm/Support/Path.h" |
46 | #include "llvm/Support/ScopedPrinter.h" |
47 | #include "llvm/Support/raw_ostream.h" |
48 | #include <chrono> |
49 | #include <cstddef> |
50 | #include <iomanip> |
51 | #include <optional> |
52 | #include <sstream> |
53 | #include <string> |
54 | #include <utility> |
55 | #include <vector> |
56 | |
57 | namespace lldb_dap { |
58 | |
59 | void EmplaceSafeString(llvm::json::Object &obj, llvm::StringRef key, |
60 | llvm::StringRef str) { |
61 | if (LLVM_LIKELY(llvm::json::isUTF8(str))) |
62 | obj.try_emplace(K: key, Args: str.str()); |
63 | else |
64 | obj.try_emplace(K: key, Args: llvm::json::fixUTF8(S: str)); |
65 | } |
66 | |
67 | llvm::StringRef GetAsString(const llvm::json::Value &value) { |
68 | if (auto s = value.getAsString()) |
69 | return *s; |
70 | return llvm::StringRef(); |
71 | } |
72 | |
73 | // Gets a string from a JSON object using the key, or returns an empty string. |
74 | std::optional<llvm::StringRef> GetString(const llvm::json::Object &obj, |
75 | llvm::StringRef key) { |
76 | return obj.getString(K: key); |
77 | } |
78 | |
79 | std::optional<llvm::StringRef> GetString(const llvm::json::Object *obj, |
80 | llvm::StringRef key) { |
81 | if (obj == nullptr) |
82 | return std::nullopt; |
83 | |
84 | return GetString(obj: *obj, key); |
85 | } |
86 | |
87 | std::optional<bool> GetBoolean(const llvm::json::Object &obj, |
88 | llvm::StringRef key) { |
89 | if (auto value = obj.getBoolean(K: key)) |
90 | return *value; |
91 | if (auto value = obj.getInteger(K: key)) |
92 | return *value != 0; |
93 | return std::nullopt; |
94 | } |
95 | |
96 | std::optional<bool> GetBoolean(const llvm::json::Object *obj, |
97 | llvm::StringRef key) { |
98 | if (obj != nullptr) |
99 | return GetBoolean(obj: *obj, key); |
100 | return std::nullopt; |
101 | } |
102 | |
103 | bool ObjectContainsKey(const llvm::json::Object &obj, llvm::StringRef key) { |
104 | return obj.find(K: key) != obj.end(); |
105 | } |
106 | |
107 | std::string EncodeMemoryReference(lldb::addr_t addr) { |
108 | return "0x" + llvm::utohexstr(X: addr); |
109 | } |
110 | |
111 | std::optional<lldb::addr_t> |
112 | DecodeMemoryReference(llvm::StringRef memoryReference) { |
113 | if (!memoryReference.starts_with(Prefix: "0x" )) |
114 | return std::nullopt; |
115 | |
116 | lldb::addr_t addr; |
117 | if (memoryReference.consumeInteger(Radix: 0, Result&: addr)) |
118 | return std::nullopt; |
119 | |
120 | return addr; |
121 | } |
122 | |
123 | std::vector<std::string> GetStrings(const llvm::json::Object *obj, |
124 | llvm::StringRef key) { |
125 | std::vector<std::string> strs; |
126 | const auto *json_array = obj->getArray(K: key); |
127 | if (!json_array) |
128 | return strs; |
129 | for (const auto &value : *json_array) { |
130 | switch (value.kind()) { |
131 | case llvm::json::Value::String: |
132 | strs.push_back(x: value.getAsString()->str()); |
133 | break; |
134 | case llvm::json::Value::Number: |
135 | case llvm::json::Value::Boolean: |
136 | strs.push_back(x: llvm::to_string(Value: value)); |
137 | break; |
138 | case llvm::json::Value::Null: |
139 | case llvm::json::Value::Object: |
140 | case llvm::json::Value::Array: |
141 | break; |
142 | } |
143 | } |
144 | return strs; |
145 | } |
146 | |
147 | std::unordered_map<std::string, std::string> |
148 | GetStringMap(const llvm::json::Object &obj, llvm::StringRef key) { |
149 | std::unordered_map<std::string, std::string> strs; |
150 | const auto *const json_object = obj.getObject(K: key); |
151 | if (!json_object) |
152 | return strs; |
153 | |
154 | for (const auto &[key, value] : *json_object) { |
155 | switch (value.kind()) { |
156 | case llvm::json::Value::String: |
157 | strs.emplace(args: key.str(), args: value.getAsString()->str()); |
158 | break; |
159 | case llvm::json::Value::Number: |
160 | case llvm::json::Value::Boolean: |
161 | strs.emplace(args: key.str(), args: llvm::to_string(Value: value)); |
162 | break; |
163 | case llvm::json::Value::Null: |
164 | case llvm::json::Value::Object: |
165 | case llvm::json::Value::Array: |
166 | break; |
167 | } |
168 | } |
169 | return strs; |
170 | } |
171 | |
172 | static bool IsClassStructOrUnionType(lldb::SBType t) { |
173 | return (t.GetTypeClass() & (lldb::eTypeClassUnion | lldb::eTypeClassStruct | |
174 | lldb::eTypeClassArray)) != 0; |
175 | } |
176 | |
177 | /// Create a short summary for a container that contains the summary of its |
178 | /// first children, so that the user can get a glimpse of its contents at a |
179 | /// glance. |
180 | static std::optional<std::string> |
181 | TryCreateAutoSummaryForContainer(lldb::SBValue &v) { |
182 | if (!v.MightHaveChildren()) |
183 | return std::nullopt; |
184 | /// As this operation can be potentially slow, we limit the total time spent |
185 | /// fetching children to a few ms. |
186 | const auto max_evaluation_time = std::chrono::milliseconds(10); |
187 | /// We don't want to generate a extremely long summary string, so we limit its |
188 | /// length. |
189 | const size_t max_length = 32; |
190 | |
191 | auto start = std::chrono::steady_clock::now(); |
192 | std::string summary; |
193 | llvm::raw_string_ostream os(summary); |
194 | os << "{" ; |
195 | |
196 | llvm::StringRef separator = "" ; |
197 | |
198 | for (size_t i = 0, e = v.GetNumChildren(); i < e; ++i) { |
199 | // If we reached the time limit or exceeded the number of characters, we |
200 | // dump `...` to signal that there are more elements in the collection. |
201 | if (summary.size() > max_length || |
202 | (std::chrono::steady_clock::now() - start) > max_evaluation_time) { |
203 | os << separator << "..." ; |
204 | break; |
205 | } |
206 | lldb::SBValue child = v.GetChildAtIndex(idx: i); |
207 | |
208 | if (llvm::StringRef name = child.GetName(); !name.empty()) { |
209 | llvm::StringRef desc; |
210 | if (llvm::StringRef summary = child.GetSummary(); !summary.empty()) |
211 | desc = summary; |
212 | else if (llvm::StringRef value = child.GetValue(); !value.empty()) |
213 | desc = value; |
214 | else if (IsClassStructOrUnionType(t: child.GetType())) |
215 | desc = "{...}" ; |
216 | else |
217 | continue; |
218 | |
219 | // If the child is an indexed entry, we don't show its index to save |
220 | // characters. |
221 | if (name.starts_with(Prefix: "[" )) |
222 | os << separator << desc; |
223 | else |
224 | os << separator << name << ":" << desc; |
225 | separator = ", " ; |
226 | } |
227 | } |
228 | os << "}" ; |
229 | |
230 | if (summary == "{...}" || summary == "{}" ) |
231 | return std::nullopt; |
232 | return summary; |
233 | } |
234 | |
235 | /// Try to create a summary string for the given value that doesn't have a |
236 | /// summary of its own. |
237 | static std::optional<std::string> TryCreateAutoSummary(lldb::SBValue &value) { |
238 | // We use the dereferenced value for generating the summary. |
239 | if (value.GetType().IsPointerType() || value.GetType().IsReferenceType()) |
240 | value = value.Dereference(); |
241 | |
242 | // We only support auto summaries for containers. |
243 | return TryCreateAutoSummaryForContainer(v&: value); |
244 | } |
245 | |
246 | void FillResponse(const llvm::json::Object &request, |
247 | llvm::json::Object &response) { |
248 | // Fill in all of the needed response fields to a "request" and set "success" |
249 | // to true by default. |
250 | response.try_emplace(K: "type" , Args: "response" ); |
251 | response.try_emplace(K: "seq" , Args: (int64_t)0); |
252 | EmplaceSafeString(obj&: response, key: "command" , |
253 | str: GetString(obj: request, key: "command" ).value_or(u: "" )); |
254 | const uint64_t seq = GetInteger<uint64_t>(obj: request, key: "seq" ).value_or(u: 0); |
255 | response.try_emplace(K: "request_seq" , Args: seq); |
256 | response.try_emplace(K: "success" , Args: true); |
257 | } |
258 | |
259 | // "Scope": { |
260 | // "type": "object", |
261 | // "description": "A Scope is a named container for variables. Optionally |
262 | // a scope can map to a source or a range within a source.", |
263 | // "properties": { |
264 | // "name": { |
265 | // "type": "string", |
266 | // "description": "Name of the scope such as 'Arguments', 'Locals'." |
267 | // }, |
268 | // "presentationHint": { |
269 | // "type": "string", |
270 | // "description": "An optional hint for how to present this scope in the |
271 | // UI. If this attribute is missing, the scope is shown |
272 | // with a generic UI.", |
273 | // "_enum": [ "arguments", "locals", "registers" ], |
274 | // }, |
275 | // "variablesReference": { |
276 | // "type": "integer", |
277 | // "description": "The variables of this scope can be retrieved by |
278 | // passing the value of variablesReference to the |
279 | // VariablesRequest." |
280 | // }, |
281 | // "namedVariables": { |
282 | // "type": "integer", |
283 | // "description": "The number of named variables in this scope. The |
284 | // client can use this optional information to present |
285 | // the variables in a paged UI and fetch them in chunks." |
286 | // }, |
287 | // "indexedVariables": { |
288 | // "type": "integer", |
289 | // "description": "The number of indexed variables in this scope. The |
290 | // client can use this optional information to present |
291 | // the variables in a paged UI and fetch them in chunks." |
292 | // }, |
293 | // "expensive": { |
294 | // "type": "boolean", |
295 | // "description": "If true, the number of variables in this scope is |
296 | // large or expensive to retrieve." |
297 | // }, |
298 | // "source": { |
299 | // "$ref": "#/definitions/Source", |
300 | // "description": "Optional source for this scope." |
301 | // }, |
302 | // "line": { |
303 | // "type": "integer", |
304 | // "description": "Optional start line of the range covered by this |
305 | // scope." |
306 | // }, |
307 | // "column": { |
308 | // "type": "integer", |
309 | // "description": "Optional start column of the range covered by this |
310 | // scope." |
311 | // }, |
312 | // "endLine": { |
313 | // "type": "integer", |
314 | // "description": "Optional end line of the range covered by this scope." |
315 | // }, |
316 | // "endColumn": { |
317 | // "type": "integer", |
318 | // "description": "Optional end column of the range covered by this |
319 | // scope." |
320 | // } |
321 | // }, |
322 | // "required": [ "name", "variablesReference", "expensive" ] |
323 | // } |
324 | llvm::json::Value CreateScope(const llvm::StringRef name, |
325 | int64_t variablesReference, |
326 | int64_t namedVariables, bool expensive) { |
327 | llvm::json::Object object; |
328 | EmplaceSafeString(obj&: object, key: "name" , str: name.str()); |
329 | |
330 | // TODO: Support "arguments" scope. At the moment lldb-dap includes the |
331 | // arguments into the "locals" scope. |
332 | if (variablesReference == VARREF_LOCALS) { |
333 | object.try_emplace(K: "presentationHint" , Args: "locals" ); |
334 | } else if (variablesReference == VARREF_REGS) { |
335 | object.try_emplace(K: "presentationHint" , Args: "registers" ); |
336 | } |
337 | |
338 | object.try_emplace(K: "variablesReference" , Args&: variablesReference); |
339 | object.try_emplace(K: "expensive" , Args&: expensive); |
340 | object.try_emplace(K: "namedVariables" , Args&: namedVariables); |
341 | return llvm::json::Value(std::move(object)); |
342 | } |
343 | |
344 | static uint64_t GetDebugInfoSizeInSection(lldb::SBSection section) { |
345 | uint64_t debug_info_size = 0; |
346 | llvm::StringRef section_name(section.GetName()); |
347 | if (section_name.starts_with(Prefix: ".debug" ) || |
348 | section_name.starts_with(Prefix: "__debug" ) || |
349 | section_name.starts_with(Prefix: ".apple" ) || section_name.starts_with(Prefix: "__apple" )) |
350 | debug_info_size += section.GetFileByteSize(); |
351 | size_t num_sub_sections = section.GetNumSubSections(); |
352 | for (size_t i = 0; i < num_sub_sections; i++) { |
353 | debug_info_size += |
354 | GetDebugInfoSizeInSection(section: section.GetSubSectionAtIndex(idx: i)); |
355 | } |
356 | return debug_info_size; |
357 | } |
358 | |
359 | static uint64_t GetDebugInfoSize(lldb::SBModule module) { |
360 | uint64_t debug_info_size = 0; |
361 | size_t num_sections = module.GetNumSections(); |
362 | for (size_t i = 0; i < num_sections; i++) { |
363 | debug_info_size += GetDebugInfoSizeInSection(section: module.GetSectionAtIndex(idx: i)); |
364 | } |
365 | return debug_info_size; |
366 | } |
367 | |
368 | static std::string ConvertDebugInfoSizeToString(uint64_t debug_info) { |
369 | std::ostringstream oss; |
370 | oss << std::fixed << std::setprecision(1); |
371 | if (debug_info < 1024) { |
372 | oss << debug_info << "B" ; |
373 | } else if (debug_info < 1024 * 1024) { |
374 | double kb = double(debug_info) / 1024.0; |
375 | oss << kb << "KB" ; |
376 | } else if (debug_info < 1024 * 1024 * 1024) { |
377 | double mb = double(debug_info) / (1024.0 * 1024.0); |
378 | oss << mb << "MB" ; |
379 | } else { |
380 | double gb = double(debug_info) / (1024.0 * 1024.0 * 1024.0); |
381 | oss << gb << "GB" ; |
382 | } |
383 | return oss.str(); |
384 | } |
385 | |
386 | llvm::json::Value CreateModule(lldb::SBTarget &target, lldb::SBModule &module, |
387 | bool id_only) { |
388 | llvm::json::Object object; |
389 | if (!target.IsValid() || !module.IsValid()) |
390 | return llvm::json::Value(std::move(object)); |
391 | |
392 | const char *uuid = module.GetUUIDString(); |
393 | object.try_emplace(K: "id" , Args: uuid ? std::string(uuid) : std::string("" )); |
394 | |
395 | if (id_only) |
396 | return llvm::json::Value(std::move(object)); |
397 | |
398 | object.try_emplace(K: "name" , Args: std::string(module.GetFileSpec().GetFilename())); |
399 | char module_path_arr[PATH_MAX]; |
400 | module.GetFileSpec().GetPath(dst_path: module_path_arr, dst_len: sizeof(module_path_arr)); |
401 | std::string module_path(module_path_arr); |
402 | object.try_emplace(K: "path" , Args&: module_path); |
403 | if (module.GetNumCompileUnits() > 0) { |
404 | std::string symbol_str = "Symbols loaded." ; |
405 | std::string debug_info_size; |
406 | uint64_t debug_info = GetDebugInfoSize(module); |
407 | if (debug_info > 0) { |
408 | debug_info_size = ConvertDebugInfoSizeToString(debug_info); |
409 | } |
410 | object.try_emplace(K: "symbolStatus" , Args&: symbol_str); |
411 | object.try_emplace(K: "debugInfoSize" , Args&: debug_info_size); |
412 | char symbol_path_arr[PATH_MAX]; |
413 | module.GetSymbolFileSpec().GetPath(dst_path: symbol_path_arr, |
414 | dst_len: sizeof(symbol_path_arr)); |
415 | std::string symbol_path(symbol_path_arr); |
416 | object.try_emplace(K: "symbolFilePath" , Args&: symbol_path); |
417 | } else { |
418 | object.try_emplace(K: "symbolStatus" , Args: "Symbols not found." ); |
419 | } |
420 | std::string load_address = |
421 | llvm::formatv(Fmt: "{0:x}" , |
422 | Vals: module.GetObjectFileHeaderAddress().GetLoadAddress(target)) |
423 | .str(); |
424 | object.try_emplace(K: "addressRange" , Args&: load_address); |
425 | std::string version_str; |
426 | uint32_t version_nums[3]; |
427 | uint32_t num_versions = |
428 | module.GetVersion(versions: version_nums, num_versions: sizeof(version_nums) / sizeof(uint32_t)); |
429 | for (uint32_t i = 0; i < num_versions; ++i) { |
430 | if (!version_str.empty()) |
431 | version_str += "." ; |
432 | version_str += std::to_string(val: version_nums[i]); |
433 | } |
434 | if (!version_str.empty()) |
435 | object.try_emplace(K: "version" , Args&: version_str); |
436 | return llvm::json::Value(std::move(object)); |
437 | } |
438 | |
439 | // "Event": { |
440 | // "allOf": [ { "$ref": "#/definitions/ProtocolMessage" }, { |
441 | // "type": "object", |
442 | // "description": "Server-initiated event.", |
443 | // "properties": { |
444 | // "type": { |
445 | // "type": "string", |
446 | // "enum": [ "event" ] |
447 | // }, |
448 | // "event": { |
449 | // "type": "string", |
450 | // "description": "Type of event." |
451 | // }, |
452 | // "body": { |
453 | // "type": [ "array", "boolean", "integer", "null", "number" , |
454 | // "object", "string" ], |
455 | // "description": "Event-specific information." |
456 | // } |
457 | // }, |
458 | // "required": [ "type", "event" ] |
459 | // }] |
460 | // }, |
461 | // "ProtocolMessage": { |
462 | // "type": "object", |
463 | // "description": "Base class of requests, responses, and events.", |
464 | // "properties": { |
465 | // "seq": { |
466 | // "type": "integer", |
467 | // "description": "Sequence number." |
468 | // }, |
469 | // "type": { |
470 | // "type": "string", |
471 | // "description": "Message type.", |
472 | // "_enum": [ "request", "response", "event" ] |
473 | // } |
474 | // }, |
475 | // "required": [ "seq", "type" ] |
476 | // } |
477 | llvm::json::Object CreateEventObject(const llvm::StringRef event_name) { |
478 | llvm::json::Object event; |
479 | event.try_emplace(K: "seq" , Args: 0); |
480 | event.try_emplace(K: "type" , Args: "event" ); |
481 | EmplaceSafeString(obj&: event, key: "event" , str: event_name); |
482 | return event; |
483 | } |
484 | |
485 | protocol::ExceptionBreakpointsFilter |
486 | CreateExceptionBreakpointFilter(const ExceptionBreakpoint &bp) { |
487 | protocol::ExceptionBreakpointsFilter filter; |
488 | filter.filter = bp.GetFilter(); |
489 | filter.label = bp.GetLabel(); |
490 | filter.defaultState = ExceptionBreakpoint::kDefaultValue; |
491 | return filter; |
492 | } |
493 | |
494 | // "StackFrame": { |
495 | // "type": "object", |
496 | // "description": "A Stackframe contains the source location.", |
497 | // "properties": { |
498 | // "id": { |
499 | // "type": "integer", |
500 | // "description": "An identifier for the stack frame. It must be unique |
501 | // across all threads. This id can be used to retrieve |
502 | // the scopes of the frame with the 'scopesRequest' or |
503 | // to restart the execution of a stackframe." |
504 | // }, |
505 | // "name": { |
506 | // "type": "string", |
507 | // "description": "The name of the stack frame, typically a method name." |
508 | // }, |
509 | // "source": { |
510 | // "$ref": "#/definitions/Source", |
511 | // "description": "The optional source of the frame." |
512 | // }, |
513 | // "line": { |
514 | // "type": "integer", |
515 | // "description": "The line within the file of the frame. If source is |
516 | // null or doesn't exist, line is 0 and must be ignored." |
517 | // }, |
518 | // "column": { |
519 | // "type": "integer", |
520 | // "description": "The column within the line. If source is null or |
521 | // doesn't exist, column is 0 and must be ignored." |
522 | // }, |
523 | // "endLine": { |
524 | // "type": "integer", |
525 | // "description": "An optional end line of the range covered by the |
526 | // stack frame." |
527 | // }, |
528 | // "endColumn": { |
529 | // "type": "integer", |
530 | // "description": "An optional end column of the range covered by the |
531 | // stack frame." |
532 | // }, |
533 | // "instructionPointerReference": { |
534 | // "type": "string", |
535 | // "description": "A memory reference for the current instruction |
536 | // pointer in this frame." |
537 | // }, |
538 | // "moduleId": { |
539 | // "type": ["integer", "string"], |
540 | // "description": "The module associated with this frame, if any." |
541 | // }, |
542 | // "presentationHint": { |
543 | // "type": "string", |
544 | // "enum": [ "normal", "label", "subtle" ], |
545 | // "description": "An optional hint for how to present this frame in |
546 | // the UI. A value of 'label' can be used to indicate |
547 | // that the frame is an artificial frame that is used |
548 | // as a visual label or separator. A value of 'subtle' |
549 | // can be used to change the appearance of a frame in |
550 | // a 'subtle' way." |
551 | // } |
552 | // }, |
553 | // "required": [ "id", "name", "line", "column" ] |
554 | // } |
555 | llvm::json::Value CreateStackFrame(lldb::SBFrame &frame, |
556 | lldb::SBFormat &format) { |
557 | llvm::json::Object object; |
558 | int64_t frame_id = MakeDAPFrameID(frame); |
559 | object.try_emplace(K: "id" , Args&: frame_id); |
560 | |
561 | std::string frame_name; |
562 | lldb::SBStream stream; |
563 | if (format && frame.GetDescriptionWithFormat(format, output&: stream).Success()) { |
564 | frame_name = stream.GetData(); |
565 | |
566 | // `function_name` can be a nullptr, which throws an error when assigned to |
567 | // an `std::string`. |
568 | } else if (const char *name = frame.GetDisplayFunctionName()) { |
569 | frame_name = name; |
570 | } |
571 | |
572 | if (frame_name.empty()) { |
573 | // If the function name is unavailable, display the pc address as a 16-digit |
574 | // hex string, e.g. "0x0000000000012345" |
575 | frame_name = GetLoadAddressString(addr: frame.GetPC()); |
576 | } |
577 | |
578 | // We only include `[opt]` if a custom frame format is not specified. |
579 | if (!format && frame.GetFunction().GetIsOptimized()) |
580 | frame_name += " [opt]" ; |
581 | |
582 | EmplaceSafeString(obj&: object, key: "name" , str: frame_name); |
583 | |
584 | auto target = frame.GetThread().GetProcess().GetTarget(); |
585 | auto source = CreateSource(address: frame.GetPCAddress(), target); |
586 | if (!IsAssemblySource(source)) { |
587 | // This is a normal source with a valid line entry. |
588 | auto line_entry = frame.GetLineEntry(); |
589 | object.try_emplace(K: "line" , Args: line_entry.GetLine()); |
590 | auto column = line_entry.GetColumn(); |
591 | object.try_emplace(K: "column" , Args&: column); |
592 | } else if (frame.GetSymbol().IsValid()) { |
593 | // This is a source where the disassembly is used, but there is a valid |
594 | // symbol. Calculate the line of the current PC from the start of the |
595 | // current symbol. |
596 | lldb::SBTarget target = frame.GetThread().GetProcess().GetTarget(); |
597 | lldb::SBInstructionList inst_list = target.ReadInstructions( |
598 | start_addr: frame.GetSymbol().GetStartAddress(), end_addr: frame.GetPCAddress(), flavor_string: nullptr); |
599 | size_t inst_line = inst_list.GetSize(); |
600 | |
601 | // Line numbers are 1-based. |
602 | object.try_emplace(K: "line" , Args: inst_line + 1); |
603 | object.try_emplace(K: "column" , Args: 1); |
604 | } else { |
605 | // No valid line entry or symbol. |
606 | object.try_emplace(K: "line" , Args: 1); |
607 | object.try_emplace(K: "column" , Args: 1); |
608 | } |
609 | |
610 | object.try_emplace(K: "source" , Args: std::move(source)); |
611 | |
612 | const auto pc = frame.GetPC(); |
613 | if (pc != LLDB_INVALID_ADDRESS) { |
614 | std::string formatted_addr = "0x" + llvm::utohexstr(X: pc); |
615 | object.try_emplace(K: "instructionPointerReference" , Args&: formatted_addr); |
616 | } |
617 | |
618 | if (frame.IsArtificial() || frame.IsHidden()) |
619 | object.try_emplace(K: "presentationHint" , Args: "subtle" ); |
620 | |
621 | return llvm::json::Value(std::move(object)); |
622 | } |
623 | |
624 | llvm::json::Value CreateExtendedStackFrameLabel(lldb::SBThread &thread, |
625 | lldb::SBFormat &format) { |
626 | std::string name; |
627 | lldb::SBStream stream; |
628 | if (format && thread.GetDescriptionWithFormat(format, output&: stream).Success()) { |
629 | name = stream.GetData(); |
630 | } else { |
631 | const uint32_t thread_idx = thread.GetExtendedBacktraceOriginatingIndexID(); |
632 | const char *queue_name = thread.GetQueueName(); |
633 | if (queue_name != nullptr) { |
634 | name = llvm::formatv(Fmt: "Enqueued from {0} (Thread {1})" , Vals&: queue_name, |
635 | Vals: thread_idx); |
636 | } else { |
637 | name = llvm::formatv(Fmt: "Thread {0}" , Vals: thread_idx); |
638 | } |
639 | } |
640 | |
641 | return llvm::json::Value(llvm::json::Object{{.K: "id" , .V: thread.GetThreadID() + 1}, |
642 | {.K: "name" , .V: name}, |
643 | {.K: "presentationHint" , .V: "label" }}); |
644 | } |
645 | |
646 | // "StoppedEvent": { |
647 | // "allOf": [ { "$ref": "#/definitions/Event" }, { |
648 | // "type": "object", |
649 | // "description": "Event message for 'stopped' event type. The event |
650 | // indicates that the execution of the debuggee has stopped |
651 | // due to some condition. This can be caused by a break |
652 | // point previously set, a stepping action has completed, |
653 | // by executing a debugger statement etc.", |
654 | // "properties": { |
655 | // "event": { |
656 | // "type": "string", |
657 | // "enum": [ "stopped" ] |
658 | // }, |
659 | // "body": { |
660 | // "type": "object", |
661 | // "properties": { |
662 | // "reason": { |
663 | // "type": "string", |
664 | // "description": "The reason for the event. For backward |
665 | // compatibility this string is shown in the UI if |
666 | // the 'description' attribute is missing (but it |
667 | // must not be translated).", |
668 | // "_enum": [ "step", "breakpoint", "exception", "pause", "entry" ] |
669 | // }, |
670 | // "description": { |
671 | // "type": "string", |
672 | // "description": "The full reason for the event, e.g. 'Paused |
673 | // on exception'. This string is shown in the UI |
674 | // as is." |
675 | // }, |
676 | // "threadId": { |
677 | // "type": "integer", |
678 | // "description": "The thread which was stopped." |
679 | // }, |
680 | // "text": { |
681 | // "type": "string", |
682 | // "description": "Additional information. E.g. if reason is |
683 | // 'exception', text contains the exception name. |
684 | // This string is shown in the UI." |
685 | // }, |
686 | // "allThreadsStopped": { |
687 | // "type": "boolean", |
688 | // "description": "If allThreadsStopped is true, a debug adapter |
689 | // can announce that all threads have stopped. |
690 | // The client should use this information to |
691 | // enable that all threads can be expanded to |
692 | // access their stacktraces. If the attribute |
693 | // is missing or false, only the thread with the |
694 | // given threadId can be expanded." |
695 | // } |
696 | // }, |
697 | // "required": [ "reason" ] |
698 | // } |
699 | // }, |
700 | // "required": [ "event", "body" ] |
701 | // }] |
702 | // } |
703 | llvm::json::Value CreateThreadStopped(DAP &dap, lldb::SBThread &thread, |
704 | uint32_t stop_id) { |
705 | llvm::json::Object event(CreateEventObject(event_name: "stopped" )); |
706 | llvm::json::Object body; |
707 | switch (thread.GetStopReason()) { |
708 | case lldb::eStopReasonTrace: |
709 | case lldb::eStopReasonPlanComplete: |
710 | body.try_emplace(K: "reason" , Args: "step" ); |
711 | break; |
712 | case lldb::eStopReasonBreakpoint: { |
713 | ExceptionBreakpoint *exc_bp = dap.GetExceptionBPFromStopReason(thread); |
714 | if (exc_bp) { |
715 | body.try_emplace(K: "reason" , Args: "exception" ); |
716 | EmplaceSafeString(obj&: body, key: "description" , str: exc_bp->GetLabel()); |
717 | } else { |
718 | InstructionBreakpoint *inst_bp = |
719 | dap.GetInstructionBPFromStopReason(thread); |
720 | if (inst_bp) { |
721 | body.try_emplace(K: "reason" , Args: "instruction breakpoint" ); |
722 | } else { |
723 | body.try_emplace(K: "reason" , Args: "breakpoint" ); |
724 | } |
725 | lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(idx: 0); |
726 | lldb::break_id_t bp_loc_id = thread.GetStopReasonDataAtIndex(idx: 1); |
727 | std::string desc_str = |
728 | llvm::formatv(Fmt: "breakpoint {0}.{1}" , Vals&: bp_id, Vals&: bp_loc_id); |
729 | body.try_emplace(K: "hitBreakpointIds" , |
730 | Args: llvm::json::Array{llvm::json::Value(bp_id)}); |
731 | EmplaceSafeString(obj&: body, key: "description" , str: desc_str); |
732 | } |
733 | } break; |
734 | case lldb::eStopReasonWatchpoint: |
735 | case lldb::eStopReasonInstrumentation: |
736 | body.try_emplace(K: "reason" , Args: "breakpoint" ); |
737 | break; |
738 | case lldb::eStopReasonProcessorTrace: |
739 | body.try_emplace(K: "reason" , Args: "processor trace" ); |
740 | break; |
741 | case lldb::eStopReasonHistoryBoundary: |
742 | body.try_emplace(K: "reason" , Args: "history boundary" ); |
743 | break; |
744 | case lldb::eStopReasonSignal: |
745 | case lldb::eStopReasonException: |
746 | body.try_emplace(K: "reason" , Args: "exception" ); |
747 | break; |
748 | case lldb::eStopReasonExec: |
749 | body.try_emplace(K: "reason" , Args: "entry" ); |
750 | break; |
751 | case lldb::eStopReasonFork: |
752 | body.try_emplace(K: "reason" , Args: "fork" ); |
753 | break; |
754 | case lldb::eStopReasonVFork: |
755 | body.try_emplace(K: "reason" , Args: "vfork" ); |
756 | break; |
757 | case lldb::eStopReasonVForkDone: |
758 | body.try_emplace(K: "reason" , Args: "vforkdone" ); |
759 | break; |
760 | case lldb::eStopReasonInterrupt: |
761 | body.try_emplace(K: "reason" , Args: "async interrupt" ); |
762 | break; |
763 | case lldb::eStopReasonThreadExiting: |
764 | case lldb::eStopReasonInvalid: |
765 | case lldb::eStopReasonNone: |
766 | break; |
767 | } |
768 | if (stop_id == 0) |
769 | body.try_emplace(K: "reason" , Args: "entry" ); |
770 | const lldb::tid_t tid = thread.GetThreadID(); |
771 | body.try_emplace(K: "threadId" , Args: (int64_t)tid); |
772 | // If no description has been set, then set it to the default thread stopped |
773 | // description. If we have breakpoints that get hit and shouldn't be reported |
774 | // as breakpoints, then they will set the description above. |
775 | if (!ObjectContainsKey(obj: body, key: "description" )) { |
776 | char description[1024]; |
777 | if (thread.GetStopDescription(dst_or_null: description, dst_len: sizeof(description))) { |
778 | EmplaceSafeString(obj&: body, key: "description" , str: description); |
779 | } |
780 | } |
781 | // "threadCausedFocus" is used in tests to validate breaking behavior. |
782 | if (tid == dap.focus_tid) { |
783 | body.try_emplace(K: "threadCausedFocus" , Args: true); |
784 | } |
785 | body.try_emplace(K: "preserveFocusHint" , Args: tid != dap.focus_tid); |
786 | body.try_emplace(K: "allThreadsStopped" , Args: true); |
787 | event.try_emplace(K: "body" , Args: std::move(body)); |
788 | return llvm::json::Value(std::move(event)); |
789 | } |
790 | |
791 | const char *GetNonNullVariableName(lldb::SBValue &v) { |
792 | const char *name = v.GetName(); |
793 | return name ? name : "<null>" ; |
794 | } |
795 | |
796 | std::string CreateUniqueVariableNameForDisplay(lldb::SBValue &v, |
797 | bool is_name_duplicated) { |
798 | lldb::SBStream name_builder; |
799 | name_builder.Print(str: GetNonNullVariableName(v)); |
800 | if (is_name_duplicated) { |
801 | lldb::SBDeclaration declaration = v.GetDeclaration(); |
802 | const char *file_name = declaration.GetFileSpec().GetFilename(); |
803 | const uint32_t line = declaration.GetLine(); |
804 | |
805 | if (file_name != nullptr && line > 0) |
806 | name_builder.Printf(format: " @ %s:%u" , file_name, line); |
807 | else if (const char *location = v.GetLocation()) |
808 | name_builder.Printf(format: " @ %s" , location); |
809 | } |
810 | return name_builder.GetData(); |
811 | } |
812 | |
813 | VariableDescription::VariableDescription(lldb::SBValue v, |
814 | bool auto_variable_summaries, |
815 | bool format_hex, |
816 | bool is_name_duplicated, |
817 | std::optional<std::string> custom_name) |
818 | : v(v) { |
819 | name = custom_name |
820 | ? *custom_name |
821 | : CreateUniqueVariableNameForDisplay(v, is_name_duplicated); |
822 | |
823 | type_obj = v.GetType(); |
824 | std::string raw_display_type_name = |
825 | llvm::StringRef(type_obj.GetDisplayTypeName()).str(); |
826 | display_type_name = |
827 | !raw_display_type_name.empty() ? raw_display_type_name : NO_TYPENAME; |
828 | |
829 | // Only format hex/default if there is no existing special format. |
830 | if (v.GetFormat() == lldb::eFormatDefault || |
831 | v.GetFormat() == lldb::eFormatHex) { |
832 | if (format_hex) |
833 | v.SetFormat(lldb::eFormatHex); |
834 | else |
835 | v.SetFormat(lldb::eFormatDefault); |
836 | } |
837 | |
838 | llvm::raw_string_ostream os_display_value(display_value); |
839 | |
840 | if (lldb::SBError sb_error = v.GetError(); sb_error.Fail()) { |
841 | error = sb_error.GetCString(); |
842 | os_display_value << "<error: " << error << ">" ; |
843 | } else { |
844 | value = llvm::StringRef(v.GetValue()).str(); |
845 | summary = llvm::StringRef(v.GetSummary()).str(); |
846 | if (summary.empty() && auto_variable_summaries) |
847 | auto_summary = TryCreateAutoSummary(value&: v); |
848 | |
849 | std::optional<std::string> effective_summary = |
850 | !summary.empty() ? summary : auto_summary; |
851 | |
852 | if (!value.empty()) { |
853 | os_display_value << value; |
854 | if (effective_summary) |
855 | os_display_value << " " << *effective_summary; |
856 | } else if (effective_summary) { |
857 | os_display_value << *effective_summary; |
858 | |
859 | // As last resort, we print its type and address if available. |
860 | } else { |
861 | if (!raw_display_type_name.empty()) { |
862 | os_display_value << raw_display_type_name; |
863 | lldb::addr_t address = v.GetLoadAddress(); |
864 | if (address != LLDB_INVALID_ADDRESS) |
865 | os_display_value << " @ " << llvm::format_hex(N: address, Width: 0); |
866 | } |
867 | } |
868 | } |
869 | |
870 | lldb::SBStream evaluateStream; |
871 | v.GetExpressionPath(description&: evaluateStream); |
872 | evaluate_name = llvm::StringRef(evaluateStream.GetData()).str(); |
873 | } |
874 | |
875 | llvm::json::Object VariableDescription::GetVariableExtensionsJSON() { |
876 | llvm::json::Object extensions; |
877 | if (error) |
878 | EmplaceSafeString(obj&: extensions, key: "error" , str: *error); |
879 | if (!value.empty()) |
880 | EmplaceSafeString(obj&: extensions, key: "value" , str: value); |
881 | if (!summary.empty()) |
882 | EmplaceSafeString(obj&: extensions, key: "summary" , str: summary); |
883 | if (auto_summary) |
884 | EmplaceSafeString(obj&: extensions, key: "autoSummary" , str: *auto_summary); |
885 | |
886 | if (lldb::SBDeclaration decl = v.GetDeclaration(); decl.IsValid()) { |
887 | llvm::json::Object decl_obj; |
888 | if (lldb::SBFileSpec file = decl.GetFileSpec(); file.IsValid()) { |
889 | char path[PATH_MAX] = "" ; |
890 | if (file.GetPath(dst_path: path, dst_len: sizeof(path)) && |
891 | lldb::SBFileSpec::ResolvePath(src_path: path, dst_path: path, PATH_MAX)) { |
892 | decl_obj.try_emplace(K: "path" , Args: std::string(path)); |
893 | } |
894 | } |
895 | |
896 | if (int line = decl.GetLine()) |
897 | decl_obj.try_emplace(K: "line" , Args&: line); |
898 | if (int column = decl.GetColumn()) |
899 | decl_obj.try_emplace(K: "column" , Args&: column); |
900 | |
901 | if (!decl_obj.empty()) |
902 | extensions.try_emplace(K: "declaration" , Args: std::move(decl_obj)); |
903 | } |
904 | return extensions; |
905 | } |
906 | |
907 | std::string VariableDescription::GetResult(llvm::StringRef context) { |
908 | // In repl context, the results can be displayed as multiple lines so more |
909 | // detailed descriptions can be returned. |
910 | if (context != "repl" ) |
911 | return display_value; |
912 | |
913 | if (!v.IsValid()) |
914 | return display_value; |
915 | |
916 | // Try the SBValue::GetDescription(), which may call into language runtime |
917 | // specific formatters (see ValueObjectPrinter). |
918 | lldb::SBStream stream; |
919 | v.GetDescription(description&: stream); |
920 | llvm::StringRef description = stream.GetData(); |
921 | return description.trim().str(); |
922 | } |
923 | |
924 | bool ValuePointsToCode(lldb::SBValue v) { |
925 | if (!v.GetType().GetPointeeType().IsFunctionType()) |
926 | return false; |
927 | |
928 | lldb::addr_t addr = v.GetValueAsAddress(); |
929 | lldb::SBLineEntry line_entry = |
930 | v.GetTarget().ResolveLoadAddress(vm_addr: addr).GetLineEntry(); |
931 | |
932 | return line_entry.IsValid(); |
933 | } |
934 | |
935 | int64_t PackLocation(int64_t var_ref, bool is_value_location) { |
936 | return var_ref << 1 | is_value_location; |
937 | } |
938 | |
939 | std::pair<int64_t, bool> UnpackLocation(int64_t location_id) { |
940 | return std::pair{location_id >> 1, location_id & 1}; |
941 | } |
942 | |
943 | // "Variable": { |
944 | // "type": "object", |
945 | // "description": "A Variable is a name/value pair. Optionally a variable |
946 | // can have a 'type' that is shown if space permits or when |
947 | // hovering over the variable's name. An optional 'kind' is |
948 | // used to render additional properties of the variable, |
949 | // e.g. different icons can be used to indicate that a |
950 | // variable is public or private. If the value is |
951 | // structured (has children), a handle is provided to |
952 | // retrieve the children with the VariablesRequest. If |
953 | // the number of named or indexed children is large, the |
954 | // numbers should be returned via the optional |
955 | // 'namedVariables' and 'indexedVariables' attributes. The |
956 | // client can use this optional information to present the |
957 | // children in a paged UI and fetch them in chunks.", |
958 | // "properties": { |
959 | // "name": { |
960 | // "type": "string", |
961 | // "description": "The variable's name." |
962 | // }, |
963 | // "value": { |
964 | // "type": "string", |
965 | // "description": "The variable's value. This can be a multi-line text, |
966 | // e.g. for a function the body of a function." |
967 | // }, |
968 | // "type": { |
969 | // "type": "string", |
970 | // "description": "The type of the variable's value. Typically shown in |
971 | // the UI when hovering over the value." |
972 | // }, |
973 | // "presentationHint": { |
974 | // "$ref": "#/definitions/VariablePresentationHint", |
975 | // "description": "Properties of a variable that can be used to determine |
976 | // how to render the variable in the UI." |
977 | // }, |
978 | // "evaluateName": { |
979 | // "type": "string", |
980 | // "description": "Optional evaluatable name of this variable which can |
981 | // be passed to the 'EvaluateRequest' to fetch the |
982 | // variable's value." |
983 | // }, |
984 | // "variablesReference": { |
985 | // "type": "integer", |
986 | // "description": "If variablesReference is > 0, the variable is |
987 | // structured and its children can be retrieved by |
988 | // passing variablesReference to the VariablesRequest." |
989 | // }, |
990 | // "namedVariables": { |
991 | // "type": "integer", |
992 | // "description": "The number of named child variables. The client can |
993 | // use this optional information to present the children |
994 | // in a paged UI and fetch them in chunks." |
995 | // }, |
996 | // "indexedVariables": { |
997 | // "type": "integer", |
998 | // "description": "The number of indexed child variables. The client |
999 | // can use this optional information to present the |
1000 | // children in a paged UI and fetch them in chunks." |
1001 | // }, |
1002 | // "memoryReference": { |
1003 | // "type": "string", |
1004 | // "description": "A memory reference associated with this variable. |
1005 | // For pointer type variables, this is generally a |
1006 | // reference to the memory address contained in the |
1007 | // pointer. For executable data, this reference may later |
1008 | // be used in a `disassemble` request. This attribute may |
1009 | // be returned by a debug adapter if corresponding |
1010 | // capability `supportsMemoryReferences` is true." |
1011 | // }, |
1012 | // "declarationLocationReference": { |
1013 | // "type": "integer", |
1014 | // "description": "A reference that allows the client to request the |
1015 | // location where the variable is declared. This should be |
1016 | // present only if the adapter is likely to be able to |
1017 | // resolve the location.\n\nThis reference shares the same |
1018 | // lifetime as the `variablesReference`. See 'Lifetime of |
1019 | // Object References' in the Overview section for |
1020 | // details." |
1021 | // }, |
1022 | // "valueLocationReference": { |
1023 | // "type": "integer", |
1024 | // "description": "A reference that allows the client to request the |
1025 | // location where the variable's value is declared. For |
1026 | // example, if the variable contains a function pointer, |
1027 | // the adapter may be able to look up the function's |
1028 | // location. This should be present only if the adapter |
1029 | // is likely to be able to resolve the location.\n\nThis |
1030 | // reference shares the same lifetime as the |
1031 | // `variablesReference`. See 'Lifetime of Object |
1032 | // References' in the Overview section for details." |
1033 | // }, |
1034 | // |
1035 | // "$__lldb_extensions": { |
1036 | // "description": "Unofficial extensions to the protocol", |
1037 | // "properties": { |
1038 | // "declaration": { |
1039 | // "type": "object", |
1040 | // "description": "The source location where the variable was |
1041 | // declared. This value won't be present if no |
1042 | // declaration is available. |
1043 | // Superseded by `declarationLocationReference`", |
1044 | // "properties": { |
1045 | // "path": { |
1046 | // "type": "string", |
1047 | // "description": "The source file path where the variable was |
1048 | // declared." |
1049 | // }, |
1050 | // "line": { |
1051 | // "type": "number", |
1052 | // "description": "The 1-indexed source line where the variable |
1053 | // was declared." |
1054 | // }, |
1055 | // "column": { |
1056 | // "type": "number", |
1057 | // "description": "The 1-indexed source column where the variable |
1058 | // was declared." |
1059 | // } |
1060 | // } |
1061 | // }, |
1062 | // "value": { |
1063 | // "type": "string", |
1064 | // "description": "The internal value of the variable as returned by |
1065 | // This is effectively SBValue.GetValue(). The other |
1066 | // `value` entry in the top-level variable response |
1067 | // is, on the other hand, just a display string for |
1068 | // the variable." |
1069 | // }, |
1070 | // "summary": { |
1071 | // "type": "string", |
1072 | // "description": "The summary string of the variable. This is |
1073 | // effectively SBValue.GetSummary()." |
1074 | // }, |
1075 | // "autoSummary": { |
1076 | // "type": "string", |
1077 | // "description": "The auto generated summary if using |
1078 | // `enableAutoVariableSummaries`." |
1079 | // }, |
1080 | // "error": { |
1081 | // "type": "string", |
1082 | // "description": "An error message generated if LLDB couldn't inspect |
1083 | // the variable." |
1084 | // } |
1085 | // } |
1086 | // } |
1087 | // }, |
1088 | // "required": [ "name", "value", "variablesReference" ] |
1089 | // } |
1090 | llvm::json::Value CreateVariable(lldb::SBValue v, int64_t var_ref, |
1091 | bool format_hex, bool auto_variable_summaries, |
1092 | bool synthetic_child_debugging, |
1093 | bool is_name_duplicated, |
1094 | std::optional<std::string> custom_name) { |
1095 | VariableDescription desc(v, auto_variable_summaries, format_hex, |
1096 | is_name_duplicated, custom_name); |
1097 | llvm::json::Object object; |
1098 | EmplaceSafeString(obj&: object, key: "name" , str: desc.name); |
1099 | EmplaceSafeString(obj&: object, key: "value" , str: desc.display_value); |
1100 | |
1101 | if (!desc.evaluate_name.empty()) |
1102 | EmplaceSafeString(obj&: object, key: "evaluateName" , str: desc.evaluate_name); |
1103 | |
1104 | // If we have a type with many children, we would like to be able to |
1105 | // give a hint to the IDE that the type has indexed children so that the |
1106 | // request can be broken up in grabbing only a few children at a time. We |
1107 | // want to be careful and only call "v.GetNumChildren()" if we have an array |
1108 | // type or if we have a synthetic child provider producing indexed children. |
1109 | // We don't want to call "v.GetNumChildren()" on all objects as class, struct |
1110 | // and union types don't need to be completed if they are never expanded. So |
1111 | // we want to avoid calling this to only cases where we it makes sense to keep |
1112 | // performance high during normal debugging. |
1113 | |
1114 | // If we have an array type, say that it is indexed and provide the number |
1115 | // of children in case we have a huge array. If we don't do this, then we |
1116 | // might take a while to produce all children at onces which can delay your |
1117 | // debug session. |
1118 | if (desc.type_obj.IsArrayType()) { |
1119 | object.try_emplace(K: "indexedVariables" , Args: v.GetNumChildren()); |
1120 | } else if (v.IsSynthetic()) { |
1121 | // For a type with a synthetic child provider, the SBType of "v" won't tell |
1122 | // us anything about what might be displayed. Instead, we check if the first |
1123 | // child's name is "[0]" and then say it is indexed. We call |
1124 | // GetNumChildren() only if the child name matches to avoid a potentially |
1125 | // expensive operation. |
1126 | if (lldb::SBValue first_child = v.GetChildAtIndex(idx: 0)) { |
1127 | llvm::StringRef first_child_name = first_child.GetName(); |
1128 | if (first_child_name == "[0]" ) { |
1129 | size_t num_children = v.GetNumChildren(); |
1130 | // If we are creating a "[raw]" fake child for each synthetic type, we |
1131 | // have to account for it when returning indexed variables. |
1132 | if (synthetic_child_debugging) |
1133 | ++num_children; |
1134 | object.try_emplace(K: "indexedVariables" , Args&: num_children); |
1135 | } |
1136 | } |
1137 | } |
1138 | EmplaceSafeString(obj&: object, key: "type" , str: desc.display_type_name); |
1139 | |
1140 | // A unique variable identifier to help in properly identifying variables with |
1141 | // the same name. This is an extension to the VS protocol. |
1142 | object.try_emplace(K: "id" , Args&: var_ref); |
1143 | |
1144 | if (v.MightHaveChildren()) |
1145 | object.try_emplace(K: "variablesReference" , Args&: var_ref); |
1146 | else |
1147 | object.try_emplace(K: "variablesReference" , Args: 0); |
1148 | |
1149 | if (v.GetDeclaration().IsValid()) |
1150 | object.try_emplace(K: "declarationLocationReference" , |
1151 | Args: PackLocation(var_ref, is_value_location: false)); |
1152 | |
1153 | if (ValuePointsToCode(v)) |
1154 | object.try_emplace(K: "valueLocationReference" , Args: PackLocation(var_ref, is_value_location: true)); |
1155 | |
1156 | if (lldb::addr_t addr = v.GetLoadAddress(); addr != LLDB_INVALID_ADDRESS) |
1157 | object.try_emplace(K: "memoryReference" , Args: EncodeMemoryReference(addr)); |
1158 | |
1159 | object.try_emplace(K: "$__lldb_extensions" , Args: desc.GetVariableExtensionsJSON()); |
1160 | return llvm::json::Value(std::move(object)); |
1161 | } |
1162 | |
1163 | llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit &unit) { |
1164 | llvm::json::Object object; |
1165 | char unit_path_arr[PATH_MAX]; |
1166 | unit.GetFileSpec().GetPath(dst_path: unit_path_arr, dst_len: sizeof(unit_path_arr)); |
1167 | std::string unit_path(unit_path_arr); |
1168 | object.try_emplace(K: "compileUnitPath" , Args&: unit_path); |
1169 | return llvm::json::Value(std::move(object)); |
1170 | } |
1171 | |
1172 | /// See |
1173 | /// https://microsoft.github.io/debug-adapter-protocol/specification#Reverse_Requests_RunInTerminal |
1174 | llvm::json::Object CreateRunInTerminalReverseRequest( |
1175 | llvm::StringRef program, const std::vector<std::string> &args, |
1176 | const llvm::StringMap<std::string> &env, llvm::StringRef cwd, |
1177 | llvm::StringRef comm_file, lldb::pid_t debugger_pid) { |
1178 | llvm::json::Object run_in_terminal_args; |
1179 | // This indicates the IDE to open an embedded terminal, instead of opening |
1180 | // the terminal in a new window. |
1181 | run_in_terminal_args.try_emplace(K: "kind" , Args: "integrated" ); |
1182 | |
1183 | // The program path must be the first entry in the "args" field |
1184 | std::vector<std::string> req_args = {DAP::debug_adapter_path.str(), |
1185 | "--comm-file" , comm_file.str()}; |
1186 | if (debugger_pid != LLDB_INVALID_PROCESS_ID) { |
1187 | req_args.push_back(x: "--debugger-pid" ); |
1188 | req_args.push_back(x: std::to_string(val: debugger_pid)); |
1189 | } |
1190 | req_args.push_back(x: "--launch-target" ); |
1191 | req_args.push_back(x: program.str()); |
1192 | req_args.insert(position: req_args.end(), first: args.begin(), last: args.end()); |
1193 | run_in_terminal_args.try_emplace(K: "args" , Args&: req_args); |
1194 | |
1195 | if (!cwd.empty()) |
1196 | run_in_terminal_args.try_emplace(K: "cwd" , Args&: cwd); |
1197 | |
1198 | if (!env.empty()) { |
1199 | llvm::json::Object env_json; |
1200 | for (const auto &kv : env) { |
1201 | if (!kv.first().empty()) |
1202 | env_json.try_emplace(K: kv.first(), Args: kv.second); |
1203 | } |
1204 | run_in_terminal_args.try_emplace(K: "env" , |
1205 | Args: llvm::json::Value(std::move(env_json))); |
1206 | } |
1207 | |
1208 | return run_in_terminal_args; |
1209 | } |
1210 | |
1211 | // Keep all the top level items from the statistics dump, except for the |
1212 | // "modules" array. It can be huge and cause delay |
1213 | // Array and dictionary value will return as <key, JSON string> pairs |
1214 | static void FilterAndGetValueForKey(const lldb::SBStructuredData data, |
1215 | const char *key, llvm::json::Object &out) { |
1216 | lldb::SBStructuredData value = data.GetValueForKey(key); |
1217 | std::string key_utf8 = llvm::json::fixUTF8(S: key); |
1218 | if (llvm::StringRef(key) == "modules" ) |
1219 | return; |
1220 | switch (value.GetType()) { |
1221 | case lldb::eStructuredDataTypeFloat: |
1222 | out.try_emplace(K: key_utf8, Args: value.GetFloatValue()); |
1223 | break; |
1224 | case lldb::eStructuredDataTypeUnsignedInteger: |
1225 | out.try_emplace(K: key_utf8, Args: value.GetIntegerValue(fail_value: (uint64_t)0)); |
1226 | break; |
1227 | case lldb::eStructuredDataTypeSignedInteger: |
1228 | out.try_emplace(K: key_utf8, Args: value.GetIntegerValue(fail_value: (int64_t)0)); |
1229 | break; |
1230 | case lldb::eStructuredDataTypeArray: { |
1231 | lldb::SBStream contents; |
1232 | value.GetAsJSON(stream&: contents); |
1233 | out.try_emplace(K: key_utf8, Args: llvm::json::fixUTF8(S: contents.GetData())); |
1234 | } break; |
1235 | case lldb::eStructuredDataTypeBoolean: |
1236 | out.try_emplace(K: key_utf8, Args: value.GetBooleanValue()); |
1237 | break; |
1238 | case lldb::eStructuredDataTypeString: { |
1239 | // Get the string size before reading |
1240 | const size_t str_length = value.GetStringValue(dst: nullptr, dst_len: 0); |
1241 | std::string str(str_length + 1, 0); |
1242 | value.GetStringValue(dst: &str[0], dst_len: str_length); |
1243 | out.try_emplace(K: key_utf8, Args: llvm::json::fixUTF8(S: str)); |
1244 | } break; |
1245 | case lldb::eStructuredDataTypeDictionary: { |
1246 | lldb::SBStream contents; |
1247 | value.GetAsJSON(stream&: contents); |
1248 | out.try_emplace(K: key_utf8, Args: llvm::json::fixUTF8(S: contents.GetData())); |
1249 | } break; |
1250 | case lldb::eStructuredDataTypeNull: |
1251 | case lldb::eStructuredDataTypeGeneric: |
1252 | case lldb::eStructuredDataTypeInvalid: |
1253 | break; |
1254 | } |
1255 | } |
1256 | |
1257 | static void addStatistic(lldb::SBTarget &target, llvm::json::Object &event) { |
1258 | lldb::SBStructuredData statistics = target.GetStatistics(); |
1259 | bool is_dictionary = |
1260 | statistics.GetType() == lldb::eStructuredDataTypeDictionary; |
1261 | if (!is_dictionary) |
1262 | return; |
1263 | llvm::json::Object stats_body; |
1264 | |
1265 | lldb::SBStringList keys; |
1266 | if (!statistics.GetKeys(keys)) |
1267 | return; |
1268 | for (size_t i = 0; i < keys.GetSize(); i++) { |
1269 | const char *key = keys.GetStringAtIndex(idx: i); |
1270 | FilterAndGetValueForKey(data: statistics, key, out&: stats_body); |
1271 | } |
1272 | llvm::json::Object body{{.K: "$__lldb_statistics" , .V: std::move(stats_body)}}; |
1273 | event.try_emplace(K: "body" , Args: std::move(body)); |
1274 | } |
1275 | |
1276 | llvm::json::Object CreateTerminatedEventObject(lldb::SBTarget &target) { |
1277 | llvm::json::Object event(CreateEventObject(event_name: "terminated" )); |
1278 | addStatistic(target, event); |
1279 | return event; |
1280 | } |
1281 | |
1282 | std::string JSONToString(const llvm::json::Value &json) { |
1283 | std::string data; |
1284 | llvm::raw_string_ostream os(data); |
1285 | os << json; |
1286 | return data; |
1287 | } |
1288 | |
1289 | } // namespace lldb_dap |
1290 | |