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 35namespace node { 36namespace inspector { 37namespace { 38 39using node::OnFatalError; 40 41using v8::Context; 42using v8::Function; 43using v8::HandleScope; 44using v8::Isolate; 45using v8::Local; 46using v8::Message; 47using v8::Object; 48using v8::Value; 49 50using v8_inspector::StringBuffer; 51using v8_inspector::StringView; 52using v8_inspector::V8Inspector; 53using v8_inspector::V8InspectorClient; 54 55#ifdef __POSIX__ 56static uv_sem_t start_io_thread_semaphore; 57#endif // __POSIX__ 58static 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. 61static std::atomic_bool start_io_thread_async_initialized { false }; 62// Protects the Agent* stored in start_io_thread_async.data. 63static Mutex start_io_thread_async_mutex; 64 65std::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. 72void StartIoThreadAsyncCallback(uv_async_t* handle) { 73 static_cast<Agent*>(handle->data)->StartIoThread(); 74} 75 76 77#ifdef __POSIX__ 78static void StartIoThreadWakeup(int signo, siginfo_t* info, void* ucontext) { 79 uv_sem_post(&start_io_thread_semaphore); 80} 81 82inline 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 94static 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 145DWORD 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 154static 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 159static 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 203const int CONTEXT_GROUP_ID = 1; 204 205std::string GetWorkerLabel(node::Environment* env) { 206 std::ostringstream result; 207 result << "Worker[" << env->thread_id() << "]"; 208 return result.str(); 209} 210 211class ChannelImpl final : public v8_inspector::V8Inspector::Channel, 212 public protocol::FrontendChannel { 213 public: 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 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 267 void schedulePauseOnNextStatement(const std::string& reason) { 268 std::unique_ptr<StringBuffer> buffer = Utf8ToStringView(reason); 269 session_->schedulePauseOnNextStatement(buffer->string(), buffer->string()); 270 } 271 272 bool preventShutdown() { 273 return prevent_shutdown_; 274 } 275 276 bool notifyWaitingForDisconnect() { 277 retaining_context_ = runtime_agent_->notifyWaitingForDisconnect(); 278 return retaining_context_; 279 } 280 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 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 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 341class SameThreadInspectorSession : public InspectorSession { 342 public: 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 354void 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 367bool 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 379bool IsFilePath(const std::string& path) { 380 return !path.empty() && path[0] == '/'; 381} 382#endif // __POSIX__ 383 384void 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 396class NodeInspectorClient : public V8InspectorClient { 397 public: 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 414 void waitForSessionsDisconnect() { 415 waiting_for_sessions_disconnect_ = true; 416 runMessageLoop(); 417 } 418 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 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 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 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 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 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 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. 560 void AsyncTaskScheduled(const StringView& task_name, void* task, 561 bool recurring) { 562 client_->asyncTaskScheduled(task_name, task, recurring); 563 } 564 565 void AsyncTaskCanceled(void* task) { 566 client_->asyncTaskCanceled(task); 567 } 568 569 void AsyncTaskStarted(void* task) { 570 client_->asyncTaskStarted(task); 571 } 572 573 void AsyncTaskFinished(void* task) { 574 client_->asyncTaskFinished(task); 575 } 576 577 void AllAsyncTasksCanceled() { 578 client_->allAsyncTasksCanceled(); 579 } 580 581 void schedulePauseOnNextStatement(const std::string& reason) { 582 for (const auto& id_channel : channels_) { 583 id_channel.second->schedulePauseOnNextStatement(reason); 584 } 585 } 586 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 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 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 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 624 bool IsActive() { 625 return !channels_.empty(); 626 } 627 628 private: 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 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 682Agent::Agent(Environment* env) 683 : parent_env_(env), 684 debug_options_(env->options()->debug_options()), 685 host_port_(env->inspector_host_port()) {} 686 687Agent::~Agent() {} 688 689bool 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 752bool 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 774void Agent::Stop() { 775 io_.reset(); 776} 777 778std::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 794std::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 810void 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 834void Agent::ReportUncaughtException(Local<Value> error, 835 Local<Message> message) { 836 if (!IsListening()) 837 return; 838 client_->ReportUncaughtException(error, message); 839 WaitForDisconnect(); 840} 841 842void Agent::PauseOnNextJavascriptStatement(const std::string& reason) { 843 client_->schedulePauseOnNextStatement(reason); 844} 845 846void 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 862void 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 875void 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 888void 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 907void Agent::AsyncTaskScheduled(const StringView& task_name, void* task, 908 bool recurring) { 909 client_->AsyncTaskScheduled(task_name, task, recurring); 910} 911 912void Agent::AsyncTaskCanceled(void* task) { 913 client_->AsyncTaskCanceled(task); 914} 915 916void Agent::AsyncTaskStarted(void* task) { 917 client_->AsyncTaskStarted(task); 918} 919 920void Agent::AsyncTaskFinished(void* task) { 921 client_->AsyncTaskFinished(task); 922} 923 924void Agent::AllAsyncTasksCanceled() { 925 client_->AllAsyncTasksCanceled(); 926} 927 928void 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 945void 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 951bool Agent::IsActive() { 952 if (client_ == nullptr) 953 return false; 954 return io_ != nullptr || client_->IsActive(); 955} 956 957void Agent::SetParentHandle( 958 std::unique_ptr<ParentInspectorHandle> parent_handle) { 959 parent_handle_ = std::move(parent_handle); 960} 961 962std::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 977void 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 987std::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 997std::string Agent::GetWsUrl() const { 998 if (io_ == nullptr) 999 return ""; 1000 return io_->GetWsUrl(); 1001} 1002 1003SameThreadInspectorSession::~SameThreadInspectorSession() { 1004 auto client = client_.lock(); 1005 if (client) 1006 client->disconnectFrontend(session_id_); 1007} 1008 1009void 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