Coverage Report

Created: 2022-01-25 06:29

/Users/buildslave/jenkins/workspace/coverage/llvm-project/lldb/tools/lldb-vscode/VSCode.cpp
Line
Count
Source (jump to first uncovered line)
1
//===-- VSCode.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 <chrono>
10
#include <cstdarg>
11
#include <fstream>
12
#include <mutex>
13
#include <sstream>
14
15
#include "LLDBUtils.h"
16
#include "VSCode.h"
17
#include "llvm/Support/FormatVariadic.h"
18
19
#if defined(_WIN32)
20
#define NOMINMAX
21
#include <fcntl.h>
22
#include <io.h>
23
#include <windows.h>
24
#endif
25
26
using namespace lldb_vscode;
27
28
namespace lldb_vscode {
29
30
VSCode g_vsc;
31
32
VSCode::VSCode()
33
    : broadcaster("lldb-vscode"),
34
      exception_breakpoints(
35
          {{"cpp_catch", "C++ Catch", lldb::eLanguageTypeC_plus_plus},
36
           {"cpp_throw", "C++ Throw", lldb::eLanguageTypeC_plus_plus},
37
           {"objc_catch", "Objective C Catch", lldb::eLanguageTypeObjC},
38
           {"objc_throw", "Objective C Throw", lldb::eLanguageTypeObjC},
39
           {"swift_catch", "Swift Catch", lldb::eLanguageTypeSwift},
40
           {"swift_throw", "Swift Throw", lldb::eLanguageTypeSwift}}),
41
      focus_tid(LLDB_INVALID_THREAD_ID), sent_terminated_event(false),
42
      stop_at_entry(false), is_attach(false), reverse_request_seq(0),
43
      waiting_for_run_in_terminal(false),
44
      progress_event_reporter(
45
2
          [&](const ProgressEvent &event) 
{ SendJSON(event.ToJSON()); }0
) {
46
2
  const char *log_file_path = getenv("LLDBVSCODE_LOG");
47
#if defined(_WIN32)
48
  // Windows opens stdout and stdin in text mode which converts \n to 13,10
49
  // while the value is just 10 on Darwin/Linux. Setting the file mode to binary
50
  // fixes this.
51
  int result = _setmode(fileno(stdout), _O_BINARY);
52
  assert(result);
53
  result = _setmode(fileno(stdin), _O_BINARY);
54
  (void)result;
55
  assert(result);
56
#endif
57
2
  if (log_file_path)
58
1
    log.reset(new std::ofstream(log_file_path));
59
2
}
60
61
0
VSCode::~VSCode() {}
62
63
0
int64_t VSCode::GetLineForPC(int64_t sourceReference, lldb::addr_t pc) const {
64
0
  auto pos = source_map.find(sourceReference);
65
0
  if (pos != source_map.end())
66
0
    return pos->second.GetLineForPC(pc);
67
0
  return 0;
68
0
}
69
70
0
ExceptionBreakpoint *VSCode::GetExceptionBreakpoint(const std::string &filter) {
71
0
  for (auto &bp : exception_breakpoints) {
72
0
    if (bp.filter == filter)
73
0
      return &bp;
74
0
  }
75
0
  return nullptr;
76
0
}
77
78
ExceptionBreakpoint *
79
0
VSCode::GetExceptionBreakpoint(const lldb::break_id_t bp_id) {
80
0
  for (auto &bp : exception_breakpoints) {
81
0
    if (bp.bp.GetID() == bp_id)
82
0
      return &bp;
83
0
  }
84
0
  return nullptr;
85
0
}
86
87
// Send the JSON in "json_str" to the "out" stream. Correctly send the
88
// "Content-Length:" field followed by the length, followed by the raw
89
// JSON bytes.
90
2
void VSCode::SendJSON(const std::string &json_str) {
91
2
  output.write_full("Content-Length: ");
92
2
  output.write_full(llvm::utostr(json_str.size()));
93
2
  output.write_full("\r\n\r\n");
94
2
  output.write_full(json_str);
95
96
2
  if (log) {
97
2
    *log << "<-- " << std::endl
98
2
         << "Content-Length: " << json_str.size() << "\r\n\r\n"
99
2
         << json_str << std::endl;
100
2
  }
101
2
}
102
103
// Serialize the JSON value into a string and send the JSON packet to
104
// the "out" stream.
105
2
void VSCode::SendJSON(const llvm::json::Value &json) {
106
2
  std::string s;
107
2
  llvm::raw_string_ostream strm(s);
108
2
  strm << json;
109
2
  static std::mutex mutex;
110
2
  std::lock_guard<std::mutex> locker(mutex);
111
2
  SendJSON(strm.str());
112
2
}
113
114
// Read a JSON packet from the "in" stream.
115
1
std::string VSCode::ReadJSON() {
116
1
  std::string length_str;
117
1
  std::string json_str;
118
1
  int length;
119
120
1
  if (!input.read_expected(log.get(), "Content-Length: "))
121
0
    return json_str;
122
123
1
  if (!input.read_line(log.get(), length_str))
124
0
    return json_str;
125
126
1
  if (!llvm::to_integer(length_str, length))
127
0
    return json_str;
128
129
1
  if (!input.read_expected(log.get(), "\r\n"))
130
0
    return json_str;
131
132
1
  if (!input.read_full(log.get(), length, json_str))
133
0
    return json_str;
134
135
1
  if (log) {
136
1
    *log << "--> " << std::endl
137
1
         << "Content-Length: " << length << "\r\n\r\n"
138
1
         << json_str << std::endl;
139
1
  }
140
141
1
  return json_str;
142
1
}
143
144
// "OutputEvent": {
145
//   "allOf": [ { "$ref": "#/definitions/Event" }, {
146
//     "type": "object",
147
//     "description": "Event message for 'output' event type. The event
148
//                     indicates that the target has produced some output.",
149
//     "properties": {
150
//       "event": {
151
//         "type": "string",
152
//         "enum": [ "output" ]
153
//       },
154
//       "body": {
155
//         "type": "object",
156
//         "properties": {
157
//           "category": {
158
//             "type": "string",
159
//             "description": "The output category. If not specified,
160
//                             'console' is assumed.",
161
//             "_enum": [ "console", "stdout", "stderr", "telemetry" ]
162
//           },
163
//           "output": {
164
//             "type": "string",
165
//             "description": "The output to report."
166
//           },
167
//           "variablesReference": {
168
//             "type": "number",
169
//             "description": "If an attribute 'variablesReference' exists
170
//                             and its value is > 0, the output contains
171
//                             objects which can be retrieved by passing
172
//                             variablesReference to the VariablesRequest."
173
//           },
174
//           "source": {
175
//             "$ref": "#/definitions/Source",
176
//             "description": "An optional source location where the output
177
//                             was produced."
178
//           },
179
//           "line": {
180
//             "type": "integer",
181
//             "description": "An optional source location line where the
182
//                             output was produced."
183
//           },
184
//           "column": {
185
//             "type": "integer",
186
//             "description": "An optional source location column where the
187
//                             output was produced."
188
//           },
189
//           "data": {
190
//             "type":["array","boolean","integer","null","number","object",
191
//                     "string"],
192
//             "description": "Optional data to report. For the 'telemetry'
193
//                             category the data will be sent to telemetry, for
194
//                             the other categories the data is shown in JSON
195
//                             format."
196
//           }
197
//         },
198
//         "required": ["output"]
199
//       }
200
//     },
201
//     "required": [ "event", "body" ]
202
//   }]
203
// }
204
1
void VSCode::SendOutput(OutputType o, const llvm::StringRef output) {
205
1
  if (output.empty())
206
1
    return;
207
208
0
  llvm::json::Object event(CreateEventObject("output"));
209
0
  llvm::json::Object body;
210
0
  const char *category = nullptr;
211
0
  switch (o) {
212
0
  case OutputType::Console:
213
0
    category = "console";
214
0
    break;
215
0
  case OutputType::Stdout:
216
0
    category = "stdout";
217
0
    break;
218
0
  case OutputType::Stderr:
219
0
    category = "stderr";
220
0
    break;
221
0
  case OutputType::Telemetry:
222
0
    category = "telemetry";
223
0
    break;
224
0
  }
225
0
  body.try_emplace("category", category);
226
0
  EmplaceSafeString(body, "output", output.str());
227
0
  event.try_emplace("body", std::move(body));
228
0
  SendJSON(llvm::json::Value(std::move(event)));
229
0
}
230
231
// interface ProgressStartEvent extends Event {
232
//   event: 'progressStart';
233
//
234
//   body: {
235
//     /**
236
//      * An ID that must be used in subsequent 'progressUpdate' and
237
//      'progressEnd'
238
//      * events to make them refer to the same progress reporting.
239
//      * IDs must be unique within a debug session.
240
//      */
241
//     progressId: string;
242
//
243
//     /**
244
//      * Mandatory (short) title of the progress reporting. Shown in the UI to
245
//      * describe the long running operation.
246
//      */
247
//     title: string;
248
//
249
//     /**
250
//      * The request ID that this progress report is related to. If specified a
251
//      * debug adapter is expected to emit
252
//      * progress events for the long running request until the request has
253
//      been
254
//      * either completed or cancelled.
255
//      * If the request ID is omitted, the progress report is assumed to be
256
//      * related to some general activity of the debug adapter.
257
//      */
258
//     requestId?: number;
259
//
260
//     /**
261
//      * If true, the request that reports progress may be canceled with a
262
//      * 'cancel' request.
263
//      * So this property basically controls whether the client should use UX
264
//      that
265
//      * supports cancellation.
266
//      * Clients that don't support cancellation are allowed to ignore the
267
//      * setting.
268
//      */
269
//     cancellable?: boolean;
270
//
271
//     /**
272
//      * Optional, more detailed progress message.
273
//      */
274
//     message?: string;
275
//
276
//     /**
277
//      * Optional progress percentage to display (value range: 0 to 100). If
278
//      * omitted no percentage will be shown.
279
//      */
280
//     percentage?: number;
281
//   };
282
// }
283
//
284
// interface ProgressUpdateEvent extends Event {
285
//   event: 'progressUpdate';
286
//
287
//   body: {
288
//     /**
289
//      * The ID that was introduced in the initial 'progressStart' event.
290
//      */
291
//     progressId: string;
292
//
293
//     /**
294
//      * Optional, more detailed progress message. If omitted, the previous
295
//      * message (if any) is used.
296
//      */
297
//     message?: string;
298
//
299
//     /**
300
//      * Optional progress percentage to display (value range: 0 to 100). If
301
//      * omitted no percentage will be shown.
302
//      */
303
//     percentage?: number;
304
//   };
305
// }
306
//
307
// interface ProgressEndEvent extends Event {
308
//   event: 'progressEnd';
309
//
310
//   body: {
311
//     /**
312
//      * The ID that was introduced in the initial 'ProgressStartEvent'.
313
//      */
314
//     progressId: string;
315
//
316
//     /**
317
//      * Optional, more detailed progress message. If omitted, the previous
318
//      * message (if any) is used.
319
//      */
320
//     message?: string;
321
//   };
322
// }
323
324
void VSCode::SendProgressEvent(uint64_t progress_id, const char *message,
325
0
                               uint64_t completed, uint64_t total) {
326
0
  progress_event_reporter.Push(progress_id, message, completed, total);
327
0
}
328
329
void __attribute__((format(printf, 3, 4)))
330
0
VSCode::SendFormattedOutput(OutputType o, const char *format, ...) {
331
0
  char buffer[1024];
332
0
  va_list args;
333
0
  va_start(args, format);
334
0
  int actual_length = vsnprintf(buffer, sizeof(buffer), format, args);
335
0
  va_end(args);
336
0
  SendOutput(
337
0
      o, llvm::StringRef(buffer, std::min<int>(actual_length, sizeof(buffer))));
338
0
}
339
340
0
int64_t VSCode::GetNextSourceReference() {
341
0
  static int64_t ref = 0;
342
0
  return ++ref;
343
0
}
344
345
ExceptionBreakpoint *
346
0
VSCode::GetExceptionBPFromStopReason(lldb::SBThread &thread) {
347
0
  const auto num = thread.GetStopReasonDataCount();
348
  // Check to see if have hit an exception breakpoint and change the
349
  // reason to "exception", but only do so if all breakpoints that were
350
  // hit are exception breakpoints.
351
0
  ExceptionBreakpoint *exc_bp = nullptr;
352
0
  for (size_t i = 0; i < num; i += 2) {
353
    // thread.GetStopReasonDataAtIndex(i) will return the bp ID and
354
    // thread.GetStopReasonDataAtIndex(i+1) will return the location
355
    // within that breakpoint. We only care about the bp ID so we can
356
    // see if this is an exception breakpoint that is getting hit.
357
0
    lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(i);
358
0
    exc_bp = GetExceptionBreakpoint(bp_id);
359
    // If any breakpoint is not an exception breakpoint, then stop and
360
    // report this as a normal breakpoint
361
0
    if (exc_bp == nullptr)
362
0
      return nullptr;
363
0
  }
364
0
  return exc_bp;
365
0
}
366
367
0
lldb::SBThread VSCode::GetLLDBThread(const llvm::json::Object &arguments) {
368
0
  auto tid = GetSigned(arguments, "threadId", LLDB_INVALID_THREAD_ID);
369
0
  return target.GetProcess().GetThreadByID(tid);
370
0
}
371
372
0
lldb::SBFrame VSCode::GetLLDBFrame(const llvm::json::Object &arguments) {
373
0
  const uint64_t frame_id = GetUnsigned(arguments, "frameId", UINT64_MAX);
374
0
  lldb::SBProcess process = target.GetProcess();
375
  // Upper 32 bits is the thread index ID
376
0
  lldb::SBThread thread =
377
0
      process.GetThreadByIndexID(GetLLDBThreadIndexID(frame_id));
378
  // Lower 32 bits is the frame index
379
0
  return thread.GetFrameAtIndex(GetLLDBFrameID(frame_id));
380
0
}
381
382
0
llvm::json::Value VSCode::CreateTopLevelScopes() {
383
0
  llvm::json::Array scopes;
384
0
  scopes.emplace_back(CreateScope("Locals", VARREF_LOCALS,
385
0
                                  g_vsc.variables.locals.GetSize(), false));
386
0
  scopes.emplace_back(CreateScope("Globals", VARREF_GLOBALS,
387
0
                                  g_vsc.variables.globals.GetSize(), false));
388
0
  scopes.emplace_back(CreateScope("Registers", VARREF_REGS,
389
0
                                  g_vsc.variables.registers.GetSize(), false));
390
0
  return llvm::json::Value(std::move(scopes));
391
0
}
392
393
void VSCode::RunLLDBCommands(llvm::StringRef prefix,
394
1
                             const std::vector<std::string> &commands) {
395
1
  SendOutput(OutputType::Console,
396
1
             llvm::StringRef(::RunLLDBCommands(prefix, commands)));
397
1
}
398
399
0
void VSCode::RunInitCommands() {
400
0
  RunLLDBCommands("Running initCommands:", init_commands);
401
0
}
402
403
0
void VSCode::RunPreRunCommands() {
404
0
  RunLLDBCommands("Running preRunCommands:", pre_run_commands);
405
0
}
406
407
0
void VSCode::RunStopCommands() {
408
0
  RunLLDBCommands("Running stopCommands:", stop_commands);
409
0
}
410
411
0
void VSCode::RunExitCommands() {
412
0
  RunLLDBCommands("Running exitCommands:", exit_commands);
413
0
}
414
415
1
void VSCode::RunTerminateCommands() {
416
1
  RunLLDBCommands("Running terminateCommands:", terminate_commands);
417
1
}
418
419
lldb::SBTarget
420
VSCode::CreateTargetFromArguments(const llvm::json::Object &arguments,
421
0
                                  lldb::SBError &error) {
422
  // Grab the name of the program we need to debug and create a target using
423
  // the given program as an argument. Executable file can be a source of target
424
  // architecture and platform, if they differ from the host. Setting exe path
425
  // in launch info is useless because Target.Launch() will not change
426
  // architecture and platform, therefore they should be known at the target
427
  // creation. We also use target triple and platform from the launch
428
  // configuration, if given, since in some cases ELF file doesn't contain
429
  // enough information to determine correct arch and platform (or ELF can be
430
  // omitted at all), so it is good to leave the user an apportunity to specify
431
  // those. Any of those three can be left empty.
432
0
  llvm::StringRef target_triple = GetString(arguments, "targetTriple");
433
0
  llvm::StringRef platform_name = GetString(arguments, "platformName");
434
0
  llvm::StringRef program = GetString(arguments, "program");
435
0
  auto target = this->debugger.CreateTarget(
436
0
      program.data(), target_triple.data(), platform_name.data(),
437
0
      true, // Add dependent modules.
438
0
      error);
439
440
0
  if (error.Fail()) {
441
    // Update message if there was an error.
442
0
    error.SetErrorStringWithFormat(
443
0
        "Could not create a target for a program '%s': %s.", program.data(),
444
0
        error.GetCString());
445
0
  }
446
447
0
  return target;
448
0
}
449
450
0
void VSCode::SetTarget(const lldb::SBTarget target) {
451
0
  this->target = target;
452
453
0
  if (target.IsValid()) {
454
    // Configure breakpoint event listeners for the target.
455
0
    lldb::SBListener listener = this->debugger.GetListener();
456
0
    listener.StartListeningForEvents(
457
0
        this->target.GetBroadcaster(),
458
0
        lldb::SBTarget::eBroadcastBitBreakpointChanged);
459
0
    listener.StartListeningForEvents(this->broadcaster,
460
0
                                     eBroadcastBitStopEventThread);
461
0
  }
462
0
}
463
464
1
PacketStatus VSCode::GetNextObject(llvm::json::Object &object) {
465
1
  std::string json = ReadJSON();
466
1
  if (json.empty())
467
0
    return PacketStatus::EndOfFile;
468
469
1
  llvm::StringRef json_sref(json);
470
1
  llvm::Expected<llvm::json::Value> json_value = llvm::json::parse(json_sref);
471
1
  if (!json_value) {
472
0
    auto error = json_value.takeError();
473
0
    if (log) {
474
0
      std::string error_str;
475
0
      llvm::raw_string_ostream strm(error_str);
476
0
      strm << error;
477
0
      strm.flush();
478
0
      *log << "error: failed to parse JSON: " << error_str << std::endl
479
0
           << json << std::endl;
480
0
    }
481
0
    return PacketStatus::JSONMalformed;
482
0
  }
483
1
  object = *json_value->getAsObject();
484
1
  if (!json_value->getAsObject()) {
485
0
    if (log)
486
0
      *log << "error: json packet isn't a object" << std::endl;
487
0
    return PacketStatus::JSONNotObject;
488
0
  }
489
1
  return PacketStatus::Success;
490
1
}
491
492
1
bool VSCode::HandleObject(const llvm::json::Object &object) {
493
1
  const auto packet_type = GetString(object, "type");
494
1
  if (packet_type == "request") {
495
1
    const auto command = GetString(object, "command");
496
1
    auto handler_pos = request_handlers.find(std::string(command));
497
1
    if (handler_pos != request_handlers.end()) {
498
1
      handler_pos->second(object);
499
1
      return true; // Success
500
1
    } else {
501
0
      if (log)
502
0
        *log << "error: unhandled command \"" << command.data() << std::endl;
503
0
      return false; // Fail
504
0
    }
505
1
  }
506
0
  return false;
507
1
}
508
509
PacketStatus VSCode::SendReverseRequest(llvm::json::Object request,
510
0
                                        llvm::json::Object &response) {
511
0
  request.try_emplace("seq", ++reverse_request_seq);
512
0
  SendJSON(llvm::json::Value(std::move(request)));
513
0
  while (true) {
514
0
    PacketStatus status = GetNextObject(response);
515
0
    const auto packet_type = GetString(response, "type");
516
0
    if (packet_type == "response")
517
0
      return status;
518
0
    else {
519
      // Not our response, we got another packet
520
0
      HandleObject(response);
521
0
    }
522
0
  }
523
0
  return PacketStatus::EndOfFile;
524
0
}
525
526
void VSCode::RegisterRequestCallback(std::string request,
527
25
                                     RequestCallback callback) {
528
25
  request_handlers[request] = callback;
529
25
}
530
531
0
void Variables::Clear() {
532
0
  locals.Clear();
533
0
  globals.Clear();
534
0
  registers.Clear();
535
0
  expandable_variables.clear();
536
0
}
537
538
0
int64_t Variables::GetNewVariableRefence(bool is_permanent) {
539
0
  if (is_permanent)
540
0
    return next_permanent_var_ref++;
541
0
  return next_temporary_var_ref++;
542
0
}
543
544
0
bool Variables::IsPermanentVariableReference(int64_t var_ref) {
545
0
  return var_ref >= PermanentVariableStartIndex;
546
0
}
547
548
0
lldb::SBValue Variables::GetVariable(int64_t var_ref) const {
549
0
  if (IsPermanentVariableReference(var_ref)) {
550
0
    auto pos = expandable_permanent_variables.find(var_ref);
551
0
    if (pos != expandable_permanent_variables.end())
552
0
      return pos->second;
553
0
  } else {
554
0
    auto pos = expandable_variables.find(var_ref);
555
0
    if (pos != expandable_variables.end())
556
0
      return pos->second;
557
0
  }
558
0
  return lldb::SBValue();
559
0
}
560
561
int64_t Variables::InsertExpandableVariable(lldb::SBValue variable,
562
0
                                            bool is_permanent) {
563
0
  int64_t var_ref = GetNewVariableRefence(is_permanent);
564
0
  if (is_permanent)
565
0
    expandable_permanent_variables.insert(std::make_pair(var_ref, variable));
566
0
  else
567
0
    expandable_variables.insert(std::make_pair(var_ref, variable));
568
0
  return var_ref;
569
0
}
570
571
} // namespace lldb_vscode