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