mirror of https://github.com/lukechilds/node.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
445 lines
14 KiB
445 lines
14 KiB
#include "inspector_io.h"
|
|
|
|
#include "inspector_socket_server.h"
|
|
#include "env.h"
|
|
#include "env-inl.h"
|
|
#include "node.h"
|
|
#include "node_crypto.h"
|
|
#include "node_mutex.h"
|
|
#include "v8-inspector.h"
|
|
#include "util.h"
|
|
#include "zlib.h"
|
|
|
|
#include <sstream>
|
|
#include <unicode/unistr.h>
|
|
|
|
#include <string.h>
|
|
#include <vector>
|
|
|
|
|
|
namespace node {
|
|
namespace inspector {
|
|
namespace {
|
|
using v8_inspector::StringBuffer;
|
|
using v8_inspector::StringView;
|
|
|
|
template<typename Transport>
|
|
using TransportAndIo = std::pair<Transport*, InspectorIo*>;
|
|
|
|
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";
|
|
}
|
|
}
|
|
|
|
// UUID RFC: https://www.ietf.org/rfc/rfc4122.txt
|
|
// Used ver 4 - with numbers
|
|
std::string GenerateID() {
|
|
uint16_t buffer[8];
|
|
CHECK(crypto::EntropySource(reinterpret_cast<unsigned char*>(buffer),
|
|
sizeof(buffer)));
|
|
|
|
char uuid[256];
|
|
snprintf(uuid, sizeof(uuid), "%04x%04x-%04x-%04x-%04x-%04x%04x%04x",
|
|
buffer[0], // time_low
|
|
buffer[1], // time_mid
|
|
buffer[2], // time_low
|
|
(buffer[3] & 0x0fff) | 0x4000, // time_hi_and_version
|
|
(buffer[4] & 0x3fff) | 0x8000, // clk_seq_hi clk_seq_low
|
|
buffer[5], // node
|
|
buffer[6],
|
|
buffer[7]);
|
|
return uuid;
|
|
}
|
|
|
|
std::string StringViewToUtf8(const StringView& view) {
|
|
if (view.is8Bit()) {
|
|
return std::string(reinterpret_cast<const char*>(view.characters8()),
|
|
view.length());
|
|
}
|
|
const uint16_t* source = view.characters16();
|
|
const UChar* unicodeSource = reinterpret_cast<const UChar*>(source);
|
|
static_assert(sizeof(*source) == sizeof(*unicodeSource),
|
|
"sizeof(*source) == sizeof(*unicodeSource)");
|
|
|
|
size_t result_length = view.length() * sizeof(*source);
|
|
std::string result(result_length, '\0');
|
|
UnicodeString utf16(unicodeSource, view.length());
|
|
// ICU components for std::string compatibility are not enabled in build...
|
|
bool done = false;
|
|
while (!done) {
|
|
CheckedArrayByteSink sink(&result[0], result_length);
|
|
utf16.toUTF8(sink);
|
|
result_length = sink.NumberOfBytesAppended();
|
|
result.resize(result_length);
|
|
done = !sink.Overflowed();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void HandleSyncCloseCb(uv_handle_t* handle) {
|
|
*static_cast<bool*>(handle->data) = true;
|
|
}
|
|
|
|
int CloseAsyncAndLoop(uv_async_t* async) {
|
|
bool is_closed = false;
|
|
async->data = &is_closed;
|
|
uv_close(reinterpret_cast<uv_handle_t*>(async), HandleSyncCloseCb);
|
|
while (!is_closed)
|
|
uv_run(async->loop, UV_RUN_ONCE);
|
|
async->data = nullptr;
|
|
return uv_loop_close(async->loop);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
std::unique_ptr<StringBuffer> Utf8ToStringView(const std::string& message) {
|
|
UnicodeString utf16 =
|
|
UnicodeString::fromUTF8(StringPiece(message.data(), message.length()));
|
|
StringView view(reinterpret_cast<const uint16_t*>(utf16.getBuffer()),
|
|
utf16.length());
|
|
return StringBuffer::create(view);
|
|
}
|
|
|
|
|
|
class IoSessionDelegate : public InspectorSessionDelegate {
|
|
public:
|
|
explicit IoSessionDelegate(InspectorIo* io) : io_(io) { }
|
|
bool WaitForFrontendMessage() override;
|
|
void OnMessage(const v8_inspector::StringView& message) override;
|
|
private:
|
|
InspectorIo* io_;
|
|
};
|
|
|
|
class InspectorIoDelegate: public node::inspector::SocketServerDelegate {
|
|
public:
|
|
InspectorIoDelegate(InspectorIo* io, const std::string& script_path,
|
|
const std::string& script_name, bool wait);
|
|
bool StartSession(int session_id, const std::string& target_id) override;
|
|
void MessageReceived(int session_id, const std::string& message) override;
|
|
void EndSession(int session_id) override;
|
|
std::vector<std::string> GetTargetIds() override;
|
|
std::string GetTargetTitle(const std::string& id) override;
|
|
std::string GetTargetUrl(const std::string& id) override;
|
|
bool IsConnected() { return connected_; }
|
|
private:
|
|
InspectorIo* io_;
|
|
bool connected_;
|
|
int session_id_;
|
|
const std::string script_name_;
|
|
const std::string script_path_;
|
|
const std::string target_id_;
|
|
bool waiting_;
|
|
};
|
|
|
|
void InterruptCallback(v8::Isolate*, void* io) {
|
|
static_cast<InspectorIo*>(io)->DispatchMessages();
|
|
}
|
|
|
|
class DispatchOnInspectorBackendTask : public v8::Task {
|
|
public:
|
|
explicit DispatchOnInspectorBackendTask(InspectorIo* io) : io_(io) {}
|
|
|
|
void Run() override {
|
|
io_->DispatchMessages();
|
|
}
|
|
|
|
private:
|
|
InspectorIo* io_;
|
|
};
|
|
|
|
InspectorIo::InspectorIo(Environment* env, v8::Platform* platform,
|
|
const std::string& path, const DebugOptions& options)
|
|
: options_(options), thread_(), delegate_(nullptr),
|
|
shutting_down_(false), state_(State::kNew),
|
|
parent_env_(env), io_thread_req_(),
|
|
platform_(platform), dispatching_messages_(false),
|
|
session_id_(0), script_name_(path) {
|
|
CHECK_EQ(0, uv_async_init(env->event_loop(), &main_thread_req_,
|
|
InspectorIo::MainThreadAsyncCb));
|
|
uv_unref(reinterpret_cast<uv_handle_t*>(&main_thread_req_));
|
|
CHECK_EQ(0, uv_sem_init(&start_sem_, 0));
|
|
}
|
|
|
|
bool InspectorIo::Start() {
|
|
CHECK_EQ(uv_thread_create(&thread_, InspectorIo::ThreadCbIO, this), 0);
|
|
uv_sem_wait(&start_sem_);
|
|
|
|
if (state_ == State::kError) {
|
|
Stop();
|
|
return false;
|
|
}
|
|
state_ = State::kAccepting;
|
|
if (options_.wait_for_connect()) {
|
|
DispatchMessages();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void InspectorIo::Stop() {
|
|
int err = uv_thread_join(&thread_);
|
|
CHECK_EQ(err, 0);
|
|
}
|
|
|
|
bool InspectorIo::IsConnected() {
|
|
return delegate_ != nullptr && delegate_->IsConnected();
|
|
}
|
|
|
|
bool InspectorIo::IsStarted() {
|
|
return platform_ != nullptr;
|
|
}
|
|
|
|
void InspectorIo::WaitForDisconnect() {
|
|
if (state_ == State::kConnected) {
|
|
shutting_down_ = true;
|
|
Write(TransportAction::kStop, 0, StringView());
|
|
fprintf(stderr, "Waiting for the debugger to disconnect...\n");
|
|
fflush(stderr);
|
|
parent_env_->inspector_agent()->RunMessageLoop();
|
|
}
|
|
}
|
|
|
|
// static
|
|
void InspectorIo::ThreadCbIO(void* io) {
|
|
static_cast<InspectorIo*>(io)->WorkerRunIO<InspectorSocketServer>();
|
|
}
|
|
|
|
// static
|
|
template <typename Transport>
|
|
void InspectorIo::WriteCbIO(uv_async_t* async) {
|
|
TransportAndIo<Transport>* io_and_transport =
|
|
static_cast<TransportAndIo<Transport>*>(async->data);
|
|
if (io_and_transport == nullptr) {
|
|
return;
|
|
}
|
|
MessageQueue<TransportAction> outgoing_messages;
|
|
InspectorIo* io = io_and_transport->second;
|
|
io->SwapBehindLock(&io->outgoing_message_queue_, &outgoing_messages);
|
|
for (const auto& outgoing : outgoing_messages) {
|
|
switch (std::get<0>(outgoing)) {
|
|
case TransportAction::kStop:
|
|
io_and_transport->first->Stop(nullptr);
|
|
break;
|
|
case TransportAction::kSendMessage:
|
|
std::string message = StringViewToUtf8(std::get<2>(outgoing)->string());
|
|
io_and_transport->first->Send(std::get<1>(outgoing), message);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
template<typename Transport>
|
|
void InspectorIo::WorkerRunIO() {
|
|
uv_loop_t loop;
|
|
loop.data = nullptr;
|
|
int err = uv_loop_init(&loop);
|
|
CHECK_EQ(err, 0);
|
|
io_thread_req_.data = nullptr;
|
|
err = uv_async_init(&loop, &io_thread_req_, WriteCbIO<Transport>);
|
|
CHECK_EQ(err, 0);
|
|
std::string script_path;
|
|
if (!script_name_.empty()) {
|
|
uv_fs_t req;
|
|
req.ptr = nullptr;
|
|
if (0 == uv_fs_realpath(&loop, &req, script_name_.c_str(), nullptr)) {
|
|
CHECK_NE(req.ptr, nullptr);
|
|
script_path = std::string(static_cast<char*>(req.ptr));
|
|
}
|
|
uv_fs_req_cleanup(&req);
|
|
}
|
|
InspectorIoDelegate delegate(this, script_path, script_name_,
|
|
options_.wait_for_connect());
|
|
delegate_ = &delegate;
|
|
InspectorSocketServer server(&delegate,
|
|
options_.host_name(),
|
|
options_.port());
|
|
TransportAndIo<Transport> queue_transport(&server, this);
|
|
io_thread_req_.data = &queue_transport;
|
|
if (!server.Start(&loop)) {
|
|
state_ = State::kError; // Safe, main thread is waiting on semaphore
|
|
CHECK_EQ(0, CloseAsyncAndLoop(&io_thread_req_));
|
|
uv_sem_post(&start_sem_);
|
|
return;
|
|
}
|
|
if (!options_.wait_for_connect()) {
|
|
uv_sem_post(&start_sem_);
|
|
}
|
|
uv_run(&loop, UV_RUN_DEFAULT);
|
|
io_thread_req_.data = nullptr;
|
|
server.Stop(nullptr);
|
|
server.TerminateConnections(nullptr);
|
|
CHECK_EQ(CloseAsyncAndLoop(&io_thread_req_), 0);
|
|
delegate_ = nullptr;
|
|
}
|
|
|
|
template <typename ActionType>
|
|
bool InspectorIo::AppendMessage(MessageQueue<ActionType>* queue,
|
|
ActionType action, int session_id,
|
|
std::unique_ptr<StringBuffer> buffer) {
|
|
Mutex::ScopedLock scoped_lock(state_lock_);
|
|
bool trigger_pumping = queue->empty();
|
|
queue->push_back(std::make_tuple(action, session_id, std::move(buffer)));
|
|
return trigger_pumping;
|
|
}
|
|
|
|
template <typename ActionType>
|
|
void InspectorIo::SwapBehindLock(MessageQueue<ActionType>* vector1,
|
|
MessageQueue<ActionType>* vector2) {
|
|
Mutex::ScopedLock scoped_lock(state_lock_);
|
|
vector1->swap(*vector2);
|
|
}
|
|
|
|
void InspectorIo::PostIncomingMessage(InspectorAction action, int session_id,
|
|
const std::string& message) {
|
|
if (AppendMessage(&incoming_message_queue_, action, session_id,
|
|
Utf8ToStringView(message))) {
|
|
v8::Isolate* isolate = parent_env_->isolate();
|
|
platform_->CallOnForegroundThread(isolate,
|
|
new DispatchOnInspectorBackendTask(this));
|
|
isolate->RequestInterrupt(InterruptCallback, this);
|
|
CHECK_EQ(0, uv_async_send(&main_thread_req_));
|
|
}
|
|
NotifyMessageReceived();
|
|
}
|
|
|
|
void InspectorIo::WaitForFrontendMessage() {
|
|
Mutex::ScopedLock scoped_lock(state_lock_);
|
|
if (incoming_message_queue_.empty())
|
|
incoming_message_cond_.Wait(scoped_lock);
|
|
}
|
|
|
|
void InspectorIo::NotifyMessageReceived() {
|
|
Mutex::ScopedLock scoped_lock(state_lock_);
|
|
incoming_message_cond_.Broadcast(scoped_lock);
|
|
}
|
|
|
|
void InspectorIo::DispatchMessages() {
|
|
// This function can be reentered if there was an incoming message while
|
|
// V8 was processing another inspector request (e.g. if the user is
|
|
// evaluating a long-running JS code snippet). This can happen only at
|
|
// specific points (e.g. the lines that call inspector_ methods)
|
|
if (dispatching_messages_)
|
|
return;
|
|
dispatching_messages_ = true;
|
|
MessageQueue<InspectorAction> tasks;
|
|
do {
|
|
tasks.clear();
|
|
SwapBehindLock(&incoming_message_queue_, &tasks);
|
|
for (const auto& task : tasks) {
|
|
StringView message = std::get<2>(task)->string();
|
|
switch (std::get<0>(task)) {
|
|
case InspectorAction::kStartSession:
|
|
CHECK_EQ(session_delegate_, nullptr);
|
|
session_id_ = std::get<1>(task);
|
|
state_ = State::kConnected;
|
|
fprintf(stderr, "Debugger attached.\n");
|
|
session_delegate_ = std::unique_ptr<InspectorSessionDelegate>(
|
|
new IoSessionDelegate(this));
|
|
parent_env_->inspector_agent()->Connect(session_delegate_.get());
|
|
break;
|
|
case InspectorAction::kEndSession:
|
|
CHECK_NE(session_delegate_, nullptr);
|
|
if (shutting_down_) {
|
|
state_ = State::kDone;
|
|
} else {
|
|
state_ = State::kAccepting;
|
|
}
|
|
parent_env_->inspector_agent()->Disconnect();
|
|
session_delegate_.reset();
|
|
break;
|
|
case InspectorAction::kSendMessage:
|
|
parent_env_->inspector_agent()->Dispatch(message);
|
|
break;
|
|
}
|
|
}
|
|
} while (!tasks.empty());
|
|
dispatching_messages_ = false;
|
|
}
|
|
|
|
// static
|
|
void InspectorIo::MainThreadAsyncCb(uv_async_t* req) {
|
|
InspectorIo* io = node::ContainerOf(&InspectorIo::main_thread_req_, req);
|
|
io->DispatchMessages();
|
|
}
|
|
|
|
void InspectorIo::Write(TransportAction action, int session_id,
|
|
const StringView& inspector_message) {
|
|
AppendMessage(&outgoing_message_queue_, action, session_id,
|
|
StringBuffer::create(inspector_message));
|
|
int err = uv_async_send(&io_thread_req_);
|
|
CHECK_EQ(0, err);
|
|
}
|
|
|
|
InspectorIoDelegate::InspectorIoDelegate(InspectorIo* io,
|
|
const std::string& script_path,
|
|
const std::string& script_name,
|
|
bool wait)
|
|
: io_(io),
|
|
connected_(false),
|
|
session_id_(0),
|
|
script_name_(script_name),
|
|
script_path_(script_path),
|
|
target_id_(GenerateID()),
|
|
waiting_(wait) { }
|
|
|
|
|
|
bool InspectorIoDelegate::StartSession(int session_id,
|
|
const std::string& target_id) {
|
|
if (connected_)
|
|
return false;
|
|
connected_ = true;
|
|
session_id_++;
|
|
io_->PostIncomingMessage(InspectorAction::kStartSession, session_id, "");
|
|
return true;
|
|
}
|
|
|
|
void InspectorIoDelegate::MessageReceived(int session_id,
|
|
const std::string& message) {
|
|
// TODO(pfeldman): Instead of blocking execution while debugger
|
|
// engages, node should wait for the run callback from the remote client
|
|
// and initiate its startup. This is a change to node.cc that should be
|
|
// upstreamed separately.
|
|
if (waiting_) {
|
|
if (message.find("\"Runtime.runIfWaitingForDebugger\"") !=
|
|
std::string::npos) {
|
|
waiting_ = false;
|
|
io_->ResumeStartup();
|
|
}
|
|
}
|
|
io_->PostIncomingMessage(InspectorAction::kSendMessage, session_id,
|
|
message);
|
|
}
|
|
|
|
void InspectorIoDelegate::EndSession(int session_id) {
|
|
connected_ = false;
|
|
io_->PostIncomingMessage(InspectorAction::kEndSession, session_id, "");
|
|
}
|
|
|
|
std::vector<std::string> InspectorIoDelegate::GetTargetIds() {
|
|
return { target_id_ };
|
|
}
|
|
|
|
std::string InspectorIoDelegate::GetTargetTitle(const std::string& id) {
|
|
return script_name_.empty() ? GetProcessTitle() : script_name_;
|
|
}
|
|
|
|
std::string InspectorIoDelegate::GetTargetUrl(const std::string& id) {
|
|
return "file://" + script_path_;
|
|
}
|
|
|
|
bool IoSessionDelegate::WaitForFrontendMessage() {
|
|
io_->WaitForFrontendMessage();
|
|
return true;
|
|
}
|
|
|
|
void IoSessionDelegate::OnMessage(const v8_inspector::StringView& message) {
|
|
io_->Write(TransportAction::kSendMessage, io_->session_id_, message);
|
|
}
|
|
|
|
} // namespace inspector
|
|
} // namespace node
|
|
|