1 #include "inspector_agent.h"
2
3 #include "inspector_io.h"
4 #include "inspector/main_thread_interface.h"
5 #include "inspector/node_string.h"
6 #include "inspector/tracing_agent.h"
7 #include "inspector/worker_agent.h"
8 #include "inspector/worker_inspector.h"
9 #include "node/inspector/protocol/Protocol.h"
10 #include "node_internals.h"
11 #include "node_url.h"
12 #include "v8-inspector.h"
13 #include "v8-platform.h"
14
15 #include "libplatform/libplatform.h"
16
17 #include <algorithm>
18 #include <string.h>
19 #include <sstream>
20 #include <unordered_map>
21 #include <vector>
22
23 #ifdef __POSIX__
24 #include <limits.h> // PTHREAD_STACK_MIN
25 #include <pthread.h>
26 #endif // __POSIX__
27
28 namespace node {
29 namespace inspector {
30 namespace {
31
32 using node::FatalError;
33
34 using v8::Array;
35 using v8::Context;
36 using v8::Function;
37 using v8::HandleScope;
38 using v8::Isolate;
39 using v8::Local;
40 using v8::Message;
41 using v8::Object;
42 using v8::String;
43 using v8::Task;
44 using v8::TaskRunner;
45 using v8::Value;
46
47 using v8_inspector::StringBuffer;
48 using v8_inspector::StringView;
49 using v8_inspector::V8Inspector;
50 using v8_inspector::V8InspectorClient;
51
52 static uv_sem_t start_io_thread_semaphore;
53 static uv_async_t start_io_thread_async;
54
55 class StartIoTask : public Task {
56 public:
StartIoTask(Agent * agent)57 explicit StartIoTask(Agent* agent) : agent(agent) {}
58
Run()59 void Run() override {
60 agent->StartIoThread();
61 }
62
63 private:
64 Agent* agent;
65 };
66
ToProtocolString(Isolate * isolate,Local<Value> value)67 std::unique_ptr<StringBuffer> ToProtocolString(Isolate* isolate,
68 Local<Value> value) {
69 TwoByteValue buffer(isolate, value);
70 return StringBuffer::create(StringView(*buffer, buffer.length()));
71 }
72
73 // Called on the main thread.
StartIoThreadAsyncCallback(uv_async_t * handle)74 void StartIoThreadAsyncCallback(uv_async_t* handle) {
75 static_cast<Agent*>(handle->data)->StartIoThread();
76 }
77
StartIoInterrupt(Isolate * isolate,void * agent)78 void StartIoInterrupt(Isolate* isolate, void* agent) {
79 static_cast<Agent*>(agent)->StartIoThread();
80 }
81
82
83 #ifdef __POSIX__
StartIoThreadWakeup(int signo)84 static void StartIoThreadWakeup(int signo) {
85 uv_sem_post(&start_io_thread_semaphore);
86 }
87
StartIoThreadMain(void * unused)88 inline void* StartIoThreadMain(void* unused) {
89 for (;;) {
90 uv_sem_wait(&start_io_thread_semaphore);
91 Agent* agent = static_cast<Agent*>(start_io_thread_async.data);
92 if (agent != nullptr)
93 agent->RequestIoThreadStart();
94 }
95 return nullptr;
96 }
97
StartDebugSignalHandler()98 static int StartDebugSignalHandler() {
99 // Start a watchdog thread for calling v8::Debug::DebugBreak() because
100 // it's not safe to call directly from the signal handler, it can
101 // deadlock with the thread it interrupts.
102 CHECK_EQ(0, uv_sem_init(&start_io_thread_semaphore, 0));
103 pthread_attr_t attr;
104 CHECK_EQ(0, pthread_attr_init(&attr));
105 #if defined(PTHREAD_STACK_MIN) && !defined(__FreeBSD__)
106 // PTHREAD_STACK_MIN is 2 KB with musl libc, which is too small to safely
107 // receive signals. PTHREAD_STACK_MIN + MINSIGSTKSZ is 8 KB on arm64, which
108 // is the musl architecture with the biggest MINSIGSTKSZ so let's use that
109 // as a lower bound and let's quadruple it just in case. The goal is to avoid
110 // creating a big 2 or 4 MB address space gap (problematic on 32 bits
111 // because of fragmentation), not squeeze out every last byte.
112 // Omitted on FreeBSD because it doesn't seem to like small stacks.
113 const size_t stack_size = std::max(static_cast<size_t>(4 * 8192),
114 static_cast<size_t>(PTHREAD_STACK_MIN));
115 CHECK_EQ(0, pthread_attr_setstacksize(&attr, stack_size));
116 #endif // defined(PTHREAD_STACK_MIN) && !defined(__FreeBSD__)
117 CHECK_EQ(0, pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED));
118 sigset_t sigmask;
119 // Mask all signals.
120 sigfillset(&sigmask);
121 sigset_t savemask;
122 CHECK_EQ(0, pthread_sigmask(SIG_SETMASK, &sigmask, &savemask));
123 sigmask = savemask;
124 pthread_t thread;
125 const int err = pthread_create(&thread, &attr,
126 StartIoThreadMain, nullptr);
127 // Restore original mask
128 CHECK_EQ(0, pthread_sigmask(SIG_SETMASK, &sigmask, nullptr));
129 CHECK_EQ(0, pthread_attr_destroy(&attr));
130 if (err != 0) {
131 fprintf(stderr, "node[%u]: pthread_create: %s\n",
132 uv_os_getpid(), strerror(err));
133 fflush(stderr);
134 // Leave SIGUSR1 blocked. We don't install a signal handler,
135 // receiving the signal would terminate the process.
136 return -err;
137 }
138 RegisterSignalHandler(SIGUSR1, StartIoThreadWakeup);
139 // Unblock SIGUSR1. A pending SIGUSR1 signal will now be delivered.
140 sigemptyset(&sigmask);
141 sigaddset(&sigmask, SIGUSR1);
142 CHECK_EQ(0, pthread_sigmask(SIG_UNBLOCK, &sigmask, nullptr));
143 return 0;
144 }
145 #endif // __POSIX__
146
147
148 #ifdef _WIN32
StartIoThreadProc(void * arg)149 DWORD WINAPI StartIoThreadProc(void* arg) {
150 Agent* agent = static_cast<Agent*>(start_io_thread_async.data);
151 if (agent != nullptr)
152 agent->RequestIoThreadStart();
153 return 0;
154 }
155
GetDebugSignalHandlerMappingName(DWORD pid,wchar_t * buf,size_t buf_len)156 static int GetDebugSignalHandlerMappingName(DWORD pid, wchar_t* buf,
157 size_t buf_len) {
158 return _snwprintf(buf, buf_len, L"node-debug-handler-%u", pid);
159 }
160
StartDebugSignalHandler()161 static int StartDebugSignalHandler() {
162 wchar_t mapping_name[32];
163 HANDLE mapping_handle;
164 DWORD pid;
165 LPTHREAD_START_ROUTINE* handler;
166
167 pid = uv_os_getpid();
168
169 if (GetDebugSignalHandlerMappingName(pid,
170 mapping_name,
171 arraysize(mapping_name)) < 0) {
172 return -1;
173 }
174
175 mapping_handle = CreateFileMappingW(INVALID_HANDLE_VALUE,
176 nullptr,
177 PAGE_READWRITE,
178 0,
179 sizeof *handler,
180 mapping_name);
181 if (mapping_handle == nullptr) {
182 return -1;
183 }
184
185 handler = reinterpret_cast<LPTHREAD_START_ROUTINE*>(
186 MapViewOfFile(mapping_handle,
187 FILE_MAP_ALL_ACCESS,
188 0,
189 0,
190 sizeof *handler));
191 if (handler == nullptr) {
192 CloseHandle(mapping_handle);
193 return -1;
194 }
195
196 *handler = StartIoThreadProc;
197
198 UnmapViewOfFile(static_cast<void*>(handler));
199
200 return 0;
201 }
202 #endif // _WIN32
203
204
205 const int CONTEXT_GROUP_ID = 1;
206
GetWorkerLabel(node::Environment * env)207 std::string GetWorkerLabel(node::Environment* env) {
208 std::ostringstream result;
209 result << "Worker[" << env->thread_id() << "]";
210 return result.str();
211 }
212
213 class ChannelImpl final : public v8_inspector::V8Inspector::Channel,
214 public protocol::FrontendChannel {
215 public:
ChannelImpl(Environment * env,const std::unique_ptr<V8Inspector> & inspector,std::shared_ptr<WorkerManager> worker_manager,std::unique_ptr<InspectorSessionDelegate> delegate,bool prevent_shutdown)216 explicit ChannelImpl(Environment* env,
217 const std::unique_ptr<V8Inspector>& inspector,
218 std::shared_ptr<WorkerManager> worker_manager,
219 std::unique_ptr<InspectorSessionDelegate> delegate,
220 bool prevent_shutdown)
221 : delegate_(std::move(delegate)),
222 prevent_shutdown_(prevent_shutdown) {
223 session_ = inspector->connect(1, this, StringView());
224 node_dispatcher_.reset(new protocol::UberDispatcher(this));
225 tracing_agent_.reset(new protocol::TracingAgent(env));
226 tracing_agent_->Wire(node_dispatcher_.get());
227 worker_agent_.reset(new protocol::WorkerAgent(worker_manager));
228 worker_agent_->Wire(node_dispatcher_.get());
229 }
230
~ChannelImpl()231 virtual ~ChannelImpl() {
232 tracing_agent_->disable();
233 tracing_agent_.reset(); // Dispose before the dispatchers
234 worker_agent_->disable();
235 worker_agent_.reset(); // Dispose before the dispatchers
236 }
237
dispatchProtocolMessage(const StringView & message)238 std::string dispatchProtocolMessage(const StringView& message) {
239 std::string raw_message = protocol::StringUtil::StringViewToUtf8(message);
240 std::unique_ptr<protocol::DictionaryValue> value =
241 protocol::DictionaryValue::cast(protocol::StringUtil::parseMessage(
242 raw_message, false));
243 int call_id;
244 std::string method;
245 node_dispatcher_->parseCommand(value.get(), &call_id, &method);
246 if (v8_inspector::V8InspectorSession::canDispatchMethod(
247 Utf8ToStringView(method)->string())) {
248 session_->dispatchProtocolMessage(message);
249 } else {
250 node_dispatcher_->dispatch(call_id, method, std::move(value),
251 raw_message);
252 }
253 return method;
254 }
255
schedulePauseOnNextStatement(const std::string & reason)256 void schedulePauseOnNextStatement(const std::string& reason) {
257 std::unique_ptr<StringBuffer> buffer = Utf8ToStringView(reason);
258 session_->schedulePauseOnNextStatement(buffer->string(), buffer->string());
259 }
260
preventShutdown()261 bool preventShutdown() {
262 return prevent_shutdown_;
263 }
264
265 private:
sendResponse(int callId,std::unique_ptr<v8_inspector::StringBuffer> message)266 void sendResponse(
267 int callId,
268 std::unique_ptr<v8_inspector::StringBuffer> message) override {
269 sendMessageToFrontend(message->string());
270 }
271
sendNotification(std::unique_ptr<v8_inspector::StringBuffer> message)272 void sendNotification(
273 std::unique_ptr<v8_inspector::StringBuffer> message) override {
274 sendMessageToFrontend(message->string());
275 }
276
flushProtocolNotifications()277 void flushProtocolNotifications() override { }
278
sendMessageToFrontend(const StringView & message)279 void sendMessageToFrontend(const StringView& message) {
280 delegate_->SendMessageToFrontend(message);
281 }
282
sendMessageToFrontend(const std::string & message)283 void sendMessageToFrontend(const std::string& message) {
284 sendMessageToFrontend(Utf8ToStringView(message)->string());
285 }
286
287 using Serializable = protocol::Serializable;
288
sendProtocolResponse(int callId,std::unique_ptr<Serializable> message)289 void sendProtocolResponse(int callId,
290 std::unique_ptr<Serializable> message) override {
291 sendMessageToFrontend(message->serializeToJSON());
292 }
sendProtocolNotification(std::unique_ptr<Serializable> message)293 void sendProtocolNotification(
294 std::unique_ptr<Serializable> message) override {
295 sendMessageToFrontend(message->serializeToJSON());
296 }
297
fallThrough(int callId,const std::string & method,const std::string & message)298 void fallThrough(int callId,
299 const std::string& method,
300 const std::string& message) override {
301 DCHECK(false);
302 }
303
304 std::unique_ptr<protocol::TracingAgent> tracing_agent_;
305 std::unique_ptr<protocol::WorkerAgent> worker_agent_;
306 std::unique_ptr<InspectorSessionDelegate> delegate_;
307 std::unique_ptr<v8_inspector::V8InspectorSession> session_;
308 std::unique_ptr<protocol::UberDispatcher> node_dispatcher_;
309 bool prevent_shutdown_;
310 };
311
312 class InspectorTimer {
313 public:
InspectorTimer(uv_loop_t * loop,double interval_s,V8InspectorClient::TimerCallback callback,void * data)314 InspectorTimer(uv_loop_t* loop,
315 double interval_s,
316 V8InspectorClient::TimerCallback callback,
317 void* data) : timer_(),
318 callback_(callback),
319 data_(data) {
320 uv_timer_init(loop, &timer_);
321 int64_t interval_ms = 1000 * interval_s;
322 uv_timer_start(&timer_, OnTimer, interval_ms, interval_ms);
323 }
324
325 InspectorTimer(const InspectorTimer&) = delete;
326
Stop()327 void Stop() {
328 uv_timer_stop(&timer_);
329 uv_close(reinterpret_cast<uv_handle_t*>(&timer_), TimerClosedCb);
330 }
331
332 private:
OnTimer(uv_timer_t * uvtimer)333 static void OnTimer(uv_timer_t* uvtimer) {
334 InspectorTimer* timer = node::ContainerOf(&InspectorTimer::timer_, uvtimer);
335 timer->callback_(timer->data_);
336 }
337
TimerClosedCb(uv_handle_t * uvtimer)338 static void TimerClosedCb(uv_handle_t* uvtimer) {
339 std::unique_ptr<InspectorTimer> timer(
340 node::ContainerOf(&InspectorTimer::timer_,
341 reinterpret_cast<uv_timer_t*>(uvtimer)));
342 // Unique_ptr goes out of scope here and pointer is deleted.
343 }
344
~InspectorTimer()345 ~InspectorTimer() {}
346
347 uv_timer_t timer_;
348 V8InspectorClient::TimerCallback callback_;
349 void* data_;
350
351 friend std::unique_ptr<InspectorTimer>::deleter_type;
352 };
353
354 class InspectorTimerHandle {
355 public:
InspectorTimerHandle(uv_loop_t * loop,double interval_s,V8InspectorClient::TimerCallback callback,void * data)356 InspectorTimerHandle(uv_loop_t* loop, double interval_s,
357 V8InspectorClient::TimerCallback callback, void* data) {
358 timer_ = new InspectorTimer(loop, interval_s, callback, data);
359 }
360
361 InspectorTimerHandle(const InspectorTimerHandle&) = delete;
362
~InspectorTimerHandle()363 ~InspectorTimerHandle() {
364 CHECK_NOT_NULL(timer_);
365 timer_->Stop();
366 timer_ = nullptr;
367 }
368 private:
369 InspectorTimer* timer_;
370 };
371
372 class SameThreadInspectorSession : public InspectorSession {
373 public:
SameThreadInspectorSession(int session_id,std::shared_ptr<NodeInspectorClient> client)374 SameThreadInspectorSession(
375 int session_id, std::shared_ptr<NodeInspectorClient> client)
376 : session_id_(session_id), client_(client) {}
377 ~SameThreadInspectorSession() override;
378 void Dispatch(const v8_inspector::StringView& message) override;
379
380 private:
381 int session_id_;
382 std::weak_ptr<NodeInspectorClient> client_;
383 };
384
NotifyClusterWorkersDebugEnabled(Environment * env)385 void NotifyClusterWorkersDebugEnabled(Environment* env) {
386 Isolate* isolate = env->isolate();
387 HandleScope handle_scope(isolate);
388 auto context = env->context();
389
390 // Send message to enable debug in cluster workers
391 Local<Object> process_object = env->process_object();
392 Local<Value> emit_fn =
393 process_object->Get(context, FIXED_ONE_BYTE_STRING(isolate, "emit"))
394 .ToLocalChecked();
395 // In case the thread started early during the startup
396 if (!emit_fn->IsFunction())
397 return;
398
399 Local<Object> message = Object::New(isolate);
400 message->Set(context, FIXED_ONE_BYTE_STRING(isolate, "cmd"),
401 FIXED_ONE_BYTE_STRING(isolate, "NODE_DEBUG_ENABLED")).FromJust();
402 Local<Value> argv[] = {
403 FIXED_ONE_BYTE_STRING(isolate, "internalMessage"),
404 message
405 };
406 MakeCallback(env->isolate(), process_object, emit_fn.As<Function>(),
407 arraysize(argv), argv, {0, 0});
408 }
409
410 #ifdef _WIN32
IsFilePath(const std::string & path)411 bool IsFilePath(const std::string& path) {
412 // '\\'
413 if (path.length() > 2 && path[0] == '\\' && path[1] == '\\')
414 return true;
415 // '[A-Z]:[/\\]'
416 if (path.length() < 3)
417 return false;
418 if ((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z'))
419 return path[1] == ':' && (path[2] == '/' || path[2] == '\\');
420 return false;
421 }
422 #else
IsFilePath(const std::string & path)423 bool IsFilePath(const std::string& path) {
424 return path.length() && path[0] == '/';
425 }
426 #endif // __POSIX__
427
428 } // namespace
429
430 class NodeInspectorClient : public V8InspectorClient {
431 public:
NodeInspectorClient(node::Environment * env,bool is_main)432 explicit NodeInspectorClient(node::Environment* env, bool is_main)
433 : env_(env), is_main_(is_main) {
434 client_ = V8Inspector::create(env->isolate(), this);
435 // TODO(bnoordhuis) Make name configurable from src/node.cc.
436 std::string name =
437 is_main_ ? GetHumanReadableProcessName() : GetWorkerLabel(env);
438 ContextInfo info(name);
439 info.is_default = true;
440 contextCreated(env->context(), info);
441 }
442
runMessageLoopOnPause(int context_group_id)443 void runMessageLoopOnPause(int context_group_id) override {
444 waiting_for_resume_ = true;
445 runMessageLoop();
446 }
447
waitForIoShutdown()448 void waitForIoShutdown() {
449 waiting_for_io_shutdown_ = true;
450 runMessageLoop();
451 }
452
waitForFrontend()453 void waitForFrontend() {
454 waiting_for_frontend_ = true;
455 runMessageLoop();
456 }
457
maxAsyncCallStackDepthChanged(int depth)458 void maxAsyncCallStackDepthChanged(int depth) override {
459 if (auto agent = env_->inspector_agent()) {
460 if (depth == 0) {
461 agent->DisableAsyncHook();
462 } else {
463 agent->EnableAsyncHook();
464 }
465 }
466 }
467
contextCreated(Local<Context> context,const ContextInfo & info)468 void contextCreated(Local<Context> context, const ContextInfo& info) {
469 auto name_buffer = Utf8ToStringView(info.name);
470 auto origin_buffer = Utf8ToStringView(info.origin);
471 std::unique_ptr<StringBuffer> aux_data_buffer;
472
473 v8_inspector::V8ContextInfo v8info(
474 context, CONTEXT_GROUP_ID, name_buffer->string());
475 v8info.origin = origin_buffer->string();
476
477 if (info.is_default) {
478 aux_data_buffer = Utf8ToStringView("{\"isDefault\":true}");
479 } else {
480 aux_data_buffer = Utf8ToStringView("{\"isDefault\":false}");
481 }
482 v8info.auxData = aux_data_buffer->string();
483
484 client_->contextCreated(v8info);
485 }
486
contextDestroyed(Local<Context> context)487 void contextDestroyed(Local<Context> context) {
488 client_->contextDestroyed(context);
489 }
490
quitMessageLoopOnPause()491 void quitMessageLoopOnPause() override {
492 waiting_for_resume_ = false;
493 }
494
connectFrontend(std::unique_ptr<InspectorSessionDelegate> delegate,bool prevent_shutdown)495 int connectFrontend(std::unique_ptr<InspectorSessionDelegate> delegate,
496 bool prevent_shutdown) {
497 events_dispatched_ = true;
498 int session_id = next_session_id_++;
499 channels_[session_id] =
500 std::make_unique<ChannelImpl>(env_, client_, getWorkerManager(),
501 std::move(delegate), prevent_shutdown);
502 return session_id;
503 }
504
disconnectFrontend(int session_id)505 void disconnectFrontend(int session_id) {
506 events_dispatched_ = true;
507 channels_.erase(session_id);
508 }
509
dispatchMessageFromFrontend(int session_id,const StringView & message)510 void dispatchMessageFromFrontend(int session_id, const StringView& message) {
511 events_dispatched_ = true;
512 std::string method =
513 channels_[session_id]->dispatchProtocolMessage(message);
514 if (waiting_for_frontend_)
515 waiting_for_frontend_ = method != "Runtime.runIfWaitingForDebugger";
516 }
517
ensureDefaultContextInGroup(int contextGroupId)518 Local<Context> ensureDefaultContextInGroup(int contextGroupId) override {
519 return env_->context();
520 }
521
installAdditionalCommandLineAPI(Local<Context> context,Local<Object> target)522 void installAdditionalCommandLineAPI(Local<Context> context,
523 Local<Object> target) override {
524 Local<Object> console_api = env_->inspector_console_api_object();
525 CHECK(!console_api.IsEmpty());
526
527 Local<Array> properties =
528 console_api->GetOwnPropertyNames(context).ToLocalChecked();
529 for (uint32_t i = 0; i < properties->Length(); ++i) {
530 Local<Value> key = properties->Get(context, i).ToLocalChecked();
531 target->Set(context,
532 key,
533 console_api->Get(context, key).ToLocalChecked()).FromJust();
534 }
535 }
536
FatalException(Local<Value> error,Local<Message> message)537 void FatalException(Local<Value> error, Local<Message> message) {
538 Isolate* isolate = env_->isolate();
539 Local<Context> context = env_->context();
540
541 int script_id = message->GetScriptOrigin().ScriptID()->Value();
542
543 Local<v8::StackTrace> stack_trace = message->GetStackTrace();
544
545 if (!stack_trace.IsEmpty() && stack_trace->GetFrameCount() > 0 &&
546 script_id == stack_trace->GetFrame(isolate, 0)->GetScriptId()) {
547 script_id = 0;
548 }
549
550 const uint8_t DETAILS[] = "Uncaught";
551
552 client_->exceptionThrown(
553 context,
554 StringView(DETAILS, sizeof(DETAILS) - 1),
555 error,
556 ToProtocolString(isolate, message->Get())->string(),
557 ToProtocolString(isolate, message->GetScriptResourceName())->string(),
558 message->GetLineNumber(context).FromMaybe(0),
559 message->GetStartColumn(context).FromMaybe(0),
560 client_->createStackTrace(stack_trace),
561 script_id);
562 }
563
startRepeatingTimer(double interval_s,TimerCallback callback,void * data)564 void startRepeatingTimer(double interval_s,
565 TimerCallback callback,
566 void* data) override {
567 timers_.emplace(std::piecewise_construct, std::make_tuple(data),
568 std::make_tuple(env_->event_loop(), interval_s, callback,
569 data));
570 }
571
cancelTimer(void * data)572 void cancelTimer(void* data) override {
573 timers_.erase(data);
574 }
575
576 // Async stack traces instrumentation.
AsyncTaskScheduled(const StringView & task_name,void * task,bool recurring)577 void AsyncTaskScheduled(const StringView& task_name, void* task,
578 bool recurring) {
579 client_->asyncTaskScheduled(task_name, task, recurring);
580 }
581
AsyncTaskCanceled(void * task)582 void AsyncTaskCanceled(void* task) {
583 client_->asyncTaskCanceled(task);
584 }
585
AsyncTaskStarted(void * task)586 void AsyncTaskStarted(void* task) {
587 client_->asyncTaskStarted(task);
588 }
589
AsyncTaskFinished(void * task)590 void AsyncTaskFinished(void* task) {
591 client_->asyncTaskFinished(task);
592 }
593
AllAsyncTasksCanceled()594 void AllAsyncTasksCanceled() {
595 client_->allAsyncTasksCanceled();
596 }
597
schedulePauseOnNextStatement(const std::string & reason)598 void schedulePauseOnNextStatement(const std::string& reason) {
599 for (const auto& id_channel : channels_) {
600 id_channel.second->schedulePauseOnNextStatement(reason);
601 }
602 }
603
hasConnectedSessions()604 bool hasConnectedSessions() {
605 for (const auto& id_channel : channels_) {
606 // Other sessions are "invisible" more most purposes
607 if (id_channel.second->preventShutdown())
608 return true;
609 }
610 return false;
611 }
612
getThreadHandle()613 std::shared_ptr<MainThreadHandle> getThreadHandle() {
614 if (interface_ == nullptr) {
615 interface_.reset(new MainThreadInterface(
616 env_->inspector_agent(), env_->event_loop(), env_->isolate(),
617 env_->isolate_data()->platform()));
618 }
619 return interface_->GetHandle();
620 }
621
getWorkerManager()622 std::shared_ptr<WorkerManager> getWorkerManager() {
623 if (worker_manager_ == nullptr) {
624 worker_manager_ =
625 std::make_shared<WorkerManager>(getThreadHandle());
626 }
627 return worker_manager_;
628 }
629
IsActive()630 bool IsActive() {
631 return !channels_.empty();
632 }
633
634 private:
shouldRunMessageLoop()635 bool shouldRunMessageLoop() {
636 if (waiting_for_frontend_)
637 return true;
638 if (waiting_for_io_shutdown_ || waiting_for_resume_)
639 return hasConnectedSessions();
640 return false;
641 }
642
runMessageLoop()643 void runMessageLoop() {
644 if (running_nested_loop_)
645 return;
646
647 running_nested_loop_ = true;
648
649 MultiIsolatePlatform* platform = env_->isolate_data()->platform();
650 while (shouldRunMessageLoop()) {
651 if (interface_ && hasConnectedSessions())
652 interface_->WaitForFrontendEvent();
653 while (platform->FlushForegroundTasks(env_->isolate())) {}
654 }
655 running_nested_loop_ = false;
656 }
657
currentTimeMS()658 double currentTimeMS() override {
659 return env_->isolate_data()->platform()->CurrentClockTimeMillis();
660 }
661
resourceNameToUrl(const StringView & resource_name_view)662 std::unique_ptr<StringBuffer> resourceNameToUrl(
663 const StringView& resource_name_view) override {
664 std::string resource_name =
665 protocol::StringUtil::StringViewToUtf8(resource_name_view);
666 if (!IsFilePath(resource_name))
667 return nullptr;
668 node::url::URL url = node::url::URL::FromFilePath(resource_name);
669 // TODO(ak239spb): replace this code with url.href().
670 // Refs: https://github.com/nodejs/node/issues/22610
671 return Utf8ToStringView(url.protocol() + "//" + url.path());
672 }
673
674 node::Environment* env_;
675 bool is_main_;
676 bool running_nested_loop_ = false;
677 std::unique_ptr<V8Inspector> client_;
678 std::unordered_map<int, std::unique_ptr<ChannelImpl>> channels_;
679 std::unordered_map<void*, InspectorTimerHandle> timers_;
680 int next_session_id_ = 1;
681 bool events_dispatched_ = false;
682 bool waiting_for_resume_ = false;
683 bool waiting_for_frontend_ = false;
684 bool waiting_for_io_shutdown_ = false;
685 // Allows accessing Inspector from non-main threads
686 std::unique_ptr<MainThreadInterface> interface_;
687 std::shared_ptr<WorkerManager> worker_manager_;
688 };
689
Agent(Environment * env)690 Agent::Agent(Environment* env)
691 : parent_env_(env),
692 debug_options_(env->options()->debug_options) {}
693
~Agent()694 Agent::~Agent() {
695 if (start_io_thread_async.data == this) {
696 start_io_thread_async.data = nullptr;
697 // This is global, will never get freed
698 uv_close(reinterpret_cast<uv_handle_t*>(&start_io_thread_async), nullptr);
699 }
700 }
701
Start(const std::string & path,std::shared_ptr<DebugOptions> options,bool is_main)702 bool Agent::Start(const std::string& path,
703 std::shared_ptr<DebugOptions> options,
704 bool is_main) {
705 if (options == nullptr) {
706 options = std::make_shared<DebugOptions>();
707 }
708 path_ = path;
709 debug_options_ = options;
710 client_ = std::make_shared<NodeInspectorClient>(parent_env_, is_main);
711 if (parent_env_->is_main_thread()) {
712 CHECK_EQ(0, uv_async_init(parent_env_->event_loop(),
713 &start_io_thread_async,
714 StartIoThreadAsyncCallback));
715 uv_unref(reinterpret_cast<uv_handle_t*>(&start_io_thread_async));
716 start_io_thread_async.data = this;
717 // Ignore failure, SIGUSR1 won't work, but that should not block node start.
718 StartDebugSignalHandler();
719 }
720
721 bool wait_for_connect = options->wait_for_connect();
722 if (parent_handle_) {
723 wait_for_connect = parent_handle_->WaitForConnect();
724 parent_handle_->WorkerStarted(client_->getThreadHandle(), wait_for_connect);
725 } else if (!options->inspector_enabled || !StartIoThread()) {
726 return false;
727 }
728 if (wait_for_connect) {
729 HandleScope scope(parent_env_->isolate());
730 parent_env_->process_object()->DefineOwnProperty(
731 parent_env_->context(),
732 FIXED_ONE_BYTE_STRING(parent_env_->isolate(), "_breakFirstLine"),
733 True(parent_env_->isolate()),
734 static_cast<v8::PropertyAttribute>(v8::ReadOnly | v8::DontEnum))
735 .FromJust();
736 client_->waitForFrontend();
737 }
738 return true;
739 }
740
StartIoThread()741 bool Agent::StartIoThread() {
742 if (io_ != nullptr)
743 return true;
744
745 CHECK_NOT_NULL(client_);
746
747 io_ = InspectorIo::Start(
748 client_->getThreadHandle(), path_, debug_options_);
749 if (io_ == nullptr) {
750 return false;
751 }
752 NotifyClusterWorkersDebugEnabled(parent_env_);
753 return true;
754 }
755
Stop()756 void Agent::Stop() {
757 io_.reset();
758 }
759
Connect(std::unique_ptr<InspectorSessionDelegate> delegate,bool prevent_shutdown)760 std::unique_ptr<InspectorSession> Agent::Connect(
761 std::unique_ptr<InspectorSessionDelegate> delegate,
762 bool prevent_shutdown) {
763 CHECK_NOT_NULL(client_);
764 int session_id = client_->connectFrontend(std::move(delegate),
765 prevent_shutdown);
766 return std::unique_ptr<InspectorSession>(
767 new SameThreadInspectorSession(session_id, client_));
768 }
769
WaitForDisconnect()770 void Agent::WaitForDisconnect() {
771 CHECK_NOT_NULL(client_);
772 bool is_worker = parent_handle_ != nullptr;
773 parent_handle_.reset();
774 if (client_->hasConnectedSessions() && !is_worker) {
775 fprintf(stderr, "Waiting for the debugger to disconnect...\n");
776 fflush(stderr);
777 }
778 // TODO(addaleax): Maybe this should use an at-exit hook for the Environment
779 // or something similar?
780 client_->contextDestroyed(parent_env_->context());
781 if (io_ != nullptr) {
782 io_->StopAcceptingNewConnections();
783 client_->waitForIoShutdown();
784 }
785 }
786
FatalException(Local<Value> error,Local<Message> message)787 void Agent::FatalException(Local<Value> error, Local<Message> message) {
788 if (!IsListening())
789 return;
790 client_->FatalException(error, message);
791 WaitForDisconnect();
792 }
793
PauseOnNextJavascriptStatement(const std::string & reason)794 void Agent::PauseOnNextJavascriptStatement(const std::string& reason) {
795 client_->schedulePauseOnNextStatement(reason);
796 }
797
RegisterAsyncHook(Isolate * isolate,Local<Function> enable_function,Local<Function> disable_function)798 void Agent::RegisterAsyncHook(Isolate* isolate,
799 Local<Function> enable_function,
800 Local<Function> disable_function) {
801 enable_async_hook_function_.Reset(isolate, enable_function);
802 disable_async_hook_function_.Reset(isolate, disable_function);
803 if (pending_enable_async_hook_) {
804 CHECK(!pending_disable_async_hook_);
805 pending_enable_async_hook_ = false;
806 EnableAsyncHook();
807 } else if (pending_disable_async_hook_) {
808 CHECK(!pending_enable_async_hook_);
809 pending_disable_async_hook_ = false;
810 DisableAsyncHook();
811 }
812 }
813
EnableAsyncHook()814 void Agent::EnableAsyncHook() {
815 if (!enable_async_hook_function_.IsEmpty()) {
816 ToggleAsyncHook(parent_env_->isolate(), enable_async_hook_function_);
817 } else if (pending_disable_async_hook_) {
818 CHECK(!pending_enable_async_hook_);
819 pending_disable_async_hook_ = false;
820 } else {
821 pending_enable_async_hook_ = true;
822 }
823 }
824
DisableAsyncHook()825 void Agent::DisableAsyncHook() {
826 if (!disable_async_hook_function_.IsEmpty()) {
827 ToggleAsyncHook(parent_env_->isolate(), disable_async_hook_function_);
828 } else if (pending_enable_async_hook_) {
829 CHECK(!pending_disable_async_hook_);
830 pending_enable_async_hook_ = false;
831 } else {
832 pending_disable_async_hook_ = true;
833 }
834 }
835
ToggleAsyncHook(Isolate * isolate,const node::Persistent<Function> & fn)836 void Agent::ToggleAsyncHook(Isolate* isolate,
837 const node::Persistent<Function>& fn) {
838 HandleScope handle_scope(isolate);
839 CHECK(!fn.IsEmpty());
840 auto context = parent_env_->context();
841 auto result = fn.Get(isolate)->Call(context, Undefined(isolate), 0, nullptr);
842 if (result.IsEmpty()) {
843 FatalError(
844 "node::inspector::Agent::ToggleAsyncHook",
845 "Cannot toggle Inspector's AsyncHook, please report this.");
846 }
847 }
848
AsyncTaskScheduled(const StringView & task_name,void * task,bool recurring)849 void Agent::AsyncTaskScheduled(const StringView& task_name, void* task,
850 bool recurring) {
851 client_->AsyncTaskScheduled(task_name, task, recurring);
852 }
853
AsyncTaskCanceled(void * task)854 void Agent::AsyncTaskCanceled(void* task) {
855 client_->AsyncTaskCanceled(task);
856 }
857
AsyncTaskStarted(void * task)858 void Agent::AsyncTaskStarted(void* task) {
859 client_->AsyncTaskStarted(task);
860 }
861
AsyncTaskFinished(void * task)862 void Agent::AsyncTaskFinished(void* task) {
863 client_->AsyncTaskFinished(task);
864 }
865
AllAsyncTasksCanceled()866 void Agent::AllAsyncTasksCanceled() {
867 client_->AllAsyncTasksCanceled();
868 }
869
RequestIoThreadStart()870 void Agent::RequestIoThreadStart() {
871 // We need to attempt to interrupt V8 flow (in case Node is running
872 // continuous JS code) and to wake up libuv thread (in case Node is waiting
873 // for IO events)
874 uv_async_send(&start_io_thread_async);
875 Isolate* isolate = parent_env_->isolate();
876 v8::Platform* platform = parent_env_->isolate_data()->platform();
877 std::shared_ptr<TaskRunner> taskrunner =
878 platform->GetForegroundTaskRunner(isolate);
879 taskrunner->PostTask(std::make_unique<StartIoTask>(this));
880 isolate->RequestInterrupt(StartIoInterrupt, this);
881 uv_async_send(&start_io_thread_async);
882 }
883
ContextCreated(Local<Context> context,const ContextInfo & info)884 void Agent::ContextCreated(Local<Context> context, const ContextInfo& info) {
885 if (client_ == nullptr) // This happens for a main context
886 return;
887 client_->contextCreated(context, info);
888 }
889
WillWaitForConnect()890 bool Agent::WillWaitForConnect() {
891 if (debug_options_->wait_for_connect())
892 return true;
893 if (parent_handle_)
894 return parent_handle_->WaitForConnect();
895 return false;
896 }
897
IsActive()898 bool Agent::IsActive() {
899 if (client_ == nullptr)
900 return false;
901 return io_ != nullptr || client_->IsActive();
902 }
903
AddWorkerInspector(int thread_id,const std::string & url,Agent * agent)904 void Agent::AddWorkerInspector(int thread_id,
905 const std::string& url,
906 Agent* agent) {
907 CHECK_NOT_NULL(client_);
908 agent->parent_handle_ =
909 client_->getWorkerManager()->NewParentHandle(thread_id, url);
910 }
911
WaitForConnect()912 void Agent::WaitForConnect() {
913 CHECK_NOT_NULL(client_);
914 client_->waitForFrontend();
915 }
916
GetWorkerManager()917 std::shared_ptr<WorkerManager> Agent::GetWorkerManager() {
918 CHECK_NOT_NULL(client_);
919 return client_->getWorkerManager();
920 }
921
~SameThreadInspectorSession()922 SameThreadInspectorSession::~SameThreadInspectorSession() {
923 auto client = client_.lock();
924 if (client)
925 client->disconnectFrontend(session_id_);
926 }
927
Dispatch(const v8_inspector::StringView & message)928 void SameThreadInspectorSession::Dispatch(
929 const v8_inspector::StringView& message) {
930 auto client = client_.lock();
931 if (client)
932 client->dispatchMessageFromFrontend(session_id_, message);
933 }
934
935
936
937 } // namespace inspector
938 } // namespace node
939