GCC Code Coverage Report
Directory: ../src/ Exec Total Coverage
File: /home/node-core-coverage/node-core-coverage/workdir/node/src/inspector_agent.cc Lines: 15 276 5.4 %
Date: 2016-07-26 Branches: 3 115 2.6 %

Line Exec Source
1
#include "inspector_agent.h"
2
3
#include "inspector_socket.h"
4
#include "env.h"
5
#include "env-inl.h"
6
#include "node.h"
7
#include "node_mutex.h"
8
#include "node_version.h"
9
#include "v8-platform.h"
10
#include "util.h"
11
12
#include "platform/v8_inspector/public/V8Inspector.h"
13
#include "platform/inspector_protocol/FrontendChannel.h"
14
#include "platform/inspector_protocol/String16.h"
15
#include "platform/inspector_protocol/Values.h"
16
17
#include "libplatform/libplatform.h"
18
19
#include <string.h>
20
#include <utility>
21
#include <vector>
22
23
// We need pid to use as ID with Chrome
24
#if defined(_MSC_VER)
25
#include <direct.h>
26
#include <io.h>
27
#define getpid GetCurrentProcessId
28
#else
29
#include <unistd.h>  // setuid, getuid
30
#endif
31
32
namespace node {
33
namespace {
34
35
const char TAG_CONNECT[] = "#connect";
36
const char TAG_DISCONNECT[] = "#disconnect";
37
38
const char DEVTOOLS_PATH[] = "/node";
39
const char DEVTOOLS_HASH[] = "521e5b7e2b7cc66b4006a8a54cb9c4e57494a5ef";
40
41
void PrintDebuggerReadyMessage(int port) {
42
  fprintf(stderr, "Debugger listening on port %d.\n"
43
    "Warning: This is an experimental feature and could change at any time.\n"
44
    "To start debugging, open the following URL in Chrome:\n"
45
    "    chrome-devtools://devtools/remote/serve_file/"
46
    "@%s/inspector.html?"
47
    "experiments=true&v8only=true&ws=localhost:%d/node\n",
48
      port, DEVTOOLS_HASH, port);
49
}
50
51
bool AcceptsConnection(inspector_socket_t* socket, const char* path) {
52
  return strncmp(DEVTOOLS_PATH, path, sizeof(DEVTOOLS_PATH)) == 0;
53
}
54
55
void DisposeInspector(inspector_socket_t* socket, int status) {
56
  delete socket;
57
}
58
59
void DisconnectAndDisposeIO(inspector_socket_t* socket) {
60
  if (socket) {
61
    inspector_close(socket, DisposeInspector);
62
  }
63
}
64
65
void OnBufferAlloc(uv_handle_t* handle, size_t len, uv_buf_t* buf) {
66
  if (len > 0) {
67
    buf->base = static_cast<char*>(malloc(len));
68
    CHECK_NE(buf->base, nullptr);
69
  }
70
  buf->len = len;
71
}
72
73
void SendHttpResponse(inspector_socket_t* socket, const char* response,
74
                      size_t len) {
75
  const char HEADERS[] = "HTTP/1.0 200 OK\r\n"
76
                         "Content-Type: application/json; charset=UTF-8\r\n"
77
                         "Cache-Control: no-cache\r\n"
78
                         "Content-Length: %ld\r\n"
79
                         "\r\n";
80
  char header[sizeof(HEADERS) + 20];
81
  int header_len = snprintf(header, sizeof(header), HEADERS, len);
82
  inspector_write(socket, header, header_len);
83
  inspector_write(socket, response, len);
84
}
85
86
void SendVersionResponse(inspector_socket_t* socket) {
87
  const char VERSION_RESPONSE_TEMPLATE[] =
88
      "[ {"
89
      "  \"Browser\": \"node.js/%s\","
90
      "  \"Protocol-Version\": \"1.1\","
91
      "  \"User-Agent\": \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36"
92
            "(KHTML, like Gecko) Chrome/45.0.2446.0 Safari/537.36\","
93
      "  \"WebKit-Version\": \"537.36 (@198122)\""
94
      "} ]";
95
  char buffer[sizeof(VERSION_RESPONSE_TEMPLATE) + 128];
96
  size_t len = snprintf(buffer, sizeof(buffer), VERSION_RESPONSE_TEMPLATE,
97
                        NODE_VERSION);
98
  ASSERT_LT(len, sizeof(buffer));
99
  SendHttpResponse(socket, buffer, len);
100
}
101
102
void SendTargentsListResponse(inspector_socket_t* socket, int port) {
103
  const char LIST_RESPONSE_TEMPLATE[] =
104
      "[ {"
105
      "  \"description\": \"node.js instance\","
106
      "  \"devtoolsFrontendUrl\": "
107
            "\"https://chrome-devtools-frontend.appspot.com/serve_file/"
108
            "@%s/inspector.html?experiments=true&v8only=true"
109
            "&ws=localhost:%d%s\","
110
      "  \"faviconUrl\": \"https://nodejs.org/static/favicon.ico\","
111
      "  \"id\": \"%d\","
112
      "  \"title\": \"%s\","
113
      "  \"type\": \"node\","
114
      "  \"webSocketDebuggerUrl\": \"ws://localhost:%d%s\""
115
      "} ]";
116
  char buffer[sizeof(LIST_RESPONSE_TEMPLATE) + 4096];
117
  char title[2048];  // uv_get_process_title trims the title if too long
118
  int err = uv_get_process_title(title, sizeof(title));
119
  if (err != 0) {
120
    snprintf(title, sizeof(title), "Node.js");
121
  }
122
  char* c = title;
123
  while (*c != '\0') {
124
    if (*c < ' ' || *c == '\"') {
125
      *c = '_';
126
    }
127
    c++;
128
  }
129
  size_t len = snprintf(buffer, sizeof(buffer), LIST_RESPONSE_TEMPLATE,
130
                        DEVTOOLS_HASH, port, DEVTOOLS_PATH, getpid(),
131
                        title, port, DEVTOOLS_PATH);
132
  ASSERT_LT(len, sizeof(buffer));
133
  SendHttpResponse(socket, buffer, len);
134
}
135
136
bool RespondToGet(inspector_socket_t* socket, const char* path, int port) {
137
  const char PATH[] = "/json";
138
  const char PATH_LIST[] = "/json/list";
139
  const char PATH_VERSION[] = "/json/version";
140
  const char PATH_ACTIVATE[] = "/json/activate/";
141
  if (!strncmp(PATH_VERSION, path, sizeof(PATH_VERSION))) {
142
    SendVersionResponse(socket);
143
  } else if (!strncmp(PATH_LIST, path, sizeof(PATH_LIST)) ||
144
             !strncmp(PATH, path, sizeof(PATH)))  {
145
    SendTargentsListResponse(socket, port);
146
  } else if (!strncmp(path, PATH_ACTIVATE, sizeof(PATH_ACTIVATE) - 1) &&
147
             atoi(path + (sizeof(PATH_ACTIVATE) - 1)) == getpid()) {
148
    const char TARGET_ACTIVATED[] = "Target activated";
149
    SendHttpResponse(socket, TARGET_ACTIVATED, sizeof(TARGET_ACTIVATED) - 1);
150
  } else {
151
    return false;
152
  }
153
  return true;
154
}
155
156
}  // namespace
157
158
namespace inspector {
159
160
using blink::protocol::DictionaryValue;
161
162
class AgentImpl {
163
 public:
164
  explicit AgentImpl(node::Environment* env);
165
  ~AgentImpl();
166
167
  // Start the inspector agent thread
168
  void Start(v8::Platform* platform, int port, bool wait);
169
  // Stop the inspector agent
170
  void Stop();
171
172
  bool IsStarted();
173
  bool IsConnected() {  return connected_; }
174
  void WaitForDisconnect();
175
176
 private:
177
  using MessageQueue = std::vector<std::pair<int, String16>>;
178
179
  static void ThreadCbIO(void* agent);
180
  static void OnSocketConnectionIO(uv_stream_t* server, int status);
181
  static bool OnInspectorHandshakeIO(inspector_socket_t* socket,
182
                                     enum inspector_handshake_event state,
183
                                     const char* path);
184
  static void WriteCbIO(uv_async_t* async);
185
186
  void WorkerRunIO();
187
  void OnInspectorConnectionIO(inspector_socket_t* socket);
188
  void OnRemoteDataIO(inspector_socket_t* stream, ssize_t read,
189
                      const uv_buf_t* b);
190
  void PostMessages();
191
  void SetConnected(bool connected);
192
  void DispatchMessages();
193
  void Write(int session_id, const String16& message);
194
  void AppendMessage(MessageQueue* vector, int session_id,
195
                     const String16& message);
196
  void SwapBehindLock(MessageQueue* vector1, MessageQueue* vector2);
197
  void PostIncomingMessage(const String16& message);
198
199
  uv_sem_t start_sem_;
200
  ConditionVariable pause_cond_;
201
  Mutex pause_lock_;
202
  Mutex queue_lock_;
203
  uv_thread_t thread_;
204
  uv_loop_t child_loop_;
205
206
  int port_;
207
  bool wait_;
208
  bool connected_;
209
  bool shutting_down_;
210
  node::Environment* parent_env_;
211
212
  uv_async_t data_written_;
213
  uv_async_t io_thread_req_;
214
  inspector_socket_t* client_socket_;
215
  blink::V8Inspector* inspector_;
216
  v8::Platform* platform_;
217
  MessageQueue incoming_message_queue_;
218
  MessageQueue outgoing_message_queue_;
219
  bool dispatching_messages_;
220
  int frontend_session_id_;
221
  int backend_session_id_;
222
223
  friend class ChannelImpl;
224
  friend class DispatchOnInspectorBackendTask;
225
  friend class SetConnectedTask;
226
  friend class V8NodeInspector;
227
  friend void InterruptCallback(v8::Isolate*, void* agent);
228
  friend void DataCallback(uv_stream_t* stream, ssize_t read,
229
                           const uv_buf_t* buf);
230
};
231
232
void InterruptCallback(v8::Isolate*, void* agent) {
233
  static_cast<AgentImpl*>(agent)->DispatchMessages();
234
}
235
236
void DataCallback(uv_stream_t* stream, ssize_t read, const uv_buf_t* buf) {
237
  inspector_socket_t* socket = static_cast<inspector_socket_t*>(stream->data);
238
  static_cast<AgentImpl*>(socket->data)->OnRemoteDataIO(socket, read, buf);
239
}
240
241
class DispatchOnInspectorBackendTask : public v8::Task {
242
 public:
243
  explicit DispatchOnInspectorBackendTask(AgentImpl* agent) : agent_(agent) {}
244
245
  void Run() override {
246
    agent_->DispatchMessages();
247
  }
248
249
 private:
250
  AgentImpl* agent_;
251
};
252
253
class ChannelImpl final : public blink::protocol::FrontendChannel {
254
 public:
255
  explicit ChannelImpl(AgentImpl* agent): agent_(agent) {}
256
  virtual ~ChannelImpl() {}
257
 private:
258
  void sendProtocolResponse(int callId, const String16& message) override {
259
    sendMessageToFrontend(message);
260
  }
261
262
  void sendProtocolNotification(const String16& message) override {
263
    sendMessageToFrontend(message);
264
  }
265
266
  void flushProtocolNotifications() override { }
267
268
  void sendMessageToFrontend(const String16& message) {
269
    agent_->Write(agent_->frontend_session_id_, message);
270
  }
271
272
  AgentImpl* const agent_;
273
};
274
275
class V8NodeInspector : public blink::V8Inspector {
276
 public:
277
  V8NodeInspector(AgentImpl* agent, node::Environment* env,
278
                  v8::Platform* platform)
279
                  : blink::V8Inspector(env->isolate(), env->context()),
280
                    agent_(agent),
281
                    isolate_(env->isolate()),
282
                    platform_(platform),
283
                    terminated_(false),
284
                    running_nested_loop_(false) {}
285
286
  void runMessageLoopOnPause(int context_group_id) override {
287
    if (running_nested_loop_)
288
      return;
289
    terminated_ = false;
290
    running_nested_loop_ = true;
291
    do {
292
      {
293
        Mutex::ScopedLock scoped_lock(agent_->pause_lock_);
294
        agent_->pause_cond_.Wait(scoped_lock);
295
      }
296
      while (v8::platform::PumpMessageLoop(platform_, isolate_))
297
        {}
298
    } while (!terminated_);
299
    terminated_ = false;
300
    running_nested_loop_ = false;
301
  }
302
303
  void quitMessageLoopOnPause() override {
304
    terminated_ = true;
305
  }
306
307
 private:
308
  AgentImpl* agent_;
309
  v8::Isolate* isolate_;
310
  v8::Platform* platform_;
311
  bool terminated_;
312
  bool running_nested_loop_;
313
};
314
315
1561
AgentImpl::AgentImpl(Environment* env) : port_(0),
316
                                         wait_(false),
317
                                         connected_(false),
318
                                         shutting_down_(false),
319
                                         parent_env_(env),
320
                                         client_socket_(nullptr),
321
                                         inspector_(nullptr),
322
                                         platform_(nullptr),
323
                                         dispatching_messages_(false),
324
                                         frontend_session_id_(0),
325
4683
                                         backend_session_id_(0) {
326
1561
  CHECK_EQ(0, uv_sem_init(&start_sem_, 0));
327
3122
  memset(&data_written_, 0, sizeof(data_written_));
328
3122
  memset(&io_thread_req_, 0, sizeof(io_thread_req_));
329
1561
}
330
331
5464
AgentImpl::~AgentImpl() {
332
1366
  if (!inspector_)
333
    return;
334
  uv_close(reinterpret_cast<uv_handle_t*>(&data_written_), nullptr);
335
1366
}
336
337
void AgentImpl::Start(v8::Platform* platform, int port, bool wait) {
338
  auto env = parent_env_;
339
  inspector_ = new V8NodeInspector(this, env, platform);
340
341
  int err;
342
343
  platform_ = platform;
344
345
  err = uv_loop_init(&child_loop_);
346
  CHECK_EQ(err, 0);
347
  err = uv_async_init(env->event_loop(), &data_written_, nullptr);
348
  CHECK_EQ(err, 0);
349
350
  uv_unref(reinterpret_cast<uv_handle_t*>(&data_written_));
351
352
  port_ = port;
353
  wait_ = wait;
354
355
  err = uv_thread_create(&thread_, AgentImpl::ThreadCbIO, this);
356
  CHECK_EQ(err, 0);
357
  uv_sem_wait(&start_sem_);
358
359
  if (wait) {
360
    DispatchMessages();
361
  }
362
}
363
364
void AgentImpl::Stop() {
365
  // TODO(repenaxa): hop on the right thread.
366
  DisconnectAndDisposeIO(client_socket_);
367
  int err = uv_thread_join(&thread_);
368
  CHECK_EQ(err, 0);
369
370
  uv_run(&child_loop_, UV_RUN_NOWAIT);
371
372
  err = uv_loop_close(&child_loop_);
373
  CHECK_EQ(err, 0);
374
  delete inspector_;
375
}
376
377
bool AgentImpl::IsStarted() {
378
  return !!platform_;
379
}
380
381
void AgentImpl::WaitForDisconnect() {
382
  shutting_down_ = true;
383
  fprintf(stderr, "Waiting for the debugger to disconnect...\n");
384
  inspector_->runMessageLoopOnPause(0);
385
}
386
387
// static
388
void AgentImpl::ThreadCbIO(void* agent) {
389
  static_cast<AgentImpl*>(agent)->WorkerRunIO();
390
}
391
392
// static
393
void AgentImpl::OnSocketConnectionIO(uv_stream_t* server, int status) {
394
  if (status == 0) {
395
    inspector_socket_t* socket = new inspector_socket_t();
396
    memset(socket, 0, sizeof(*socket));
397
    socket->data = server->data;
398
    if (inspector_accept(server, socket,
399
                         AgentImpl::OnInspectorHandshakeIO) != 0) {
400
      delete socket;
401
    }
402
  }
403
}
404
405
// static
406
bool AgentImpl::OnInspectorHandshakeIO(inspector_socket_t* socket,
407
                                   enum inspector_handshake_event state,
408
                                   const char* path) {
409
  AgentImpl* agent = static_cast<AgentImpl*>(socket->data);
410
  switch (state) {
411
  case kInspectorHandshakeHttpGet:
412
    return RespondToGet(socket, path, agent->port_);
413
  case kInspectorHandshakeUpgrading:
414
    return AcceptsConnection(socket, path);
415
  case kInspectorHandshakeUpgraded:
416
    agent->OnInspectorConnectionIO(socket);
417
    return true;
418
  case kInspectorHandshakeFailed:
419
    delete socket;
420
    return false;
421
  default:
422
    UNREACHABLE();
423
  }
424
}
425
426
void AgentImpl::OnRemoteDataIO(inspector_socket_t* socket,
427
                               ssize_t read,
428
                               const uv_buf_t* buf) {
429
  Mutex::ScopedLock scoped_lock(pause_lock_);
430
  if (read > 0) {
431
    String16 str = String16::fromUTF8(buf->base, read);
432
    PostIncomingMessage(str);
433
    // TODO(pfeldman): Instead of blocking execution while debugger
434
    // engages, node should wait for the run callback from the remote client
435
    // and initiate its startup. This is a change to node.cc that should be
436
    // upstreamed separately.
437
    if (wait_ && str.find("\"Runtime.run\"") != std::string::npos) {
438
      wait_ = false;
439
      uv_sem_post(&start_sem_);
440
    }
441
442
    platform_->CallOnForegroundThread(parent_env_->isolate(),
443
        new DispatchOnInspectorBackendTask(this));
444
    parent_env_->isolate()->RequestInterrupt(InterruptCallback, this);
445
    uv_async_send(&data_written_);
446
  } else if (read <= 0) {
447
    // EOF
448
    if (client_socket_ == socket) {
449
      String16 message(TAG_DISCONNECT, sizeof(TAG_DISCONNECT) - 1);
450
      client_socket_ = nullptr;
451
      PostIncomingMessage(message);
452
    }
453
    DisconnectAndDisposeIO(socket);
454
  }
455
  if (buf) {
456
    free(buf->base);
457
  }
458
  pause_cond_.Broadcast(scoped_lock);
459
}
460
461
// static
462
void AgentImpl::WriteCbIO(uv_async_t* async) {
463
  AgentImpl* agent = static_cast<AgentImpl*>(async->data);
464
  inspector_socket_t* socket = agent->client_socket_;
465
  if (socket) {
466
    MessageQueue outgoing_messages;
467
    agent->SwapBehindLock(&agent->outgoing_message_queue_, &outgoing_messages);
468
    for (const MessageQueue::value_type& outgoing : outgoing_messages) {
469
      if (outgoing.first == agent->frontend_session_id_) {
470
        std::string message = outgoing.second.utf8();
471
        inspector_write(socket, message.c_str(), message.length());
472
      }
473
    }
474
  }
475
}
476
477
void AgentImpl::WorkerRunIO() {
478
  sockaddr_in addr;
479
  uv_tcp_t server;
480
  int err = uv_async_init(&child_loop_, &io_thread_req_, AgentImpl::WriteCbIO);
481
  CHECK_EQ(0, err);
482
  io_thread_req_.data = this;
483
  uv_tcp_init(&child_loop_, &server);
484
  uv_ip4_addr("0.0.0.0", port_, &addr);
485
  server.data = this;
486
  err = uv_tcp_bind(&server,
487
                    reinterpret_cast<const struct sockaddr*>(&addr), 0);
488
  if (err == 0) {
489
    err = uv_listen(reinterpret_cast<uv_stream_t*>(&server), 1,
490
                    OnSocketConnectionIO);
491
  }
492
  if (err == 0) {
493
    PrintDebuggerReadyMessage(port_);
494
  } else {
495
    fprintf(stderr, "Unable to open devtools socket: %s\n", uv_strerror(err));
496
    ABORT();
497
  }
498
  if (!wait_) {
499
    uv_sem_post(&start_sem_);
500
  }
501
  uv_run(&child_loop_, UV_RUN_DEFAULT);
502
  uv_close(reinterpret_cast<uv_handle_t*>(&io_thread_req_), nullptr);
503
  uv_close(reinterpret_cast<uv_handle_t*>(&server), nullptr);
504
  uv_run(&child_loop_, UV_RUN_DEFAULT);
505
}
506
507
void AgentImpl::AppendMessage(MessageQueue* queue, int session_id,
508
                              const String16& message) {
509
  Mutex::ScopedLock scoped_lock(queue_lock_);
510
  queue->push_back(std::make_pair(session_id, message));
511
}
512
513
void AgentImpl::SwapBehindLock(MessageQueue* vector1, MessageQueue* vector2) {
514
  Mutex::ScopedLock scoped_lock(queue_lock_);
515
  vector1->swap(*vector2);
516
}
517
518
void AgentImpl::PostIncomingMessage(const String16& message) {
519
  AppendMessage(&incoming_message_queue_, frontend_session_id_, message);
520
  v8::Isolate* isolate = parent_env_->isolate();
521
  platform_->CallOnForegroundThread(isolate,
522
                                    new DispatchOnInspectorBackendTask(this));
523
  isolate->RequestInterrupt(InterruptCallback, this);
524
  uv_async_send(&data_written_);
525
}
526
527
void AgentImpl::OnInspectorConnectionIO(inspector_socket_t* socket) {
528
  if (client_socket_) {
529
    DisconnectAndDisposeIO(socket);
530
    return;
531
  }
532
  client_socket_ = socket;
533
  inspector_read_start(socket, OnBufferAlloc, DataCallback);
534
  frontend_session_id_++;
535
  PostIncomingMessage(String16(TAG_CONNECT, sizeof(TAG_CONNECT) - 1));
536
}
537
538
void AgentImpl::DispatchMessages() {
539
  if (dispatching_messages_)
540
    return;
541
  dispatching_messages_ = true;
542
  MessageQueue tasks;
543
  SwapBehindLock(&incoming_message_queue_, &tasks);
544
  for (const MessageQueue::value_type& pair : tasks) {
545
    const String16& message = pair.second;
546
    if (message == TAG_CONNECT) {
547
      CHECK_EQ(false, connected_);
548
      backend_session_id_++;
549
      connected_ = true;
550
      fprintf(stderr, "Debugger attached.\n");
551
      inspector_->connectFrontend(new ChannelImpl(this));
552
    } else if (message == TAG_DISCONNECT) {
553
      CHECK(connected_);
554
      connected_ = false;
555
      if (!shutting_down_)
556
        PrintDebuggerReadyMessage(port_);
557
      inspector_->quitMessageLoopOnPause();
558
      inspector_->disconnectFrontend();
559
    } else {
560
      inspector_->dispatchMessageFromFrontend(message);
561
    }
562
  }
563
  uv_async_send(&data_written_);
564
  dispatching_messages_ = false;
565
}
566
567
void AgentImpl::Write(int session_id, const String16& message) {
568
  AppendMessage(&outgoing_message_queue_, session_id, message);
569
  int err = uv_async_send(&io_thread_req_);
570
  CHECK_EQ(0, err);
571
}
572
573
// Exported class Agent
574
1561
Agent::Agent(node::Environment* env) : impl(new AgentImpl(env)) {}
575
576
2732
Agent::~Agent() {
577
1366
  delete impl;
578
1366
}
579
580
void Agent::Start(v8::Platform* platform, int port, bool wait) {
581
  impl->Start(platform, port, wait);
582
}
583
584
void Agent::Stop() {
585
  impl->Stop();
586
}
587
588
bool Agent::IsStarted() {
589
  return impl->IsStarted();
590
}
591
592
1503
bool Agent::IsConnected() {
593
1503
  return impl->IsConnected();
594
}
595
596
void Agent::WaitForDisconnect() {
597
  impl->WaitForDisconnect();
598
}
599
600
}  // namespace inspector
601
}  // namespace node