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