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 <algorithm>
10 #include <iomanip>
11 #include <sstream>
12 #include <string.h>
13
14 #include "llvm/ADT/Optional.h"
15 #include "llvm/Support/FormatAdapters.h"
16 #include "llvm/Support/Path.h"
17 #include "llvm/Support/ScopedPrinter.h"
18
19 #include "lldb/API/SBBreakpoint.h"
20 #include "lldb/API/SBBreakpointLocation.h"
21 #include "lldb/API/SBDeclaration.h"
22 #include "lldb/API/SBValue.h"
23 #include "lldb/Host/PosixApi.h"
24
25 #include "ExceptionBreakpoint.h"
26 #include "JSONUtils.h"
27 #include "LLDBUtils.h"
28 #include "VSCode.h"
29
30 namespace lldb_vscode {
31
EmplaceSafeString(llvm::json::Object & obj,llvm::StringRef key,llvm::StringRef str)32 void EmplaceSafeString(llvm::json::Object &obj, llvm::StringRef key,
33 llvm::StringRef str) {
34 if (LLVM_LIKELY(llvm::json::isUTF8(str)))
35 obj.try_emplace(key, str.str());
36 else
37 obj.try_emplace(key, llvm::json::fixUTF8(str));
38 }
39
GetAsString(const llvm::json::Value & value)40 llvm::StringRef GetAsString(const llvm::json::Value &value) {
41 if (auto s = value.getAsString())
42 return *s;
43 return llvm::StringRef();
44 }
45
46 // Gets a string from a JSON object using the key, or returns an empty string.
GetString(const llvm::json::Object & obj,llvm::StringRef key)47 llvm::StringRef GetString(const llvm::json::Object &obj, llvm::StringRef key) {
48 if (llvm::Optional<llvm::StringRef> value = obj.getString(key))
49 return *value;
50 return llvm::StringRef();
51 }
52
GetString(const llvm::json::Object * obj,llvm::StringRef key)53 llvm::StringRef GetString(const llvm::json::Object *obj, llvm::StringRef key) {
54 if (obj == nullptr)
55 return llvm::StringRef();
56 return GetString(*obj, key);
57 }
58
59 // Gets an unsigned integer from a JSON object using the key, or returns the
60 // specified fail value.
GetUnsigned(const llvm::json::Object & obj,llvm::StringRef key,uint64_t fail_value)61 uint64_t GetUnsigned(const llvm::json::Object &obj, llvm::StringRef key,
62 uint64_t fail_value) {
63 if (auto value = obj.getInteger(key))
64 return (uint64_t)*value;
65 return fail_value;
66 }
67
GetUnsigned(const llvm::json::Object * obj,llvm::StringRef key,uint64_t fail_value)68 uint64_t GetUnsigned(const llvm::json::Object *obj, llvm::StringRef key,
69 uint64_t fail_value) {
70 if (obj == nullptr)
71 return fail_value;
72 return GetUnsigned(*obj, key, fail_value);
73 }
74
GetBoolean(const llvm::json::Object & obj,llvm::StringRef key,bool fail_value)75 bool GetBoolean(const llvm::json::Object &obj, llvm::StringRef key,
76 bool fail_value) {
77 if (auto value = obj.getBoolean(key))
78 return *value;
79 if (auto value = obj.getInteger(key))
80 return *value != 0;
81 return fail_value;
82 }
83
GetBoolean(const llvm::json::Object * obj,llvm::StringRef key,bool fail_value)84 bool GetBoolean(const llvm::json::Object *obj, llvm::StringRef key,
85 bool fail_value) {
86 if (obj == nullptr)
87 return fail_value;
88 return GetBoolean(*obj, key, fail_value);
89 }
90
GetSigned(const llvm::json::Object & obj,llvm::StringRef key,int64_t fail_value)91 int64_t GetSigned(const llvm::json::Object &obj, llvm::StringRef key,
92 int64_t fail_value) {
93 if (auto value = obj.getInteger(key))
94 return *value;
95 return fail_value;
96 }
97
GetSigned(const llvm::json::Object * obj,llvm::StringRef key,int64_t fail_value)98 int64_t GetSigned(const llvm::json::Object *obj, llvm::StringRef key,
99 int64_t fail_value) {
100 if (obj == nullptr)
101 return fail_value;
102 return GetSigned(*obj, key, fail_value);
103 }
104
ObjectContainsKey(const llvm::json::Object & obj,llvm::StringRef key)105 bool ObjectContainsKey(const llvm::json::Object &obj, llvm::StringRef key) {
106 return obj.find(key) != obj.end();
107 }
108
GetStrings(const llvm::json::Object * obj,llvm::StringRef key)109 std::vector<std::string> GetStrings(const llvm::json::Object *obj,
110 llvm::StringRef key) {
111 std::vector<std::string> strs;
112 auto json_array = obj->getArray(key);
113 if (!json_array)
114 return strs;
115 for (const auto &value : *json_array) {
116 switch (value.kind()) {
117 case llvm::json::Value::String:
118 strs.push_back(value.getAsString()->str());
119 break;
120 case llvm::json::Value::Number:
121 case llvm::json::Value::Boolean:
122 strs.push_back(llvm::to_string(value));
123 break;
124 case llvm::json::Value::Null:
125 case llvm::json::Value::Object:
126 case llvm::json::Value::Array:
127 break;
128 }
129 }
130 return strs;
131 }
132
SetValueForKey(lldb::SBValue & v,llvm::json::Object & object,llvm::StringRef key)133 void SetValueForKey(lldb::SBValue &v, llvm::json::Object &object,
134 llvm::StringRef key) {
135
136 llvm::StringRef value = v.GetValue();
137 llvm::StringRef summary = v.GetSummary();
138 llvm::StringRef type_name = v.GetType().GetDisplayTypeName();
139 lldb::SBError error = v.GetError();
140
141 std::string result;
142 llvm::raw_string_ostream strm(result);
143 if (!error.Success()) {
144 strm << "<error: " << error.GetCString() << ">";
145 } else if (!value.empty()) {
146 strm << value;
147 if (!summary.empty())
148 strm << ' ' << summary;
149 } else if (!summary.empty()) {
150 strm << ' ' << summary;
151 } else if (!type_name.empty()) {
152 strm << type_name;
153 lldb::addr_t address = v.GetLoadAddress();
154 if (address != LLDB_INVALID_ADDRESS)
155 strm << " @ " << llvm::format_hex(address, 0);
156 }
157 strm.flush();
158 EmplaceSafeString(object, key, result);
159 }
160
FillResponse(const llvm::json::Object & request,llvm::json::Object & response)161 void FillResponse(const llvm::json::Object &request,
162 llvm::json::Object &response) {
163 // Fill in all of the needed response fields to a "request" and set "success"
164 // to true by default.
165 response.try_emplace("type", "response");
166 response.try_emplace("seq", (int64_t)0);
167 EmplaceSafeString(response, "command", GetString(request, "command"));
168 const int64_t seq = GetSigned(request, "seq", 0);
169 response.try_emplace("request_seq", seq);
170 response.try_emplace("success", true);
171 }
172
173 // "Scope": {
174 // "type": "object",
175 // "description": "A Scope is a named container for variables. Optionally
176 // a scope can map to a source or a range within a source.",
177 // "properties": {
178 // "name": {
179 // "type": "string",
180 // "description": "Name of the scope such as 'Arguments', 'Locals'."
181 // },
182 // "presentationHint": {
183 // "type": "string",
184 // "description": "An optional hint for how to present this scope in the
185 // UI. If this attribute is missing, the scope is shown
186 // with a generic UI.",
187 // "_enum": [ "arguments", "locals", "registers" ],
188 // },
189 // "variablesReference": {
190 // "type": "integer",
191 // "description": "The variables of this scope can be retrieved by
192 // passing the value of variablesReference to the
193 // VariablesRequest."
194 // },
195 // "namedVariables": {
196 // "type": "integer",
197 // "description": "The number of named variables in this scope. The
198 // client can use this optional information to present
199 // the variables in a paged UI and fetch them in chunks."
200 // },
201 // "indexedVariables": {
202 // "type": "integer",
203 // "description": "The number of indexed variables in this scope. The
204 // client can use this optional information to present
205 // the variables in a paged UI and fetch them in chunks."
206 // },
207 // "expensive": {
208 // "type": "boolean",
209 // "description": "If true, the number of variables in this scope is
210 // large or expensive to retrieve."
211 // },
212 // "source": {
213 // "$ref": "#/definitions/Source",
214 // "description": "Optional source for this scope."
215 // },
216 // "line": {
217 // "type": "integer",
218 // "description": "Optional start line of the range covered by this
219 // scope."
220 // },
221 // "column": {
222 // "type": "integer",
223 // "description": "Optional start column of the range covered by this
224 // scope."
225 // },
226 // "endLine": {
227 // "type": "integer",
228 // "description": "Optional end line of the range covered by this scope."
229 // },
230 // "endColumn": {
231 // "type": "integer",
232 // "description": "Optional end column of the range covered by this
233 // scope."
234 // }
235 // },
236 // "required": [ "name", "variablesReference", "expensive" ]
237 // }
CreateScope(const llvm::StringRef name,int64_t variablesReference,int64_t namedVariables,bool expensive)238 llvm::json::Value CreateScope(const llvm::StringRef name,
239 int64_t variablesReference,
240 int64_t namedVariables, bool expensive) {
241 llvm::json::Object object;
242 EmplaceSafeString(object, "name", name.str());
243
244 // TODO: Support "arguments" scope. At the moment lldb-vscode includes the
245 // arguments into the "locals" scope.
246 if (variablesReference == VARREF_LOCALS) {
247 object.try_emplace("presentationHint", "locals");
248 } else if (variablesReference == VARREF_REGS) {
249 object.try_emplace("presentationHint", "registers");
250 }
251
252 object.try_emplace("variablesReference", variablesReference);
253 object.try_emplace("expensive", expensive);
254 object.try_emplace("namedVariables", namedVariables);
255 return llvm::json::Value(std::move(object));
256 }
257
258 // "Breakpoint": {
259 // "type": "object",
260 // "description": "Information about a Breakpoint created in setBreakpoints
261 // or setFunctionBreakpoints.",
262 // "properties": {
263 // "id": {
264 // "type": "integer",
265 // "description": "An optional unique identifier for the breakpoint."
266 // },
267 // "verified": {
268 // "type": "boolean",
269 // "description": "If true breakpoint could be set (but not necessarily
270 // at the desired location)."
271 // },
272 // "message": {
273 // "type": "string",
274 // "description": "An optional message about the state of the breakpoint.
275 // This is shown to the user and can be used to explain
276 // why a breakpoint could not be verified."
277 // },
278 // "source": {
279 // "$ref": "#/definitions/Source",
280 // "description": "The source where the breakpoint is located."
281 // },
282 // "line": {
283 // "type": "integer",
284 // "description": "The start line of the actual range covered by the
285 // breakpoint."
286 // },
287 // "column": {
288 // "type": "integer",
289 // "description": "An optional start column of the actual range covered
290 // by the breakpoint."
291 // },
292 // "endLine": {
293 // "type": "integer",
294 // "description": "An optional end line of the actual range covered by
295 // the breakpoint."
296 // },
297 // "endColumn": {
298 // "type": "integer",
299 // "description": "An optional end column of the actual range covered by
300 // the breakpoint. If no end line is given, then the end
301 // column is assumed to be in the start line."
302 // }
303 // },
304 // "required": [ "verified" ]
305 // }
CreateBreakpoint(lldb::SBBreakpoint & bp,llvm::Optional<llvm::StringRef> request_path,llvm::Optional<uint32_t> request_line)306 llvm::json::Value CreateBreakpoint(lldb::SBBreakpoint &bp,
307 llvm::Optional<llvm::StringRef> request_path,
308 llvm::Optional<uint32_t> request_line) {
309 // Each breakpoint location is treated as a separate breakpoint for VS code.
310 // They don't have the notion of a single breakpoint with multiple locations.
311 llvm::json::Object object;
312 if (!bp.IsValid())
313 return llvm::json::Value(std::move(object));
314
315 object.try_emplace("verified", bp.GetNumResolvedLocations() > 0);
316 object.try_emplace("id", bp.GetID());
317 // VS Code DAP doesn't currently allow one breakpoint to have multiple
318 // locations so we just report the first one. If we report all locations
319 // then the IDE starts showing the wrong line numbers and locations for
320 // other source file and line breakpoints in the same file.
321
322 // Below we search for the first resolved location in a breakpoint and report
323 // this as the breakpoint location since it will have a complete location
324 // that is at least loaded in the current process.
325 lldb::SBBreakpointLocation bp_loc;
326 const auto num_locs = bp.GetNumLocations();
327 for (size_t i = 0; i < num_locs; ++i) {
328 bp_loc = bp.GetLocationAtIndex(i);
329 if (bp_loc.IsResolved())
330 break;
331 }
332 // If not locations are resolved, use the first location.
333 if (!bp_loc.IsResolved())
334 bp_loc = bp.GetLocationAtIndex(0);
335 auto bp_addr = bp_loc.GetAddress();
336
337 if (request_path)
338 object.try_emplace("source", CreateSource(*request_path));
339
340 if (bp_addr.IsValid()) {
341 auto line_entry = bp_addr.GetLineEntry();
342 const auto line = line_entry.GetLine();
343 if (line != UINT32_MAX)
344 object.try_emplace("line", line);
345 object.try_emplace("source", CreateSource(line_entry));
346 }
347 // We try to add request_line as a fallback
348 if (request_line)
349 object.try_emplace("line", *request_line);
350 return llvm::json::Value(std::move(object));
351 }
352
GetDebugInfoSizeInSection(lldb::SBSection section)353 static uint64_t GetDebugInfoSizeInSection(lldb::SBSection section) {
354 uint64_t debug_info_size = 0;
355 llvm::StringRef section_name(section.GetName());
356 if (section_name.startswith(".debug") || section_name.startswith("__debug") ||
357 section_name.startswith(".apple") || section_name.startswith("__apple"))
358 debug_info_size += section.GetFileByteSize();
359 size_t num_sub_sections = section.GetNumSubSections();
360 for (size_t i = 0; i < num_sub_sections; i++) {
361 debug_info_size +=
362 GetDebugInfoSizeInSection(section.GetSubSectionAtIndex(i));
363 }
364 return debug_info_size;
365 }
366
GetDebugInfoSize(lldb::SBModule module)367 static uint64_t GetDebugInfoSize(lldb::SBModule module) {
368 uint64_t debug_info_size = 0;
369 size_t num_sections = module.GetNumSections();
370 for (size_t i = 0; i < num_sections; i++) {
371 debug_info_size += GetDebugInfoSizeInSection(module.GetSectionAtIndex(i));
372 }
373 return debug_info_size;
374 }
375
ConvertDebugInfoSizeToString(uint64_t debug_info)376 static std::string ConvertDebugInfoSizeToString(uint64_t debug_info) {
377 std::ostringstream oss;
378 oss << std::fixed << std::setprecision(1);
379 if (debug_info < 1024) {
380 oss << debug_info << "B";
381 } else if (debug_info < 1024 * 1024) {
382 double kb = double(debug_info) / 1024.0;
383 oss << kb << "KB";
384 } else if (debug_info < 1024 * 1024 * 1024) {
385 double mb = double(debug_info) / (1024.0 * 1024.0);
386 oss << mb << "MB";
387 } else {
388 double gb = double(debug_info) / (1024.0 * 1024.0 * 1024.0);
389 oss << gb << "GB";
390 }
391 return oss.str();
392 }
CreateModule(lldb::SBModule & module)393 llvm::json::Value CreateModule(lldb::SBModule &module) {
394 llvm::json::Object object;
395 if (!module.IsValid())
396 return llvm::json::Value(std::move(object));
397 const char *uuid = module.GetUUIDString();
398 object.try_emplace("id", uuid ? std::string(uuid) : std::string(""));
399 object.try_emplace("name", std::string(module.GetFileSpec().GetFilename()));
400 char module_path_arr[PATH_MAX];
401 module.GetFileSpec().GetPath(module_path_arr, sizeof(module_path_arr));
402 std::string module_path(module_path_arr);
403 object.try_emplace("path", module_path);
404 if (module.GetNumCompileUnits() > 0) {
405 std::string symbol_str = "Symbols loaded.";
406 std::string debug_info_size;
407 uint64_t debug_info = GetDebugInfoSize(module);
408 if (debug_info > 0) {
409 debug_info_size = ConvertDebugInfoSizeToString(debug_info);
410 }
411 object.try_emplace("symbolStatus", symbol_str);
412 object.try_emplace("debugInfoSize", debug_info_size);
413 char symbol_path_arr[PATH_MAX];
414 module.GetSymbolFileSpec().GetPath(symbol_path_arr,
415 sizeof(symbol_path_arr));
416 std::string symbol_path(symbol_path_arr);
417 object.try_emplace("symbolFilePath", symbol_path);
418 } else {
419 object.try_emplace("symbolStatus", "Symbols not found.");
420 }
421 std::string loaded_addr = std::to_string(
422 module.GetObjectFileHeaderAddress().GetLoadAddress(g_vsc.target));
423 object.try_emplace("addressRange", loaded_addr);
424 std::string version_str;
425 uint32_t version_nums[3];
426 uint32_t num_versions =
427 module.GetVersion(version_nums, sizeof(version_nums) / sizeof(uint32_t));
428 for (uint32_t i = 0; i < num_versions; ++i) {
429 if (!version_str.empty())
430 version_str += ".";
431 version_str += std::to_string(version_nums[i]);
432 }
433 if (!version_str.empty())
434 object.try_emplace("version", version_str);
435 return llvm::json::Value(std::move(object));
436 }
437
AppendBreakpoint(lldb::SBBreakpoint & bp,llvm::json::Array & breakpoints,llvm::Optional<llvm::StringRef> request_path,llvm::Optional<uint32_t> request_line)438 void AppendBreakpoint(lldb::SBBreakpoint &bp, llvm::json::Array &breakpoints,
439 llvm::Optional<llvm::StringRef> request_path,
440 llvm::Optional<uint32_t> request_line) {
441 breakpoints.emplace_back(CreateBreakpoint(bp, request_path, request_line));
442 }
443
444 // "Event": {
445 // "allOf": [ { "$ref": "#/definitions/ProtocolMessage" }, {
446 // "type": "object",
447 // "description": "Server-initiated event.",
448 // "properties": {
449 // "type": {
450 // "type": "string",
451 // "enum": [ "event" ]
452 // },
453 // "event": {
454 // "type": "string",
455 // "description": "Type of event."
456 // },
457 // "body": {
458 // "type": [ "array", "boolean", "integer", "null", "number" ,
459 // "object", "string" ],
460 // "description": "Event-specific information."
461 // }
462 // },
463 // "required": [ "type", "event" ]
464 // }]
465 // },
466 // "ProtocolMessage": {
467 // "type": "object",
468 // "description": "Base class of requests, responses, and events.",
469 // "properties": {
470 // "seq": {
471 // "type": "integer",
472 // "description": "Sequence number."
473 // },
474 // "type": {
475 // "type": "string",
476 // "description": "Message type.",
477 // "_enum": [ "request", "response", "event" ]
478 // }
479 // },
480 // "required": [ "seq", "type" ]
481 // }
CreateEventObject(const llvm::StringRef event_name)482 llvm::json::Object CreateEventObject(const llvm::StringRef event_name) {
483 llvm::json::Object event;
484 event.try_emplace("seq", 0);
485 event.try_emplace("type", "event");
486 EmplaceSafeString(event, "event", event_name);
487 return event;
488 }
489
490 // "ExceptionBreakpointsFilter": {
491 // "type": "object",
492 // "description": "An ExceptionBreakpointsFilter is shown in the UI as an
493 // option for configuring how exceptions are dealt with.",
494 // "properties": {
495 // "filter": {
496 // "type": "string",
497 // "description": "The internal ID of the filter. This value is passed
498 // to the setExceptionBreakpoints request."
499 // },
500 // "label": {
501 // "type": "string",
502 // "description": "The name of the filter. This will be shown in the UI."
503 // },
504 // "default": {
505 // "type": "boolean",
506 // "description": "Initial value of the filter. If not specified a value
507 // 'false' is assumed."
508 // }
509 // },
510 // "required": [ "filter", "label" ]
511 // }
512 llvm::json::Value
CreateExceptionBreakpointFilter(const ExceptionBreakpoint & bp)513 CreateExceptionBreakpointFilter(const ExceptionBreakpoint &bp) {
514 llvm::json::Object object;
515 EmplaceSafeString(object, "filter", bp.filter);
516 EmplaceSafeString(object, "label", bp.label);
517 object.try_emplace("default", bp.default_value);
518 return llvm::json::Value(std::move(object));
519 }
520
521 // "Source": {
522 // "type": "object",
523 // "description": "A Source is a descriptor for source code. It is returned
524 // from the debug adapter as part of a StackFrame and it is
525 // used by clients when specifying breakpoints.",
526 // "properties": {
527 // "name": {
528 // "type": "string",
529 // "description": "The short name of the source. Every source returned
530 // from the debug adapter has a name. When sending a
531 // source to the debug adapter this name is optional."
532 // },
533 // "path": {
534 // "type": "string",
535 // "description": "The path of the source to be shown in the UI. It is
536 // only used to locate and load the content of the
537 // source if no sourceReference is specified (or its
538 // value is 0)."
539 // },
540 // "sourceReference": {
541 // "type": "number",
542 // "description": "If sourceReference > 0 the contents of the source must
543 // be retrieved through the SourceRequest (even if a path
544 // is specified). A sourceReference is only valid for a
545 // session, so it must not be used to persist a source."
546 // },
547 // "presentationHint": {
548 // "type": "string",
549 // "description": "An optional hint for how to present the source in the
550 // UI. A value of 'deemphasize' can be used to indicate
551 // that the source is not available or that it is
552 // skipped on stepping.",
553 // "enum": [ "normal", "emphasize", "deemphasize" ]
554 // },
555 // "origin": {
556 // "type": "string",
557 // "description": "The (optional) origin of this source: possible values
558 // 'internal module', 'inlined content from source map',
559 // etc."
560 // },
561 // "sources": {
562 // "type": "array",
563 // "items": {
564 // "$ref": "#/definitions/Source"
565 // },
566 // "description": "An optional list of sources that are related to this
567 // source. These may be the source that generated this
568 // source."
569 // },
570 // "adapterData": {
571 // "type":["array","boolean","integer","null","number","object","string"],
572 // "description": "Optional data that a debug adapter might want to loop
573 // through the client. The client should leave the data
574 // intact and persist it across sessions. The client
575 // should not interpret the data."
576 // },
577 // "checksums": {
578 // "type": "array",
579 // "items": {
580 // "$ref": "#/definitions/Checksum"
581 // },
582 // "description": "The checksums associated with this file."
583 // }
584 // }
585 // }
CreateSource(lldb::SBLineEntry & line_entry)586 llvm::json::Value CreateSource(lldb::SBLineEntry &line_entry) {
587 llvm::json::Object object;
588 lldb::SBFileSpec file = line_entry.GetFileSpec();
589 if (file.IsValid()) {
590 const char *name = file.GetFilename();
591 if (name)
592 EmplaceSafeString(object, "name", name);
593 char path[PATH_MAX] = "";
594 file.GetPath(path, sizeof(path));
595 if (path[0]) {
596 EmplaceSafeString(object, "path", std::string(path));
597 }
598 }
599 return llvm::json::Value(std::move(object));
600 }
601
CreateSource(llvm::StringRef source_path)602 llvm::json::Value CreateSource(llvm::StringRef source_path) {
603 llvm::json::Object source;
604 llvm::StringRef name = llvm::sys::path::filename(source_path);
605 EmplaceSafeString(source, "name", name);
606 EmplaceSafeString(source, "path", source_path);
607 return llvm::json::Value(std::move(source));
608 }
609
CreateSource(lldb::SBFrame & frame,int64_t & disasm_line)610 llvm::json::Value CreateSource(lldb::SBFrame &frame, int64_t &disasm_line) {
611 disasm_line = 0;
612 auto line_entry = frame.GetLineEntry();
613 if (line_entry.GetFileSpec().IsValid())
614 return CreateSource(line_entry);
615
616 llvm::json::Object object;
617 const auto pc = frame.GetPC();
618
619 lldb::SBInstructionList insts;
620 lldb::SBFunction function = frame.GetFunction();
621 lldb::addr_t low_pc = LLDB_INVALID_ADDRESS;
622 if (function.IsValid()) {
623 low_pc = function.GetStartAddress().GetLoadAddress(g_vsc.target);
624 auto addr_srcref = g_vsc.addr_to_source_ref.find(low_pc);
625 if (addr_srcref != g_vsc.addr_to_source_ref.end()) {
626 // We have this disassembly cached already, return the existing
627 // sourceReference
628 object.try_emplace("sourceReference", addr_srcref->second);
629 disasm_line = g_vsc.GetLineForPC(addr_srcref->second, pc);
630 } else {
631 insts = function.GetInstructions(g_vsc.target);
632 }
633 } else {
634 lldb::SBSymbol symbol = frame.GetSymbol();
635 if (symbol.IsValid()) {
636 low_pc = symbol.GetStartAddress().GetLoadAddress(g_vsc.target);
637 auto addr_srcref = g_vsc.addr_to_source_ref.find(low_pc);
638 if (addr_srcref != g_vsc.addr_to_source_ref.end()) {
639 // We have this disassembly cached already, return the existing
640 // sourceReference
641 object.try_emplace("sourceReference", addr_srcref->second);
642 disasm_line = g_vsc.GetLineForPC(addr_srcref->second, pc);
643 } else {
644 insts = symbol.GetInstructions(g_vsc.target);
645 }
646 }
647 }
648 const auto num_insts = insts.GetSize();
649 if (low_pc != LLDB_INVALID_ADDRESS && num_insts > 0) {
650 EmplaceSafeString(object, "name", frame.GetFunctionName());
651 SourceReference source;
652 llvm::raw_string_ostream src_strm(source.content);
653 std::string line;
654 for (size_t i = 0; i < num_insts; ++i) {
655 lldb::SBInstruction inst = insts.GetInstructionAtIndex(i);
656 const auto inst_addr = inst.GetAddress().GetLoadAddress(g_vsc.target);
657 const char *m = inst.GetMnemonic(g_vsc.target);
658 const char *o = inst.GetOperands(g_vsc.target);
659 const char *c = inst.GetComment(g_vsc.target);
660 if (pc == inst_addr)
661 disasm_line = i + 1;
662 const auto inst_offset = inst_addr - low_pc;
663 int spaces = 0;
664 if (inst_offset < 10)
665 spaces = 3;
666 else if (inst_offset < 100)
667 spaces = 2;
668 else if (inst_offset < 1000)
669 spaces = 1;
670 line.clear();
671 llvm::raw_string_ostream line_strm(line);
672 line_strm << llvm::formatv("{0:X+}: <{1}> {2} {3,12} {4}", inst_addr,
673 inst_offset, llvm::fmt_repeat(' ', spaces), m,
674 o);
675
676 // If there is a comment append it starting at column 60 or after one
677 // space past the last char
678 const uint32_t comment_row = std::max(line_strm.str().size(), (size_t)60);
679 if (c && c[0]) {
680 if (line.size() < comment_row)
681 line_strm.indent(comment_row - line_strm.str().size());
682 line_strm << " # " << c;
683 }
684 src_strm << line_strm.str() << "\n";
685 source.addr_to_line[inst_addr] = i + 1;
686 }
687 // Flush the source stream
688 src_strm.str();
689 auto sourceReference = VSCode::GetNextSourceReference();
690 g_vsc.source_map[sourceReference] = std::move(source);
691 g_vsc.addr_to_source_ref[low_pc] = sourceReference;
692 object.try_emplace("sourceReference", sourceReference);
693 }
694 return llvm::json::Value(std::move(object));
695 }
696
697 // "StackFrame": {
698 // "type": "object",
699 // "description": "A Stackframe contains the source location.",
700 // "properties": {
701 // "id": {
702 // "type": "integer",
703 // "description": "An identifier for the stack frame. It must be unique
704 // across all threads. This id can be used to retrieve
705 // the scopes of the frame with the 'scopesRequest' or
706 // to restart the execution of a stackframe."
707 // },
708 // "name": {
709 // "type": "string",
710 // "description": "The name of the stack frame, typically a method name."
711 // },
712 // "source": {
713 // "$ref": "#/definitions/Source",
714 // "description": "The optional source of the frame."
715 // },
716 // "line": {
717 // "type": "integer",
718 // "description": "The line within the file of the frame. If source is
719 // null or doesn't exist, line is 0 and must be ignored."
720 // },
721 // "column": {
722 // "type": "integer",
723 // "description": "The column within the line. If source is null or
724 // doesn't exist, column is 0 and must be ignored."
725 // },
726 // "endLine": {
727 // "type": "integer",
728 // "description": "An optional end line of the range covered by the
729 // stack frame."
730 // },
731 // "endColumn": {
732 // "type": "integer",
733 // "description": "An optional end column of the range covered by the
734 // stack frame."
735 // },
736 // "moduleId": {
737 // "type": ["integer", "string"],
738 // "description": "The module associated with this frame, if any."
739 // },
740 // "presentationHint": {
741 // "type": "string",
742 // "enum": [ "normal", "label", "subtle" ],
743 // "description": "An optional hint for how to present this frame in
744 // the UI. A value of 'label' can be used to indicate
745 // that the frame is an artificial frame that is used
746 // as a visual label or separator. A value of 'subtle'
747 // can be used to change the appearance of a frame in
748 // a 'subtle' way."
749 // }
750 // },
751 // "required": [ "id", "name", "line", "column" ]
752 // }
CreateStackFrame(lldb::SBFrame & frame)753 llvm::json::Value CreateStackFrame(lldb::SBFrame &frame) {
754 llvm::json::Object object;
755 int64_t frame_id = MakeVSCodeFrameID(frame);
756 object.try_emplace("id", frame_id);
757
758 std::string frame_name;
759 const char *func_name = frame.GetFunctionName();
760 if (func_name)
761 frame_name = func_name;
762 else
763 frame_name = "<unknown>";
764 bool is_optimized = frame.GetFunction().GetIsOptimized();
765 if (is_optimized)
766 frame_name += " [opt]";
767 EmplaceSafeString(object, "name", frame_name);
768
769 int64_t disasm_line = 0;
770 object.try_emplace("source", CreateSource(frame, disasm_line));
771
772 auto line_entry = frame.GetLineEntry();
773 if (disasm_line > 0) {
774 object.try_emplace("line", disasm_line);
775 } else {
776 auto line = line_entry.GetLine();
777 if (line == UINT32_MAX)
778 line = 0;
779 object.try_emplace("line", line);
780 }
781 object.try_emplace("column", line_entry.GetColumn());
782 return llvm::json::Value(std::move(object));
783 }
784
785 // "Thread": {
786 // "type": "object",
787 // "description": "A Thread",
788 // "properties": {
789 // "id": {
790 // "type": "integer",
791 // "description": "Unique identifier for the thread."
792 // },
793 // "name": {
794 // "type": "string",
795 // "description": "A name of the thread."
796 // }
797 // },
798 // "required": [ "id", "name" ]
799 // }
CreateThread(lldb::SBThread & thread)800 llvm::json::Value CreateThread(lldb::SBThread &thread) {
801 llvm::json::Object object;
802 object.try_emplace("id", (int64_t)thread.GetThreadID());
803 char thread_str[64];
804 snprintf(thread_str, sizeof(thread_str), "Thread #%u", thread.GetIndexID());
805 const char *name = thread.GetName();
806 if (name) {
807 std::string thread_with_name(thread_str);
808 thread_with_name += ' ';
809 thread_with_name += name;
810 EmplaceSafeString(object, "name", thread_with_name);
811 } else {
812 EmplaceSafeString(object, "name", std::string(thread_str));
813 }
814 return llvm::json::Value(std::move(object));
815 }
816
817 // "StoppedEvent": {
818 // "allOf": [ { "$ref": "#/definitions/Event" }, {
819 // "type": "object",
820 // "description": "Event message for 'stopped' event type. The event
821 // indicates that the execution of the debuggee has stopped
822 // due to some condition. This can be caused by a break
823 // point previously set, a stepping action has completed,
824 // by executing a debugger statement etc.",
825 // "properties": {
826 // "event": {
827 // "type": "string",
828 // "enum": [ "stopped" ]
829 // },
830 // "body": {
831 // "type": "object",
832 // "properties": {
833 // "reason": {
834 // "type": "string",
835 // "description": "The reason for the event. For backward
836 // compatibility this string is shown in the UI if
837 // the 'description' attribute is missing (but it
838 // must not be translated).",
839 // "_enum": [ "step", "breakpoint", "exception", "pause", "entry" ]
840 // },
841 // "description": {
842 // "type": "string",
843 // "description": "The full reason for the event, e.g. 'Paused
844 // on exception'. This string is shown in the UI
845 // as is."
846 // },
847 // "threadId": {
848 // "type": "integer",
849 // "description": "The thread which was stopped."
850 // },
851 // "text": {
852 // "type": "string",
853 // "description": "Additional information. E.g. if reason is
854 // 'exception', text contains the exception name.
855 // This string is shown in the UI."
856 // },
857 // "allThreadsStopped": {
858 // "type": "boolean",
859 // "description": "If allThreadsStopped is true, a debug adapter
860 // can announce that all threads have stopped.
861 // The client should use this information to
862 // enable that all threads can be expanded to
863 // access their stacktraces. If the attribute
864 // is missing or false, only the thread with the
865 // given threadId can be expanded."
866 // }
867 // },
868 // "required": [ "reason" ]
869 // }
870 // },
871 // "required": [ "event", "body" ]
872 // }]
873 // }
CreateThreadStopped(lldb::SBThread & thread,uint32_t stop_id)874 llvm::json::Value CreateThreadStopped(lldb::SBThread &thread,
875 uint32_t stop_id) {
876 llvm::json::Object event(CreateEventObject("stopped"));
877 llvm::json::Object body;
878 switch (thread.GetStopReason()) {
879 case lldb::eStopReasonTrace:
880 case lldb::eStopReasonPlanComplete:
881 body.try_emplace("reason", "step");
882 break;
883 case lldb::eStopReasonBreakpoint: {
884 ExceptionBreakpoint *exc_bp = g_vsc.GetExceptionBPFromStopReason(thread);
885 if (exc_bp) {
886 body.try_emplace("reason", "exception");
887 EmplaceSafeString(body, "description", exc_bp->label);
888 } else {
889 body.try_emplace("reason", "breakpoint");
890 char desc_str[64];
891 uint64_t bp_id = thread.GetStopReasonDataAtIndex(0);
892 uint64_t bp_loc_id = thread.GetStopReasonDataAtIndex(1);
893 snprintf(desc_str, sizeof(desc_str), "breakpoint %" PRIu64 ".%" PRIu64,
894 bp_id, bp_loc_id);
895 EmplaceSafeString(body, "description", desc_str);
896 }
897 } break;
898 case lldb::eStopReasonWatchpoint:
899 case lldb::eStopReasonInstrumentation:
900 body.try_emplace("reason", "breakpoint");
901 break;
902 case lldb::eStopReasonProcessorTrace:
903 body.try_emplace("reason", "processor trace");
904 break;
905 case lldb::eStopReasonSignal:
906 case lldb::eStopReasonException:
907 body.try_emplace("reason", "exception");
908 break;
909 case lldb::eStopReasonExec:
910 body.try_emplace("reason", "entry");
911 break;
912 case lldb::eStopReasonFork:
913 body.try_emplace("reason", "fork");
914 break;
915 case lldb::eStopReasonVFork:
916 body.try_emplace("reason", "vfork");
917 break;
918 case lldb::eStopReasonVForkDone:
919 body.try_emplace("reason", "vforkdone");
920 break;
921 case lldb::eStopReasonThreadExiting:
922 case lldb::eStopReasonInvalid:
923 case lldb::eStopReasonNone:
924 break;
925 }
926 if (stop_id == 0)
927 body.try_emplace("reason", "entry");
928 const lldb::tid_t tid = thread.GetThreadID();
929 body.try_emplace("threadId", (int64_t)tid);
930 // If no description has been set, then set it to the default thread stopped
931 // description. If we have breakpoints that get hit and shouldn't be reported
932 // as breakpoints, then they will set the description above.
933 if (ObjectContainsKey(body, "description")) {
934 char description[1024];
935 if (thread.GetStopDescription(description, sizeof(description))) {
936 EmplaceSafeString(body, "description", std::string(description));
937 }
938 }
939 if (tid == g_vsc.focus_tid) {
940 body.try_emplace("threadCausedFocus", true);
941 }
942 body.try_emplace("preserveFocusHint", tid != g_vsc.focus_tid);
943 body.try_emplace("allThreadsStopped", true);
944 event.try_emplace("body", std::move(body));
945 return llvm::json::Value(std::move(event));
946 }
947
GetNonNullVariableName(lldb::SBValue v)948 const char *GetNonNullVariableName(lldb::SBValue v) {
949 const char *name = v.GetName();
950 return name ? name : "<null>";
951 }
952
CreateUniqueVariableNameForDisplay(lldb::SBValue v,bool is_name_duplicated)953 std::string CreateUniqueVariableNameForDisplay(lldb::SBValue v,
954 bool is_name_duplicated) {
955 lldb::SBStream name_builder;
956 name_builder.Print(GetNonNullVariableName(v));
957 if (is_name_duplicated) {
958 lldb::SBDeclaration declaration = v.GetDeclaration();
959 const char *file_name = declaration.GetFileSpec().GetFilename();
960 const uint32_t line = declaration.GetLine();
961
962 if (file_name != nullptr && line > 0)
963 name_builder.Printf(" @ %s:%u", file_name, line);
964 else if (const char *location = v.GetLocation())
965 name_builder.Printf(" @ %s", location);
966 }
967 return name_builder.GetData();
968 }
969
970 // "Variable": {
971 // "type": "object",
972 // "description": "A Variable is a name/value pair. Optionally a variable
973 // can have a 'type' that is shown if space permits or when
974 // hovering over the variable's name. An optional 'kind' is
975 // used to render additional properties of the variable,
976 // e.g. different icons can be used to indicate that a
977 // variable is public or private. If the value is
978 // structured (has children), a handle is provided to
979 // retrieve the children with the VariablesRequest. If
980 // the number of named or indexed children is large, the
981 // numbers should be returned via the optional
982 // 'namedVariables' and 'indexedVariables' attributes. The
983 // client can use this optional information to present the
984 // children in a paged UI and fetch them in chunks.",
985 // "properties": {
986 // "name": {
987 // "type": "string",
988 // "description": "The variable's name."
989 // },
990 // "value": {
991 // "type": "string",
992 // "description": "The variable's value. This can be a multi-line text,
993 // e.g. for a function the body of a function."
994 // },
995 // "type": {
996 // "type": "string",
997 // "description": "The type of the variable's value. Typically shown in
998 // the UI when hovering over the value."
999 // },
1000 // "presentationHint": {
1001 // "$ref": "#/definitions/VariablePresentationHint",
1002 // "description": "Properties of a variable that can be used to determine
1003 // how to render the variable in the UI."
1004 // },
1005 // "evaluateName": {
1006 // "type": "string",
1007 // "description": "Optional evaluatable name of this variable which can
1008 // be passed to the 'EvaluateRequest' to fetch the
1009 // variable's value."
1010 // },
1011 // "variablesReference": {
1012 // "type": "integer",
1013 // "description": "If variablesReference is > 0, the variable is
1014 // structured and its children can be retrieved by
1015 // passing variablesReference to the VariablesRequest."
1016 // },
1017 // "namedVariables": {
1018 // "type": "integer",
1019 // "description": "The number of named child variables. The client can
1020 // use this optional information to present the children
1021 // in a paged UI and fetch them in chunks."
1022 // },
1023 // "indexedVariables": {
1024 // "type": "integer",
1025 // "description": "The number of indexed child variables. The client
1026 // can use this optional information to present the
1027 // children in a paged UI and fetch them in chunks."
1028 // }
1029 // },
1030 // "required": [ "name", "value", "variablesReference" ]
1031 // }
CreateVariable(lldb::SBValue v,int64_t variablesReference,int64_t varID,bool format_hex,bool is_name_duplicated)1032 llvm::json::Value CreateVariable(lldb::SBValue v, int64_t variablesReference,
1033 int64_t varID, bool format_hex,
1034 bool is_name_duplicated) {
1035 llvm::json::Object object;
1036 EmplaceSafeString(object, "name",
1037 CreateUniqueVariableNameForDisplay(v, is_name_duplicated));
1038
1039 if (format_hex)
1040 v.SetFormat(lldb::eFormatHex);
1041 SetValueForKey(v, object, "value");
1042 auto type_obj = v.GetType();
1043 auto type_cstr = type_obj.GetDisplayTypeName();
1044 // If we have a type with many many children, we would like to be able to
1045 // give a hint to the IDE that the type has indexed children so that the
1046 // request can be broken up in grabbing only a few children at a time. We want
1047 // to be careful and only call "v.GetNumChildren()" if we have an array type
1048 // or if we have a synthetic child provider. We don't want to call
1049 // "v.GetNumChildren()" on all objects as class, struct and union types don't
1050 // need to be completed if they are never expanded. So we want to avoid
1051 // calling this to only cases where we it makes sense to keep performance high
1052 // during normal debugging.
1053
1054 // If we have an array type, say that it is indexed and provide the number of
1055 // children in case we have a huge array. If we don't do this, then we might
1056 // take a while to produce all children at onces which can delay your debug
1057 // session.
1058 const bool is_array = type_obj.IsArrayType();
1059 const bool is_synthetic = v.IsSynthetic();
1060 if (is_array || is_synthetic) {
1061 const auto num_children = v.GetNumChildren();
1062 if (is_array) {
1063 object.try_emplace("indexedVariables", num_children);
1064 } else {
1065 // If a type has a synthetic child provider, then the SBType of "v" won't
1066 // tell us anything about what might be displayed. So we can check if the
1067 // first child's name is "[0]" and then we can say it is indexed.
1068 const char *first_child_name = v.GetChildAtIndex(0).GetName();
1069 if (first_child_name && strcmp(first_child_name, "[0]") == 0)
1070 object.try_emplace("indexedVariables", num_children);
1071 }
1072 }
1073 EmplaceSafeString(object, "type", type_cstr ? type_cstr : NO_TYPENAME);
1074 if (varID != INT64_MAX)
1075 object.try_emplace("id", varID);
1076 if (v.MightHaveChildren())
1077 object.try_emplace("variablesReference", variablesReference);
1078 else
1079 object.try_emplace("variablesReference", (int64_t)0);
1080 lldb::SBStream evaluateStream;
1081 v.GetExpressionPath(evaluateStream);
1082 const char *evaluateName = evaluateStream.GetData();
1083 if (evaluateName && evaluateName[0])
1084 EmplaceSafeString(object, "evaluateName", std::string(evaluateName));
1085 return llvm::json::Value(std::move(object));
1086 }
1087
CreateCompileUnit(lldb::SBCompileUnit unit)1088 llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit unit) {
1089 llvm::json::Object object;
1090 char unit_path_arr[PATH_MAX];
1091 unit.GetFileSpec().GetPath(unit_path_arr, sizeof(unit_path_arr));
1092 std::string unit_path(unit_path_arr);
1093 object.try_emplace("compileUnitPath", unit_path);
1094 return llvm::json::Value(std::move(object));
1095 }
1096
1097 /// See
1098 /// https://microsoft.github.io/debug-adapter-protocol/specification#Reverse_Requests_RunInTerminal
1099 llvm::json::Object
CreateRunInTerminalReverseRequest(const llvm::json::Object & launch_request,llvm::StringRef debug_adaptor_path,llvm::StringRef comm_file)1100 CreateRunInTerminalReverseRequest(const llvm::json::Object &launch_request,
1101 llvm::StringRef debug_adaptor_path,
1102 llvm::StringRef comm_file) {
1103 llvm::json::Object reverse_request;
1104 reverse_request.try_emplace("type", "request");
1105 reverse_request.try_emplace("command", "runInTerminal");
1106
1107 llvm::json::Object run_in_terminal_args;
1108 // This indicates the IDE to open an embedded terminal, instead of opening the
1109 // terminal in a new window.
1110 run_in_terminal_args.try_emplace("kind", "integrated");
1111
1112 auto launch_request_arguments = launch_request.getObject("arguments");
1113 // The program path must be the first entry in the "args" field
1114 std::vector<std::string> args = {
1115 debug_adaptor_path.str(), "--comm-file", comm_file.str(),
1116 "--launch-target", GetString(launch_request_arguments, "program").str()};
1117 std::vector<std::string> target_args =
1118 GetStrings(launch_request_arguments, "args");
1119 args.insert(args.end(), target_args.begin(), target_args.end());
1120 run_in_terminal_args.try_emplace("args", args);
1121
1122 const auto cwd = GetString(launch_request_arguments, "cwd");
1123 if (!cwd.empty())
1124 run_in_terminal_args.try_emplace("cwd", cwd);
1125
1126 // We need to convert the input list of environments variables into a
1127 // dictionary
1128 std::vector<std::string> envs = GetStrings(launch_request_arguments, "env");
1129 llvm::json::Object environment;
1130 for (const std::string &env : envs) {
1131 size_t index = env.find('=');
1132 environment.try_emplace(env.substr(0, index), env.substr(index + 1));
1133 }
1134 run_in_terminal_args.try_emplace("env",
1135 llvm::json::Value(std::move(environment)));
1136
1137 reverse_request.try_emplace(
1138 "arguments", llvm::json::Value(std::move(run_in_terminal_args)));
1139 return reverse_request;
1140 }
1141
JSONToString(const llvm::json::Value & json)1142 std::string JSONToString(const llvm::json::Value &json) {
1143 std::string data;
1144 llvm::raw_string_ostream os(data);
1145 os << json;
1146 os.flush();
1147 return data;
1148 }
1149
1150 } // namespace lldb_vscode
1151