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.h"
16 #include "node_url.h"
17 #include "util-inl.h"
18 #include "v8-inspector.h"
19 #include "v8-platform.h"
20
21 #include "libplatform/libplatform.h"
22
23 #ifdef __POSIX__
24 #include <pthread.h>
25 #include <climits> // PTHREAD_STACK_MIN
26 #endif // __POSIX__
27
28 #include <algorithm>
29 #include <cstring>
30 #include <sstream>
31 #include <unordered_map>
32 #include <vector>
33
34 namespace node {
35 namespace inspector {
36 namespace {
37
38 using node::FatalError;
39
40 using v8::Context;
41 using v8::Function;
42 using v8::Global;
43 using v8::HandleScope;
44 using v8::Isolate;
45 using v8::Local;
46 using v8::Message;
47 using v8::Object;
48 using v8::Task;
49 using v8::Value;
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)76 static void StartIoThreadWakeup(int signo) {
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 InspectorTimer {
330 public:
InspectorTimer(Environment * env,double interval_s,V8InspectorClient::TimerCallback callback,void * data)331 InspectorTimer(Environment* env,
332 double interval_s,
333 V8InspectorClient::TimerCallback callback,
334 void* data) : env_(env),
335 callback_(callback),
336 data_(data) {
337 uv_timer_init(env->event_loop(), &timer_);
338 int64_t interval_ms = 1000 * interval_s;
339 uv_timer_start(&timer_, OnTimer, interval_ms, interval_ms);
340 timer_.data = this;
341 }
342
343 InspectorTimer(const InspectorTimer&) = delete;
344
Stop()345 void Stop() {
346 if (timer_.data == nullptr) return;
347
348 timer_.data = nullptr;
349 uv_timer_stop(&timer_);
350 env_->CloseHandle(reinterpret_cast<uv_handle_t*>(&timer_), TimerClosedCb);
351 }
352
env() const353 inline Environment* env() const { return env_; }
354
355 private:
OnTimer(uv_timer_t * uvtimer)356 static void OnTimer(uv_timer_t* uvtimer) {
357 InspectorTimer* timer = node::ContainerOf(&InspectorTimer::timer_, uvtimer);
358 timer->callback_(timer->data_);
359 }
360
TimerClosedCb(uv_handle_t * uvtimer)361 static void TimerClosedCb(uv_handle_t* uvtimer) {
362 std::unique_ptr<InspectorTimer> timer(
363 node::ContainerOf(&InspectorTimer::timer_,
364 reinterpret_cast<uv_timer_t*>(uvtimer)));
365 // Unique_ptr goes out of scope here and pointer is deleted.
366 }
367
368 ~InspectorTimer() = default;
369
370 Environment* env_;
371 uv_timer_t timer_;
372 V8InspectorClient::TimerCallback callback_;
373 void* data_;
374
375 friend std::unique_ptr<InspectorTimer>::deleter_type;
376 };
377
378 class InspectorTimerHandle {
379 public:
InspectorTimerHandle(Environment * env,double interval_s,V8InspectorClient::TimerCallback callback,void * data)380 InspectorTimerHandle(Environment* env, double interval_s,
381 V8InspectorClient::TimerCallback callback, void* data) {
382 timer_ = new InspectorTimer(env, interval_s, callback, data);
383
384 env->AddCleanupHook(CleanupHook, this);
385 }
386
387 InspectorTimerHandle(const InspectorTimerHandle&) = delete;
388
~InspectorTimerHandle()389 ~InspectorTimerHandle() {
390 Stop();
391 }
392
393 private:
Stop()394 void Stop() {
395 if (timer_ != nullptr) {
396 timer_->env()->RemoveCleanupHook(CleanupHook, this);
397 timer_->Stop();
398 }
399 timer_ = nullptr;
400 }
401
CleanupHook(void * data)402 static void CleanupHook(void* data) {
403 static_cast<InspectorTimerHandle*>(data)->Stop();
404 }
405
406 InspectorTimer* timer_;
407 };
408
409 class SameThreadInspectorSession : public InspectorSession {
410 public:
SameThreadInspectorSession(int session_id,std::shared_ptr<NodeInspectorClient> client)411 SameThreadInspectorSession(
412 int session_id, std::shared_ptr<NodeInspectorClient> client)
413 : session_id_(session_id), client_(client) {}
414 ~SameThreadInspectorSession() override;
415 void Dispatch(const v8_inspector::StringView& message) override;
416
417 private:
418 int session_id_;
419 std::weak_ptr<NodeInspectorClient> client_;
420 };
421
NotifyClusterWorkersDebugEnabled(Environment * env)422 void NotifyClusterWorkersDebugEnabled(Environment* env) {
423 Isolate* isolate = env->isolate();
424 HandleScope handle_scope(isolate);
425 Local<Context> context = env->context();
426
427 // Send message to enable debug in cluster workers
428 Local<Object> message = Object::New(isolate);
429 message->Set(context, FIXED_ONE_BYTE_STRING(isolate, "cmd"),
430 FIXED_ONE_BYTE_STRING(isolate, "NODE_DEBUG_ENABLED")).Check();
431 ProcessEmit(env, "internalMessage", message);
432 }
433
434 #ifdef _WIN32
IsFilePath(const std::string & path)435 bool IsFilePath(const std::string& path) {
436 // '\\'
437 if (path.length() > 2 && path[0] == '\\' && path[1] == '\\')
438 return true;
439 // '[A-Z]:[/\\]'
440 if (path.length() < 3)
441 return false;
442 if ((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z'))
443 return path[1] == ':' && (path[2] == '/' || path[2] == '\\');
444 return false;
445 }
446 #else
IsFilePath(const std::string & path)447 bool IsFilePath(const std::string& path) {
448 return !path.empty() && path[0] == '/';
449 }
450 #endif // __POSIX__
451
452 } // namespace
453
454 class NodeInspectorClient : public V8InspectorClient {
455 public:
NodeInspectorClient(node::Environment * env,bool is_main)456 explicit NodeInspectorClient(node::Environment* env, bool is_main)
457 : env_(env), is_main_(is_main) {
458 client_ = V8Inspector::create(env->isolate(), this);
459 // TODO(bnoordhuis) Make name configurable from src/node.cc.
460 std::string name =
461 is_main_ ? GetHumanReadableProcessName() : GetWorkerLabel(env);
462 ContextInfo info(name);
463 info.is_default = true;
464 contextCreated(env->context(), info);
465 }
466
runMessageLoopOnPause(int context_group_id)467 void runMessageLoopOnPause(int context_group_id) override {
468 waiting_for_resume_ = true;
469 runMessageLoop();
470 }
471
waitForSessionsDisconnect()472 void waitForSessionsDisconnect() {
473 waiting_for_sessions_disconnect_ = true;
474 runMessageLoop();
475 }
476
waitForFrontend()477 void waitForFrontend() {
478 waiting_for_frontend_ = true;
479 runMessageLoop();
480 }
481
maxAsyncCallStackDepthChanged(int depth)482 void maxAsyncCallStackDepthChanged(int depth) override {
483 if (waiting_for_sessions_disconnect_) {
484 // V8 isolate is mostly done and is only letting Inspector protocol
485 // clients gather data.
486 return;
487 }
488 if (auto agent = env_->inspector_agent()) {
489 if (depth == 0) {
490 agent->DisableAsyncHook();
491 } else {
492 agent->EnableAsyncHook();
493 }
494 }
495 }
496
contextCreated(Local<Context> context,const ContextInfo & info)497 void contextCreated(Local<Context> context, const ContextInfo& info) {
498 auto name_buffer = Utf8ToStringView(info.name);
499 auto origin_buffer = Utf8ToStringView(info.origin);
500 std::unique_ptr<StringBuffer> aux_data_buffer;
501
502 v8_inspector::V8ContextInfo v8info(
503 context, CONTEXT_GROUP_ID, name_buffer->string());
504 v8info.origin = origin_buffer->string();
505
506 if (info.is_default) {
507 aux_data_buffer = Utf8ToStringView("{\"isDefault\":true}");
508 } else {
509 aux_data_buffer = Utf8ToStringView("{\"isDefault\":false}");
510 }
511 v8info.auxData = aux_data_buffer->string();
512
513 client_->contextCreated(v8info);
514 }
515
contextDestroyed(Local<Context> context)516 void contextDestroyed(Local<Context> context) {
517 client_->contextDestroyed(context);
518 }
519
quitMessageLoopOnPause()520 void quitMessageLoopOnPause() override {
521 waiting_for_resume_ = false;
522 }
523
runIfWaitingForDebugger(int context_group_id)524 void runIfWaitingForDebugger(int context_group_id) override {
525 waiting_for_frontend_ = false;
526 }
527
connectFrontend(std::unique_ptr<InspectorSessionDelegate> delegate,bool prevent_shutdown)528 int connectFrontend(std::unique_ptr<InspectorSessionDelegate> delegate,
529 bool prevent_shutdown) {
530 int session_id = next_session_id_++;
531 channels_[session_id] = std::make_unique<ChannelImpl>(env_,
532 client_,
533 getWorkerManager(),
534 std::move(delegate),
535 getThreadHandle(),
536 prevent_shutdown);
537 return session_id;
538 }
539
disconnectFrontend(int session_id)540 void disconnectFrontend(int session_id) {
541 auto it = channels_.find(session_id);
542 if (it == channels_.end())
543 return;
544 bool retaining_context = it->second->retainingContext();
545 channels_.erase(it);
546 if (retaining_context) {
547 for (const auto& id_channel : channels_) {
548 if (id_channel.second->retainingContext())
549 return;
550 }
551 contextDestroyed(env_->context());
552 }
553 if (waiting_for_sessions_disconnect_ && !is_main_)
554 waiting_for_sessions_disconnect_ = false;
555 }
556
dispatchMessageFromFrontend(int session_id,const StringView & message)557 void dispatchMessageFromFrontend(int session_id, const StringView& message) {
558 channels_[session_id]->dispatchProtocolMessage(message);
559 }
560
ensureDefaultContextInGroup(int contextGroupId)561 Local<Context> ensureDefaultContextInGroup(int contextGroupId) override {
562 return env_->context();
563 }
564
installAdditionalCommandLineAPI(Local<Context> context,Local<Object> target)565 void installAdditionalCommandLineAPI(Local<Context> context,
566 Local<Object> target) override {
567 Local<Function> installer = env_->inspector_console_extension_installer();
568 if (!installer.IsEmpty()) {
569 Local<Value> argv[] = {target};
570 // If there is an exception, proceed in JS land
571 USE(installer->Call(context, target, arraysize(argv), argv));
572 }
573 }
574
ReportUncaughtException(Local<Value> error,Local<Message> message)575 void ReportUncaughtException(Local<Value> error, Local<Message> message) {
576 Isolate* isolate = env_->isolate();
577 Local<Context> context = env_->context();
578
579 int script_id = message->GetScriptOrigin().ScriptID()->Value();
580
581 Local<v8::StackTrace> stack_trace = message->GetStackTrace();
582
583 if (!stack_trace.IsEmpty() && stack_trace->GetFrameCount() > 0 &&
584 script_id == stack_trace->GetFrame(isolate, 0)->GetScriptId()) {
585 script_id = 0;
586 }
587
588 const uint8_t DETAILS[] = "Uncaught";
589
590 client_->exceptionThrown(
591 context,
592 StringView(DETAILS, sizeof(DETAILS) - 1),
593 error,
594 ToProtocolString(isolate, message->Get())->string(),
595 ToProtocolString(isolate, message->GetScriptResourceName())->string(),
596 message->GetLineNumber(context).FromMaybe(0),
597 message->GetStartColumn(context).FromMaybe(0),
598 client_->createStackTrace(stack_trace),
599 script_id);
600 }
601
startRepeatingTimer(double interval_s,TimerCallback callback,void * data)602 void startRepeatingTimer(double interval_s,
603 TimerCallback callback,
604 void* data) override {
605 timers_.emplace(std::piecewise_construct, std::make_tuple(data),
606 std::make_tuple(env_, interval_s, callback,
607 data));
608 }
609
cancelTimer(void * data)610 void cancelTimer(void* data) override {
611 timers_.erase(data);
612 }
613
614 // Async stack traces instrumentation.
AsyncTaskScheduled(const StringView & task_name,void * task,bool recurring)615 void AsyncTaskScheduled(const StringView& task_name, void* task,
616 bool recurring) {
617 client_->asyncTaskScheduled(task_name, task, recurring);
618 }
619
AsyncTaskCanceled(void * task)620 void AsyncTaskCanceled(void* task) {
621 client_->asyncTaskCanceled(task);
622 }
623
AsyncTaskStarted(void * task)624 void AsyncTaskStarted(void* task) {
625 client_->asyncTaskStarted(task);
626 }
627
AsyncTaskFinished(void * task)628 void AsyncTaskFinished(void* task) {
629 client_->asyncTaskFinished(task);
630 }
631
AllAsyncTasksCanceled()632 void AllAsyncTasksCanceled() {
633 client_->allAsyncTasksCanceled();
634 }
635
schedulePauseOnNextStatement(const std::string & reason)636 void schedulePauseOnNextStatement(const std::string& reason) {
637 for (const auto& id_channel : channels_) {
638 id_channel.second->schedulePauseOnNextStatement(reason);
639 }
640 }
641
hasConnectedSessions()642 bool hasConnectedSessions() {
643 for (const auto& id_channel : channels_) {
644 // Other sessions are "invisible" more most purposes
645 if (id_channel.second->preventShutdown())
646 return true;
647 }
648 return false;
649 }
650
notifyWaitingForDisconnect()651 bool notifyWaitingForDisconnect() {
652 bool retaining_context = false;
653 for (const auto& id_channel : channels_) {
654 if (id_channel.second->notifyWaitingForDisconnect())
655 retaining_context = true;
656 }
657 return retaining_context;
658 }
659
getThreadHandle()660 std::shared_ptr<MainThreadHandle> getThreadHandle() {
661 if (!interface_) {
662 interface_ = std::make_shared<MainThreadInterface>(
663 env_->inspector_agent());
664 }
665 return interface_->GetHandle();
666 }
667
getWorkerManager()668 std::shared_ptr<WorkerManager> getWorkerManager() {
669 if (!is_main_) {
670 return nullptr;
671 }
672 if (worker_manager_ == nullptr) {
673 worker_manager_ =
674 std::make_shared<WorkerManager>(getThreadHandle());
675 }
676 return worker_manager_;
677 }
678
IsActive()679 bool IsActive() {
680 return !channels_.empty();
681 }
682
683 private:
shouldRunMessageLoop()684 bool shouldRunMessageLoop() {
685 if (waiting_for_frontend_)
686 return true;
687 if (waiting_for_sessions_disconnect_ || waiting_for_resume_) {
688 return hasConnectedSessions();
689 }
690 return false;
691 }
692
runMessageLoop()693 void runMessageLoop() {
694 if (running_nested_loop_)
695 return;
696
697 running_nested_loop_ = true;
698
699 while (shouldRunMessageLoop()) {
700 if (interface_) interface_->WaitForFrontendEvent();
701 env_->RunAndClearInterrupts();
702 }
703 running_nested_loop_ = false;
704 }
705
currentTimeMS()706 double currentTimeMS() override {
707 return env_->isolate_data()->platform()->CurrentClockTimeMillis();
708 }
709
resourceNameToUrl(const StringView & resource_name_view)710 std::unique_ptr<StringBuffer> resourceNameToUrl(
711 const StringView& resource_name_view) override {
712 std::string resource_name =
713 protocol::StringUtil::StringViewToUtf8(resource_name_view);
714 if (!IsFilePath(resource_name))
715 return nullptr;
716 node::url::URL url = node::url::URL::FromFilePath(resource_name);
717 // TODO(ak239spb): replace this code with url.href().
718 // Refs: https://github.com/nodejs/node/issues/22610
719 return Utf8ToStringView(url.protocol() + "//" + url.path());
720 }
721
722 node::Environment* env_;
723 bool is_main_;
724 bool running_nested_loop_ = false;
725 std::unique_ptr<V8Inspector> client_;
726 // Note: ~ChannelImpl may access timers_ so timers_ has to come first.
727 std::unordered_map<void*, InspectorTimerHandle> timers_;
728 std::unordered_map<int, std::unique_ptr<ChannelImpl>> channels_;
729 int next_session_id_ = 1;
730 bool waiting_for_resume_ = false;
731 bool waiting_for_frontend_ = false;
732 bool waiting_for_sessions_disconnect_ = false;
733 // Allows accessing Inspector from non-main threads
734 std::shared_ptr<MainThreadInterface> interface_;
735 std::shared_ptr<WorkerManager> worker_manager_;
736 };
737
Agent(Environment * env)738 Agent::Agent(Environment* env)
739 : parent_env_(env),
740 debug_options_(env->options()->debug_options()),
741 host_port_(env->inspector_host_port()) {}
742
~Agent()743 Agent::~Agent() {}
744
Start(const std::string & path,const DebugOptions & options,std::shared_ptr<ExclusiveAccess<HostPort>> host_port,bool is_main)745 bool Agent::Start(const std::string& path,
746 const DebugOptions& options,
747 std::shared_ptr<ExclusiveAccess<HostPort>> host_port,
748 bool is_main) {
749 path_ = path;
750 debug_options_ = options;
751 CHECK_NOT_NULL(host_port);
752 host_port_ = host_port;
753
754 client_ = std::make_shared<NodeInspectorClient>(parent_env_, is_main);
755 if (parent_env_->owns_inspector()) {
756 Mutex::ScopedLock lock(start_io_thread_async_mutex);
757 CHECK_EQ(start_io_thread_async_initialized.exchange(true), false);
758 CHECK_EQ(0, uv_async_init(parent_env_->event_loop(),
759 &start_io_thread_async,
760 StartIoThreadAsyncCallback));
761 uv_unref(reinterpret_cast<uv_handle_t*>(&start_io_thread_async));
762 start_io_thread_async.data = this;
763 // Ignore failure, SIGUSR1 won't work, but that should not block node start.
764 StartDebugSignalHandler();
765
766 parent_env_->AddCleanupHook([](void* data) {
767 Environment* env = static_cast<Environment*>(data);
768
769 {
770 Mutex::ScopedLock lock(start_io_thread_async_mutex);
771 start_io_thread_async.data = nullptr;
772 }
773
774 // This is global, will never get freed
775 env->CloseHandle(&start_io_thread_async, [](uv_async_t*) {
776 CHECK(start_io_thread_async_initialized.exchange(false));
777 });
778 }, parent_env_);
779 }
780
781 AtExit(parent_env_, [](void* env) {
782 Agent* agent = static_cast<Environment*>(env)->inspector_agent();
783 if (agent->IsActive()) {
784 agent->WaitForDisconnect();
785 }
786 }, parent_env_);
787
788 bool wait_for_connect = options.wait_for_connect();
789 if (parent_handle_) {
790 wait_for_connect = parent_handle_->WaitForConnect();
791 parent_handle_->WorkerStarted(client_->getThreadHandle(), wait_for_connect);
792 } else if (!options.inspector_enabled || !StartIoThread()) {
793 return false;
794 }
795
796 // Patch the debug options to implement waitForDebuggerOnStart for
797 // the NodeWorker.enable method.
798 if (wait_for_connect) {
799 CHECK(!parent_env_->has_serialized_options());
800 debug_options_.EnableBreakFirstLine();
801 parent_env_->options()->get_debug_options()->EnableBreakFirstLine();
802 client_->waitForFrontend();
803 }
804 return true;
805 }
806
StartIoThread()807 bool Agent::StartIoThread() {
808 if (io_ != nullptr)
809 return true;
810
811 CHECK_NOT_NULL(client_);
812
813 io_ = InspectorIo::Start(client_->getThreadHandle(),
814 path_,
815 host_port_,
816 debug_options_.inspect_publish_uid);
817 if (io_ == nullptr) {
818 return false;
819 }
820 NotifyClusterWorkersDebugEnabled(parent_env_);
821 return true;
822 }
823
Stop()824 void Agent::Stop() {
825 io_.reset();
826 }
827
Connect(std::unique_ptr<InspectorSessionDelegate> delegate,bool prevent_shutdown)828 std::unique_ptr<InspectorSession> Agent::Connect(
829 std::unique_ptr<InspectorSessionDelegate> delegate,
830 bool prevent_shutdown) {
831 CHECK_NOT_NULL(client_);
832 int session_id = client_->connectFrontend(std::move(delegate),
833 prevent_shutdown);
834 return std::unique_ptr<InspectorSession>(
835 new SameThreadInspectorSession(session_id, client_));
836 }
837
ConnectToMainThread(std::unique_ptr<InspectorSessionDelegate> delegate,bool prevent_shutdown)838 std::unique_ptr<InspectorSession> Agent::ConnectToMainThread(
839 std::unique_ptr<InspectorSessionDelegate> delegate,
840 bool prevent_shutdown) {
841 CHECK_NOT_NULL(parent_handle_);
842 CHECK_NOT_NULL(client_);
843 auto thread_safe_delegate =
844 client_->getThreadHandle()->MakeDelegateThreadSafe(std::move(delegate));
845 return parent_handle_->Connect(std::move(thread_safe_delegate),
846 prevent_shutdown);
847 }
848
WaitForDisconnect()849 void Agent::WaitForDisconnect() {
850 CHECK_NOT_NULL(client_);
851 bool is_worker = parent_handle_ != nullptr;
852 parent_handle_.reset();
853 if (client_->hasConnectedSessions() && !is_worker) {
854 fprintf(stderr, "Waiting for the debugger to disconnect...\n");
855 fflush(stderr);
856 }
857 if (!client_->notifyWaitingForDisconnect()) {
858 client_->contextDestroyed(parent_env_->context());
859 } else if (is_worker) {
860 client_->waitForSessionsDisconnect();
861 }
862 if (io_ != nullptr) {
863 io_->StopAcceptingNewConnections();
864 client_->waitForSessionsDisconnect();
865 }
866 }
867
ReportUncaughtException(Local<Value> error,Local<Message> message)868 void Agent::ReportUncaughtException(Local<Value> error,
869 Local<Message> message) {
870 if (!IsListening())
871 return;
872 client_->ReportUncaughtException(error, message);
873 WaitForDisconnect();
874 }
875
PauseOnNextJavascriptStatement(const std::string & reason)876 void Agent::PauseOnNextJavascriptStatement(const std::string& reason) {
877 client_->schedulePauseOnNextStatement(reason);
878 }
879
RegisterAsyncHook(Isolate * isolate,Local<Function> enable_function,Local<Function> disable_function)880 void Agent::RegisterAsyncHook(Isolate* isolate,
881 Local<Function> enable_function,
882 Local<Function> disable_function) {
883 enable_async_hook_function_.Reset(isolate, enable_function);
884 disable_async_hook_function_.Reset(isolate, disable_function);
885 if (pending_enable_async_hook_) {
886 CHECK(!pending_disable_async_hook_);
887 pending_enable_async_hook_ = false;
888 EnableAsyncHook();
889 } else if (pending_disable_async_hook_) {
890 CHECK(!pending_enable_async_hook_);
891 pending_disable_async_hook_ = false;
892 DisableAsyncHook();
893 }
894 }
895
EnableAsyncHook()896 void Agent::EnableAsyncHook() {
897 if (!enable_async_hook_function_.IsEmpty()) {
898 ToggleAsyncHook(parent_env_->isolate(), enable_async_hook_function_);
899 } else if (pending_disable_async_hook_) {
900 CHECK(!pending_enable_async_hook_);
901 pending_disable_async_hook_ = false;
902 } else {
903 pending_enable_async_hook_ = true;
904 }
905 }
906
DisableAsyncHook()907 void Agent::DisableAsyncHook() {
908 if (!disable_async_hook_function_.IsEmpty()) {
909 ToggleAsyncHook(parent_env_->isolate(), disable_async_hook_function_);
910 } else if (pending_enable_async_hook_) {
911 CHECK(!pending_disable_async_hook_);
912 pending_enable_async_hook_ = false;
913 } else {
914 pending_disable_async_hook_ = true;
915 }
916 }
917
ToggleAsyncHook(Isolate * isolate,const Global<Function> & fn)918 void Agent::ToggleAsyncHook(Isolate* isolate,
919 const Global<Function>& fn) {
920 // Guard against running this during cleanup -- no async events will be
921 // emitted anyway at that point anymore, and calling into JS is not possible.
922 // This should probably not be something we're attempting in the first place,
923 // Refs: https://github.com/nodejs/node/pull/34362#discussion_r456006039
924 if (!parent_env_->can_call_into_js()) return;
925 CHECK(parent_env_->has_run_bootstrapping_code());
926 HandleScope handle_scope(isolate);
927 CHECK(!fn.IsEmpty());
928 auto context = parent_env_->context();
929 v8::TryCatch try_catch(isolate);
930 USE(fn.Get(isolate)->Call(context, Undefined(isolate), 0, nullptr));
931 if (try_catch.HasCaught() && !try_catch.HasTerminated()) {
932 PrintCaughtException(isolate, context, try_catch);
933 FatalError("\nnode::inspector::Agent::ToggleAsyncHook",
934 "Cannot toggle Inspector's AsyncHook, please report this.");
935 }
936 }
937
AsyncTaskScheduled(const StringView & task_name,void * task,bool recurring)938 void Agent::AsyncTaskScheduled(const StringView& task_name, void* task,
939 bool recurring) {
940 client_->AsyncTaskScheduled(task_name, task, recurring);
941 }
942
AsyncTaskCanceled(void * task)943 void Agent::AsyncTaskCanceled(void* task) {
944 client_->AsyncTaskCanceled(task);
945 }
946
AsyncTaskStarted(void * task)947 void Agent::AsyncTaskStarted(void* task) {
948 client_->AsyncTaskStarted(task);
949 }
950
AsyncTaskFinished(void * task)951 void Agent::AsyncTaskFinished(void* task) {
952 client_->AsyncTaskFinished(task);
953 }
954
AllAsyncTasksCanceled()955 void Agent::AllAsyncTasksCanceled() {
956 client_->AllAsyncTasksCanceled();
957 }
958
RequestIoThreadStart()959 void Agent::RequestIoThreadStart() {
960 // We need to attempt to interrupt V8 flow (in case Node is running
961 // continuous JS code) and to wake up libuv thread (in case Node is waiting
962 // for IO events)
963 CHECK(start_io_thread_async_initialized);
964 uv_async_send(&start_io_thread_async);
965 parent_env_->RequestInterrupt([this](Environment*) {
966 StartIoThread();
967 });
968
969 CHECK(start_io_thread_async_initialized);
970 uv_async_send(&start_io_thread_async);
971 }
972
ContextCreated(Local<Context> context,const ContextInfo & info)973 void Agent::ContextCreated(Local<Context> context, const ContextInfo& info) {
974 if (client_ == nullptr) // This happens for a main context
975 return;
976 client_->contextCreated(context, info);
977 }
978
WillWaitForConnect()979 bool Agent::WillWaitForConnect() {
980 if (debug_options_.wait_for_connect()) return true;
981 if (parent_handle_)
982 return parent_handle_->WaitForConnect();
983 return false;
984 }
985
IsActive()986 bool Agent::IsActive() {
987 if (client_ == nullptr)
988 return false;
989 return io_ != nullptr || client_->IsActive();
990 }
991
SetParentHandle(std::unique_ptr<ParentInspectorHandle> parent_handle)992 void Agent::SetParentHandle(
993 std::unique_ptr<ParentInspectorHandle> parent_handle) {
994 parent_handle_ = std::move(parent_handle);
995 }
996
GetParentHandle(int thread_id,const std::string & url)997 std::unique_ptr<ParentInspectorHandle> Agent::GetParentHandle(
998 int thread_id, const std::string& url) {
999 if (!parent_handle_) {
1000 return client_->getWorkerManager()->NewParentHandle(thread_id, url);
1001 } else {
1002 return parent_handle_->NewParentInspectorHandle(thread_id, url);
1003 }
1004 }
1005
WaitForConnect()1006 void Agent::WaitForConnect() {
1007 CHECK_NOT_NULL(client_);
1008 client_->waitForFrontend();
1009 }
1010
GetWorkerManager()1011 std::shared_ptr<WorkerManager> Agent::GetWorkerManager() {
1012 CHECK_NOT_NULL(client_);
1013 return client_->getWorkerManager();
1014 }
1015
GetWsUrl() const1016 std::string Agent::GetWsUrl() const {
1017 if (io_ == nullptr)
1018 return "";
1019 return io_->GetWsUrl();
1020 }
1021
~SameThreadInspectorSession()1022 SameThreadInspectorSession::~SameThreadInspectorSession() {
1023 auto client = client_.lock();
1024 if (client)
1025 client->disconnectFrontend(session_id_);
1026 }
1027
Dispatch(const v8_inspector::StringView & message)1028 void SameThreadInspectorSession::Dispatch(
1029 const v8_inspector::StringView& message) {
1030 auto client = client_.lock();
1031 if (client)
1032 client->dispatchMessageFromFrontend(session_id_, message);
1033 }
1034
1035 } // namespace inspector
1036 } // namespace node
1037