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.
533 lines
18 KiB
533 lines
18 KiB
#include "inspector_io.h"
|
|
#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR
|
|
|
|
#include "inspector_socket_server.h"
|
|
#include "env.h"
|
|
#include "node.h"
|
|
//cjh #include "node_crypto.h"
|
|
#include "node_mutex.h"
|
|
#include "v8-inspector.h"
|
|
#include "util.h"
|
|
#include "zlib.h"
|
|
|
|
#include "libplatform/libplatform.h"
|
|
|
|
#include <sstream>
|
|
//cjh #include <unicode/unistr.h>
|
|
|
|
#include <string.h>
|
|
#include <vector>
|
|
|
|
#include "base/UTF8.h" //cjh added
|
|
|
|
namespace node {
|
|
namespace inspector {
|
|
namespace {
|
|
using AsyncAndAgent = std::pair<uv_async_t, Agent *>;
|
|
using v8_inspector::StringBuffer;
|
|
using v8_inspector::StringView;
|
|
|
|
template <typename Transport>
|
|
using TransportAndIo = std::pair<Transport *, InspectorIo *>;
|
|
|
|
std::string GetProcessTitle() {
|
|
char title[2048];
|
|
int err = uv_get_process_title(title, sizeof(title));
|
|
if (err == 0) {
|
|
return title;
|
|
} else {
|
|
// Title is too long, or could not be retrieved.
|
|
return "Node.js";
|
|
}
|
|
}
|
|
|
|
std::string ScriptPath(uv_loop_t *loop, const std::string &script_name) {
|
|
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);
|
|
}
|
|
|
|
return script_path;
|
|
}
|
|
|
|
// UUID RFC: https://www.ietf.org/rfc/rfc4122.txt
|
|
// Used ver 4 - with numbers
|
|
std::string GenerateID() {
|
|
// uint16_t buffer[8];
|
|
|
|
//cjh same uuid
|
|
uint16_t buffer[8] = {1, 2, 3, 4, 5, 6, 7, 8};
|
|
|
|
//cjh
|
|
//cjh 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();
|
|
|
|
std::u16string u16Str((char16_t *)source);
|
|
std::string ret;
|
|
cc::StringUtils::UTF16ToUTF8(u16Str, ret);
|
|
return ret;
|
|
// 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');
|
|
//cjh 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;
|
|
|
|
return "";
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
// Delete main_thread_req_ on async handle close
|
|
void ReleasePairOnAsyncClose(uv_handle_t *async) {
|
|
AsyncAndAgent *pair = node::ContainerOf(&AsyncAndAgent::first,
|
|
reinterpret_cast<uv_async_t *>(async));
|
|
delete pair;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
std::unique_ptr<StringBuffer> Utf8ToStringView(const std::string &message) {
|
|
//cjh UnicodeString utf16 =
|
|
// UnicodeString::fromUTF8(StringPiece(message.data(), message.length()));
|
|
|
|
std::u16string u16Str;
|
|
cc::StringUtils::UTF8ToUTF16(message, u16Str);
|
|
|
|
// StringView view(reinterpret_cast<const uint16_t*>(utf16.getBuffer()),
|
|
// utf16.length());
|
|
|
|
StringView view(reinterpret_cast<const uint16_t *>(u16Str.c_str()),
|
|
u16Str.length());
|
|
return StringBuffer::create(view);
|
|
}
|
|
|
|
class IoSessionDelegate : public InspectorSessionDelegate {
|
|
public:
|
|
explicit IoSessionDelegate(InspectorIo *io) : io_(io) {}
|
|
bool WaitForFrontendMessageWhilePaused() override;
|
|
void SendMessageToFrontend(const v8_inspector::StringView &message) override;
|
|
|
|
private:
|
|
InspectorIo *io_;
|
|
};
|
|
|
|
// Passed to InspectorSocketServer to handle WS inspector protocol events,
|
|
// mostly session start, message received, and session end.
|
|
class InspectorIoDelegate : public node::inspector::SocketServerDelegate {
|
|
public:
|
|
InspectorIoDelegate(InspectorIo *io, const std::string &script_path,
|
|
const std::string &script_name, bool wait);
|
|
// Calls PostIncomingMessage() with appropriate InspectorAction:
|
|
// kStartSession
|
|
bool StartSession(int session_id, const std::string &target_id) override;
|
|
// kSendMessage
|
|
void MessageReceived(int session_id, const std::string &message) override;
|
|
// kEndSession
|
|
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_; }
|
|
void ServerDone() override {
|
|
io_->ServerDone();
|
|
}
|
|
|
|
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 *agent) {
|
|
InspectorIo *io = static_cast<Agent *>(agent)->io();
|
|
if (io != nullptr)
|
|
io->DispatchMessages();
|
|
}
|
|
|
|
class DispatchMessagesTask : public v8::Task {
|
|
public:
|
|
explicit DispatchMessagesTask(Agent *agent) : agent_(agent) {}
|
|
|
|
void Run() override {
|
|
InspectorIo *io = agent_->io();
|
|
if (io != nullptr)
|
|
io->DispatchMessages();
|
|
}
|
|
|
|
private:
|
|
Agent *agent_;
|
|
};
|
|
|
|
InspectorIo::InspectorIo(Environment *env, v8::Platform *platform,
|
|
const std::string &path, const DebugOptions &options,
|
|
bool wait_for_connect)
|
|
: options_(options),
|
|
thread_(),
|
|
delegate_(nullptr),
|
|
state_(State::kNew),
|
|
parent_env_(env),
|
|
thread_req_(),
|
|
platform_(platform),
|
|
dispatching_messages_(false),
|
|
session_id_(0),
|
|
script_name_(path),
|
|
wait_for_connect_(wait_for_connect),
|
|
port_(-1) {
|
|
main_thread_req_ = new AsyncAndAgent({uv_async_t(), env->inspector_agent()});
|
|
CHECK_EQ(0, uv_async_init(env->event_loop(), &main_thread_req_->first,
|
|
InspectorIo::MainThreadReqAsyncCb));
|
|
uv_unref(reinterpret_cast<uv_handle_t *>(&main_thread_req_->first));
|
|
CHECK_EQ(0, uv_sem_init(&thread_start_sem_, 0));
|
|
}
|
|
|
|
InspectorIo::~InspectorIo() {
|
|
uv_sem_destroy(&thread_start_sem_);
|
|
uv_close(reinterpret_cast<uv_handle_t *>(&main_thread_req_->first),
|
|
ReleasePairOnAsyncClose);
|
|
}
|
|
|
|
bool InspectorIo::Start() {
|
|
CHECK_EQ(state_, State::kNew);
|
|
CHECK_EQ(uv_thread_create(&thread_, InspectorIo::ThreadMain, this), 0);
|
|
uv_sem_wait(&thread_start_sem_);
|
|
|
|
if (state_ == State::kError) {
|
|
return false;
|
|
}
|
|
state_ = State::kAccepting;
|
|
if (wait_for_connect_) {
|
|
DispatchMessages();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void InspectorIo::Stop() {
|
|
CHECK(state_ == State::kAccepting || state_ == State::kConnected);
|
|
Write(TransportAction::kKill, 0, StringView());
|
|
int err = uv_thread_join(&thread_);
|
|
CHECK_EQ(err, 0);
|
|
state_ = State::kShutDown;
|
|
DispatchMessages();
|
|
}
|
|
|
|
bool InspectorIo::IsConnected() {
|
|
return delegate_ != nullptr && delegate_->IsConnected();
|
|
}
|
|
|
|
bool InspectorIo::IsStarted() {
|
|
return platform_ != nullptr;
|
|
}
|
|
|
|
void InspectorIo::WaitForDisconnect() {
|
|
if (state_ == State::kAccepting)
|
|
state_ = State::kDone;
|
|
if (state_ == State::kConnected) {
|
|
state_ = State::kShutDown;
|
|
Write(TransportAction::kStop, 0, StringView());
|
|
SE_LOGD("Waiting for the debugger to disconnect...\n");
|
|
parent_env_->inspector_agent()->RunMessageLoop();
|
|
}
|
|
}
|
|
|
|
// static
|
|
void InspectorIo::ThreadMain(void *io) {
|
|
static_cast<InspectorIo *>(io)->ThreadMain<InspectorSocketServer>();
|
|
}
|
|
|
|
// static
|
|
template <typename Transport>
|
|
void InspectorIo::IoThreadAsyncCb(uv_async_t *async) {
|
|
TransportAndIo<Transport> *transport_and_io =
|
|
static_cast<TransportAndIo<Transport> *>(async->data);
|
|
if (transport_and_io == nullptr) {
|
|
return;
|
|
}
|
|
Transport *transport = transport_and_io->first;
|
|
InspectorIo *io = transport_and_io->second;
|
|
MessageQueue<TransportAction> outgoing_message_queue;
|
|
io->SwapBehindLock(&io->outgoing_message_queue_, &outgoing_message_queue);
|
|
for (const auto &outgoing : outgoing_message_queue) {
|
|
switch (std::get<0>(outgoing)) {
|
|
case TransportAction::kKill:
|
|
transport->TerminateConnections();
|
|
// Fallthrough
|
|
case TransportAction::kStop:
|
|
transport->Stop(nullptr);
|
|
break;
|
|
case TransportAction::kSendMessage:
|
|
std::string message = StringViewToUtf8(std::get<2>(outgoing)->string());
|
|
transport->Send(std::get<1>(outgoing), message);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename Transport>
|
|
void InspectorIo::ThreadMain() {
|
|
uv_loop_t loop;
|
|
loop.data = nullptr;
|
|
int err = uv_loop_init(&loop);
|
|
CHECK_EQ(err, 0);
|
|
thread_req_.data = nullptr;
|
|
err = uv_async_init(&loop, &thread_req_, IoThreadAsyncCb<Transport>);
|
|
CHECK_EQ(err, 0);
|
|
std::string script_path = ScriptPath(&loop, script_name_);
|
|
InspectorIoDelegate delegate(this, script_path, script_name_,
|
|
wait_for_connect_);
|
|
delegate_ = &delegate;
|
|
Transport server(&delegate, &loop, options_.host_name(), options_.port());
|
|
TransportAndIo<Transport> queue_transport(&server, this);
|
|
thread_req_.data = &queue_transport;
|
|
if (!server.Start()) {
|
|
state_ = State::kError; // Safe, main thread is waiting on semaphore
|
|
CHECK_EQ(0, CloseAsyncAndLoop(&thread_req_));
|
|
uv_sem_post(&thread_start_sem_);
|
|
return;
|
|
}
|
|
port_ = server.Port(); // Safe, main thread is waiting on semaphore.
|
|
if (!wait_for_connect_) {
|
|
uv_sem_post(&thread_start_sem_);
|
|
}
|
|
uv_run(&loop, UV_RUN_DEFAULT);
|
|
thread_req_.data = nullptr;
|
|
CHECK_EQ(uv_loop_close(&loop), 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))) {
|
|
Agent *agent = main_thread_req_->second;
|
|
v8::Isolate *isolate = parent_env_->isolate();
|
|
platform_->GetForegroundTaskRunner(isolate)->PostTask(std::make_unique<DispatchMessagesTask>(agent));
|
|
isolate->RequestInterrupt(InterruptCallback, agent);
|
|
CHECK_EQ(0, uv_async_send(&main_thread_req_->first));
|
|
}
|
|
NotifyMessageReceived();
|
|
}
|
|
|
|
std::vector<std::string> InspectorIo::GetTargetIds() const {
|
|
return delegate_ ? delegate_->GetTargetIds() : std::vector<std::string>();
|
|
}
|
|
|
|
void InspectorIo::WaitForFrontendMessageWhilePaused() {
|
|
dispatching_messages_ = false;
|
|
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;
|
|
bool had_messages = false;
|
|
do {
|
|
if (dispatching_message_queue_.empty())
|
|
SwapBehindLock(&incoming_message_queue_, &dispatching_message_queue_);
|
|
had_messages = !dispatching_message_queue_.empty();
|
|
while (!dispatching_message_queue_.empty()) {
|
|
MessageQueue<InspectorAction>::value_type task;
|
|
std::swap(dispatching_message_queue_.front(), task);
|
|
dispatching_message_queue_.pop_front();
|
|
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;
|
|
SE_LOGD("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 (state_ == State::kShutDown) {
|
|
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 (had_messages);
|
|
dispatching_messages_ = false;
|
|
}
|
|
|
|
// static
|
|
void InspectorIo::MainThreadReqAsyncCb(uv_async_t *req) {
|
|
AsyncAndAgent *pair = node::ContainerOf(&AsyncAndAgent::first, req);
|
|
// Note that this may be called after io was closed or even after a new
|
|
// one was created and ran.
|
|
InspectorIo *io = pair->second->io();
|
|
if (io != nullptr)
|
|
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(&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) {
|
|
// REFINE(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::WaitForFrontendMessageWhilePaused() {
|
|
io_->WaitForFrontendMessageWhilePaused();
|
|
return true;
|
|
}
|
|
|
|
void IoSessionDelegate::SendMessageToFrontend(
|
|
const v8_inspector::StringView &message) {
|
|
io_->Write(TransportAction::kSendMessage, io_->session_id_, message);
|
|
}
|
|
|
|
} // namespace inspector
|
|
} // namespace node
|
|
|
|
#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR
|
|
|