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