diff --git a/src/inspector_agent.cc b/src/inspector_agent.cc index 32899600acaa4f..d1c12d6e5a99f6 100644 --- a/src/inspector_agent.cc +++ b/src/inspector_agent.cc @@ -51,7 +51,14 @@ void PrintDebuggerReadyMessage(int port) { } bool AcceptsConnection(inspector_socket_t* socket, const std::string& path) { - return 0 == path.compare(0, sizeof(DEVTOOLS_PATH) - 1, DEVTOOLS_PATH); + return StringEqualNoCaseN(path.c_str(), DEVTOOLS_PATH, + sizeof(DEVTOOLS_PATH) - 1); +} + +void Escape(std::string* string) { + for (char& c : *string) { + c = (c == '\"' || c == '\\') ? '_' : c; + } } void DisposeInspector(inspector_socket_t* socket, int status) { @@ -98,7 +105,21 @@ void SendVersionResponse(inspector_socket_t* socket) { SendHttpResponse(socket, buffer, len); } -void SendTargentsListResponse(inspector_socket_t* socket, int port) { +std::string GetProcessTitle() { + // uv_get_process_title will trim the title if it is too long. + char title[2048]; + int err = uv_get_process_title(title, sizeof(title)); + if (err == 0) { + return title; + } else { + return "Node.js"; + } +} + +void SendTargentsListResponse(inspector_socket_t* socket, + const std::string& script_name_, + const std::string& script_path_, + int port) { const char LIST_RESPONSE_TEMPLATE[] = "[ {" " \"description\": \"node.js instance\"," @@ -110,45 +131,60 @@ void SendTargentsListResponse(inspector_socket_t* socket, int port) { " \"id\": \"%d\"," " \"title\": \"%s\"," " \"type\": \"node\"," + " \"url\": \"%s\"," " \"webSocketDebuggerUrl\": \"ws://localhost:%d%s\"" "} ]"; - char buffer[sizeof(LIST_RESPONSE_TEMPLATE) + 4096]; - char title[2048]; // uv_get_process_title trims the title if too long - int err = uv_get_process_title(title, sizeof(title)); - if (err != 0) { - snprintf(title, sizeof(title), "Node.js"); - } - char* c = title; - while (*c != '\0') { - if (*c < ' ' || *c == '\"') { - *c = '_'; - } - c++; + std::string title = script_name_.empty() ? GetProcessTitle() : script_name_; + + // This attribute value is a "best effort" URL that is passed as a JSON + // string. It is not guaranteed to resolve to a valid resource. + std::string url = "file://" + script_path_; + + Escape(&title); + Escape(&url); + + const int NUMERIC_FIELDS_LENGTH = 5 * 2 + 20; // 2 x port + 1 x pid (64 bit) + + int buf_len = sizeof(LIST_RESPONSE_TEMPLATE) + sizeof(DEVTOOLS_HASH) + + sizeof(DEVTOOLS_PATH) * 2 + title.length() + + url.length() + NUMERIC_FIELDS_LENGTH; + std::string buffer(buf_len, '\0'); + + int len = snprintf(&buffer[0], buf_len, LIST_RESPONSE_TEMPLATE, + DEVTOOLS_HASH, port, DEVTOOLS_PATH, getpid(), + title.c_str(), url.c_str(), + port, DEVTOOLS_PATH); + buffer.resize(len); + ASSERT_LT(len, buf_len); // Buffer should be big enough! + SendHttpResponse(socket, buffer.data(), len); +} + +const char* match_path_segment(const char* path, const char* expected) { + size_t len = strlen(expected); + if (StringEqualNoCaseN(path, expected, len)) { + if (path[len] == '/') return path + len + 1; + if (path[len] == '\0') return path + len; } - size_t len = snprintf(buffer, sizeof(buffer), LIST_RESPONSE_TEMPLATE, - DEVTOOLS_HASH, port, DEVTOOLS_PATH, getpid(), - title, port, DEVTOOLS_PATH); - ASSERT_LT(len, sizeof(buffer)); - SendHttpResponse(socket, buffer, len); + return nullptr; } -bool RespondToGet(inspector_socket_t* socket, const std::string& path, +bool RespondToGet(inspector_socket_t* socket, const std::string& script_name_, + const std::string& script_path_, const std::string& path, int port) { - const char PATH[] = "/json"; - const char PATH_LIST[] = "/json/list"; - const char PATH_VERSION[] = "/json/version"; - const char PATH_ACTIVATE[] = "/json/activate/"; - if (0 == path.compare(0, sizeof(PATH_VERSION) - 1, PATH_VERSION)) { + const char* command = match_path_segment(path.c_str(), "/json"); + if (command == nullptr) + return false; + + if (match_path_segment(command, "list") || command[0] == '\0') { + SendTargentsListResponse(socket, script_name_, script_path_, port); + } else if (match_path_segment(command, "version")) { SendVersionResponse(socket); - } else if (0 == path.compare(0, sizeof(PATH_LIST) - 1, PATH_LIST) || - 0 == path.compare(0, sizeof(PATH) - 1, PATH)) { - SendTargentsListResponse(socket, port); - } else if (0 == path.compare(0, sizeof(PATH_ACTIVATE) - 1, PATH_ACTIVATE) && - atoi(path.substr(sizeof(PATH_ACTIVATE) - 1).c_str()) == getpid()) { + } else { + const char* pid = match_path_segment(command, "activate"); + if (pid == nullptr || atoi(pid) != getpid()) + return false; const char TARGET_ACTIVATED[] = "Target activated"; SendHttpResponse(socket, TARGET_ACTIVATED, sizeof(TARGET_ACTIVATED) - 1); - } else { - return false; } return true; } @@ -166,7 +202,7 @@ class AgentImpl { ~AgentImpl(); // Start the inspector agent thread - bool Start(v8::Platform* platform, int port, bool wait); + bool Start(v8::Platform* platform, const char* path, int port, bool wait); // Stop the inspector agent void Stop(); @@ -227,6 +263,9 @@ class AgentImpl { int frontend_session_id_; int backend_session_id_; + std::string script_name_; + std::string script_path_; + friend class ChannelImpl; friend class DispatchOnInspectorBackendTask; friend class SetConnectedTask; @@ -442,10 +481,13 @@ void InspectorWrapConsoleCall(const v8::FunctionCallbackInfo& args) { array).ToLocalChecked()); } -bool AgentImpl::Start(v8::Platform* platform, int port, bool wait) { +bool AgentImpl::Start(v8::Platform* platform, const char* path, + int port, bool wait) { auto env = parent_env_; inspector_ = new V8NodeInspector(this, env, platform); platform_ = platform; + if (path != nullptr) + script_name_ = path; InstallInspectorOnProcess(); @@ -566,7 +608,8 @@ bool AgentImpl::OnInspectorHandshakeIO(inspector_socket_t* socket, AgentImpl* agent = static_cast(socket->data); switch (state) { case kInspectorHandshakeHttpGet: - return RespondToGet(socket, path, agent->port_); + return RespondToGet(socket, agent->script_name_, agent->script_path_, path, + agent->port_); case kInspectorHandshakeUpgrading: return AcceptsConnection(socket, path); case kInspectorHandshakeUpgraded: @@ -635,6 +678,12 @@ void AgentImpl::WorkerRunIO() { err = uv_async_init(&child_loop_, &io_thread_req_, AgentImpl::WriteCbIO); CHECK_EQ(err, 0); io_thread_req_.data = this; + if (!script_name_.empty()) { + uv_fs_t req; + if (0 == uv_fs_realpath(&child_loop_, &req, script_name_.c_str(), nullptr)) + script_path_ = std::string(reinterpret_cast(req.ptr)); + uv_fs_req_cleanup(&req); + } uv_tcp_init(&child_loop_, &server); uv_ip4_addr("0.0.0.0", port_, &addr); server.data = this; @@ -752,8 +801,9 @@ Agent::~Agent() { delete impl; } -bool Agent::Start(v8::Platform* platform, int port, bool wait) { - return impl->Start(platform, port, wait); +bool Agent::Start(v8::Platform* platform, const char* path, + int port, bool wait) { + return impl->Start(platform, path, port, wait); } void Agent::Stop() { diff --git a/src/inspector_agent.h b/src/inspector_agent.h index 43433fdc6e69f0..3607cffba5d21f 100644 --- a/src/inspector_agent.h +++ b/src/inspector_agent.h @@ -29,7 +29,7 @@ class Agent { ~Agent(); // Start the inspector agent thread - bool Start(v8::Platform* platform, int port, bool wait); + bool Start(v8::Platform* platform, const char* path, int port, bool wait); // Stop the inspector agent void Stop(); diff --git a/src/node.cc b/src/node.cc index 9dc66f907f4c3f..d048bd03c8b692 100644 --- a/src/node.cc +++ b/src/node.cc @@ -207,9 +207,10 @@ static struct { platform_ = nullptr; } - bool StartInspector(Environment *env, int port, bool wait) { + bool StartInspector(Environment *env, const char* script_path, + int port, bool wait) { #if HAVE_INSPECTOR - return env->inspector_agent()->Start(platform_, port, wait); + return env->inspector_agent()->Start(platform_, script_path, port, wait); #else return true; #endif // HAVE_INSPECTOR @@ -220,7 +221,8 @@ static struct { void Initialize(int thread_pool_size) {} void PumpMessageLoop(Isolate* isolate) {} void Dispose() {} - bool StartInspector(Environment *env, int port, bool wait) { + bool StartInspector(Environment *env, const char* script_path, + int port, bool wait) { env->ThrowError("Node compiled with NODE_USE_V8_PLATFORM=0"); return false; // make compiler happy } @@ -3766,10 +3768,11 @@ static void DispatchMessagesDebugAgentCallback(Environment* env) { } -static void StartDebug(Environment* env, bool wait) { +static void StartDebug(Environment* env, const char* path, bool wait) { CHECK(!debugger_running); if (use_inspector) { - debugger_running = v8_platform.StartInspector(env, inspector_port, wait); + debugger_running = v8_platform.StartInspector(env, path, inspector_port, + wait); } else { env->debugger_agent()->set_dispatch_handler( DispatchMessagesDebugAgentCallback); @@ -3831,7 +3834,7 @@ static void DispatchDebugMessagesAsyncCallback(uv_async_t* handle) { Environment* env = Environment::GetCurrent(isolate); Context::Scope context_scope(env->context()); - StartDebug(env, false); + StartDebug(env, nullptr, false); EnableDebug(env); } @@ -4396,7 +4399,10 @@ static void StartNodeInstance(void* arg) { // Start debug agent when argv has --debug if (instance_data->use_debug_agent()) { - StartDebug(&env, debug_wait_connect); + const char* path = instance_data->argc() > 1 + ? instance_data->argv()[1] + : nullptr; + StartDebug(&env, path, debug_wait_connect); if (use_inspector && !debugger_running) { exit(12); }